Reference
Events
Every output a template can bind, the exact payload each one carries, when it is delivered, and which platforms cannot deliver it — with the reason they give when you subscribe anyway.
An output here is not a DOM event with a cast on top. Each one is typed, each one carries a declared payload, and each one is a cold observable: the platform’s recogniser or listener is attached when Angular subscribes and let go when the view is destroyed. A view nobody is listening to costs nothing.
import type { NativePanEvent } from '@angular-native/primitives'
onPan(event: NativePanEvent) { … }When they arrive
Section titled “When they arrive”Never in the middle of a frame. A native event is queued and handed to JavaScript at the start of the next frame, so everything that happened between two vsyncs is processed in one turn and produces one commit.
A drag gives one (pan) per frame, not one per touch sample. The extra samples
would each cost a change-detection pass and produce a tree identical to the one
that frame is going to mount anyway.
On every primitive
Section titled “On every primitive”These live on the base class, so all 25 have them.
Gestures
Section titled “Gestures”| Output | Payload |
|---|---|
(press) |
{ x, y } — points, relative to the view that took it |
(doublePress) |
{ x, y } |
(longPress) |
{ x, y } — once only, when the system decides it counts |
(pan) |
{ x, y, translationX, translationY, velocityX, velocityY, state } |
(pinch) |
{ scale, velocity, state } — scale is relative to the gesture’s start |
(rotation) |
{ rotation, velocity, state } — radians since the gesture began |
(swipeLeft) (swipeRight) (swipeUp) (swipeDown) |
{ x, y } |
state is 'begin' | 'move' | 'end' | 'cancel'. cancel is not a failure — the
system took the gesture away because another one won. velocity is in points
per second, useful for coasting on after the finger lifts.
translation is measured from where the finger started, not from the previous
event.
The full story — recognisers per platform, what cancel means in practice, and
how these compose with [animate] — is in
gestures and animation.
Layout and the screen
Section titled “Layout and the screen”| Output | Payload | |
|---|---|---|
(layout) |
{ x, y, width, height } |
The resolved frame, relative to the parent, every time it changes. |
(safeArea) |
{ top, right, bottom, left } |
The margins the system reserves — including the keyboard. |
(layout) is emitted by the core, not by any platform: the core is what
computes the frame, so it works identically everywhere and was never implemented
twice.
(safeArea) changes on rotation, on going into split screen, and while the
keyboard animates. See the safe area and the keyboard.
Focus and the pointer
Section titled “Focus and the pointer”| Output | Payload | |
|---|---|---|
(focus) |
{ value? } |
value only when the view is a text field. |
(blur) |
{ value? } |
|
(hover) |
{ hovered, x, y } |
Desktop only. One output rather than two, because one NSTrackingArea delivers both. |
Focus used to live on an-text-input alone, because on a phone focus belongs to
the keyboard. On a TV it is the whole platform — the remote walks the focusable
views and there is no other way to highlight the one under the cursor — so it
moved to the base.
The watch
Section titled “The watch”| Output | Payload | |
|---|---|---|
(crown) |
{ delta, offset, velocity } |
The digital crown, while it turns. |
(crownIdle) |
— | It stopped. Without this there is no way to know when to stop. |
delta is the change since the last notification, which is nearly always what
is wanted; SwiftUI only hands over the running total, and offset is that
total since the view took focus.
It is on the base class and not on a control because the crown goes to whichever view holds focus, whatever it is — the equivalent of rolling a mouse wheel over something.
Per primitive
Section titled “Per primitive”| Primitive | Output | Payload |
|---|---|---|
an-switch |
(onChange) |
boolean |
an-slider |
(valueChange) |
number |
an-stepper |
(change) |
{ value: number } |
an-segmented-control |
(change) |
{ index: number } |
an-select |
(change) |
{ index: number } |
an-date-picker |
(change) |
{ value: number } |
an-tab-bar |
(select) |
number — the index |
an-text-input |
(valueChange) |
string |
(submit) |
string — the return key |
|
an-textarea |
(change) |
{ value: string } |
an-search-bar |
(input) |
{ value: string } |
(submit) |
{ value: string } |
|
an-scroll-view |
(scroll) |
{ x, y } |
(refresh) |
— pull to refresh | |
an-image |
(load) |
{ width, height } — the image’s intrinsic size |
an-stack-view |
(back) |
— the edge gesture, or Android’s button |
an-navigation-bar |
(back) |
— the back item |
an-modal |
(dismiss) |
— dismissed by the user rather than by the prop |
an-alert |
(select) |
number — which button |
Six of these hand over the value itself rather than an object —
(onChange), (valueChange) on both the slider and the text input,
(select) on the tab bar and the alert — because there was nothing else in the
payload to keep it company and $event.value on every one of them was noise.
The rest keep the object, so that adding a second field later is not a breaking
change.
What a platform cannot deliver
Section titled “What a platform cannot deliver”An event a platform cannot give warns when you subscribe, and the warning carries the reason. It does not stay silent, and the reason is nearly always the SDK’s rather than a decision made here.
| Event | Why not |
|---|---|
(pinch), (rotation) |
The remote’s surface is single-touch, and UIPinchGestureRecognizer and UIRotationGestureRecognizer are not in the tvOS SDK. |
(pan) and the four swipes do work — the surface reports a drag. (press)
arrives from the centre button, so a view that cannot take focus can never be
pressed.
(swipeLeft) and friends work, but only on views this host owns. AppKit has
no NSSwipeGestureRecognizer; the gesture arrives as swipeWithEvent: down the
responder chain, which has to be handled on the class. Bind a swipe directly to
a system control — an NSButton, an NSSlider — and it warns on subscribe and
says to put it on a wrapping an-view instead.
watchOS
Section titled “watchOS”The one host that is not a view hierarchy, and the one with the longest list.
| Event | The reason it gives |
|---|---|
(pinch) |
MagnifyGesture is @available(watchOS, unavailable), and two fingers do not fit on a 40 mm screen. |
(rotation) |
RotateGesture is @available(watchOS, unavailable). |
(back) |
Outside a NavigationStack the watch gives no edge drag, and putting one up would nest SwiftUI’s layout inside taffy’s. |
(refresh) |
On a watch you do not pull a list down to reload it — that is the crown, which already arrives as (crown). |
(scroll) |
SwiftUI’s ScrollView does not publish its offset on watchOS 11, this shell’s minimum. |
(safeArea) |
A watch app takes the whole screen and the system reserves no margins that could be asked about. |
(focus), (blur) |
Not yet: on a watch, focus is the same focus that decides who holds the crown, and two owners would make the crown jump elsewhere while typing. |
Phones have no pointer
Section titled “Phones have no pointer”(hover) is desktop-only, and so is the [cursor] prop that goes with it. A
finger has no shape and nothing hovers before it touches. That is declared
rather than forgotten.
The warning that is deliberately not printed
Section titled “The warning that is deliberately not printed”Angular registers an element listener for every output that appears in a
template, including ones that are not platform events at all — onChange,
valueChange. Warning about those would mean a warning on every startup about
something that works perfectly, and a warning that always appears is a warning
nobody reads.
So the hosts keep a list of the events they know how to attach, and warn only about the first kind: something a template genuinely asked the platform for and this platform does not give.