Skip to content

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) {}

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.

These live on the base class, so all 25 have them.

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.

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.

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.

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.

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.

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.

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.

(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.