@symbiote-native/angular 0.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/babel-register-composed.cjs +58 -18
- package/bin/symbiote-angular-dev.cjs +16 -4
- package/build/angular/anchor-host-registry.js +1 -15
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/change-detection-flush.d.ts +31 -0
- package/build/angular/change-detection-flush.js +110 -0
- package/build/angular/change-detection-flush.js.map +1 -0
- package/build/angular/components/activity-indicator-props.d.ts +2 -0
- package/build/angular/components/activity-indicator-props.js +2 -0
- package/build/angular/components/activity-indicator-props.js.map +1 -0
- package/build/angular/components/button-props.d.ts +2 -0
- package/build/angular/components/button-props.js +2 -0
- package/build/angular/components/button-props.js.map +1 -0
- package/build/angular/components/flat-list/index.d.ts +2 -1
- package/build/angular/components/flat-list/index.js +22 -10
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-background-props.d.ts +6 -0
- package/build/angular/components/image-background-props.js +2 -0
- package/build/angular/components/image-background-props.js.map +1 -0
- package/build/angular/components/{image/shared.d.ts → image-shared.d.ts} +4 -4
- package/build/angular/components/{image/shared.js → image-shared.js} +39 -28
- package/build/angular/components/image-shared.js.map +1 -0
- package/build/angular/components/input-accessory-view-props.d.ts +7 -0
- package/build/angular/components/input-accessory-view-props.js +9 -0
- package/build/angular/components/input-accessory-view-props.js.map +1 -0
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +28 -27
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +2 -0
- package/build/angular/components/modal/index.js +33 -20
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable-props.d.ts +32 -0
- package/build/angular/components/pressable-props.js +11 -0
- package/build/angular/components/pressable-props.js.map +1 -0
- package/build/angular/components/refresh-control-props.d.ts +16 -0
- package/build/angular/components/refresh-control-props.js +14 -0
- package/build/angular/components/refresh-control-props.js.map +1 -0
- package/build/angular/components/safe-area-view-props.d.ts +7 -0
- package/build/angular/components/safe-area-view-props.js +11 -0
- package/build/angular/components/safe-area-view-props.js.map +1 -0
- package/build/angular/components/scroll-view-props.d.ts +76 -0
- package/build/angular/components/scroll-view-props.js +18 -0
- package/build/angular/components/scroll-view-props.js.map +1 -0
- package/build/angular/components/section-list/index.d.ts +3 -0
- package/build/angular/components/section-list/index.js +10 -1
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/text-input-props.d.ts +45 -0
- package/build/angular/components/text-input-props.js +18 -0
- package/build/angular/components/text-input-props.js.map +1 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +6 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js +2 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js.map +1 -0
- package/build/angular/components/touchable-props.d.ts +14 -0
- package/build/angular/components/touchable-props.js +13 -0
- package/build/angular/components/touchable-props.js.map +1 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js +2 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js.map +1 -0
- package/build/angular/components/virtualized-list/index.d.ts +8 -1
- package/build/angular/components/virtualized-list/index.js +373 -278
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +3 -0
- package/build/angular/components/virtualized-section-list/index.js +10 -1
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +21 -27
- package/build/angular/components.js +13 -15
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.d.ts +11 -2
- package/build/angular/create-portal/index.js +37 -11
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +1 -1
- package/build/angular/element-props.d.ts +53 -0
- package/build/angular/element-props.js +2 -0
- package/build/angular/element-props.js.map +1 -0
- package/build/angular/elements.d.ts +528 -0
- package/build/angular/elements.js +1444 -0
- package/build/angular/elements.js.map +1 -0
- package/build/angular/gate-demand.d.ts +37 -0
- package/build/angular/gate-demand.js +84 -0
- package/build/angular/gate-demand.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/index.d.ts +5 -2
- package/build/angular/index.js +16 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +6 -6
- package/build/angular/modules/animated/create-animated-component.js +73 -81
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.js +1 -1
- package/build/angular/modules/animated/index.js.map +1 -1
- package/build/angular/modules/image/index.d.ts +3 -0
- package/build/angular/modules/image/index.js +16 -0
- package/build/angular/modules/image/index.js.map +1 -0
- package/build/angular/primitives/index.d.ts +28 -16
- package/build/angular/primitives/index.js +77 -31
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +2 -3
- package/build/angular/primitives/shared.js +8 -53
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -0
- package/build/angular/register.js +16 -0
- package/build/angular/register.js.map +1 -0
- package/build/angular/render/index.js +4 -2
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.js +207 -60
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/renderer/value-change.d.ts +2 -0
- package/build/angular/renderer/value-change.js +9 -0
- package/build/angular/renderer/value-change.js.map +1 -0
- package/metro-config.cjs +23 -5
- package/metro-css-parser.cjs +2 -1
- package/package.json +17 -7
- package/src/anchor-host-registry.ts +1 -15
- package/src/change-detection-flush.ts +120 -0
- package/src/components/activity-indicator-props.ts +21 -0
- package/src/components/button-props.ts +26 -0
- package/src/components/flat-list/index.ts +12 -4
- package/src/components/image-background-props.ts +24 -0
- package/src/components/{image/shared.ts → image-shared.ts} +57 -26
- package/src/components/input-accessory-view-props.ts +21 -0
- package/src/components/keyboard-avoiding-view/index.ts +27 -16
- package/src/components/modal/index.ts +32 -11
- package/src/components/pressable-props.ts +73 -0
- package/src/components/refresh-control-props.ts +43 -0
- package/src/components/safe-area-view-props.ts +28 -0
- package/src/components/scroll-view-props.ts +109 -0
- package/src/components/section-list/index.ts +18 -0
- package/src/components/text-input-props.ts +72 -0
- package/src/components/touchable-native-feedback/touchable-native-feedback-props.ts +23 -0
- package/src/components/touchable-props.ts +30 -0
- package/src/components/touchable-without-feedback/touchable-without-feedback-props.ts +21 -0
- package/src/components/virtualized-list/index.ts +254 -153
- package/src/components/virtualized-section-list/index.ts +18 -0
- package/src/components.ts +86 -35
- package/src/create-portal/index.ts +39 -9
- package/src/create-tunnel/index.ts +1 -1
- package/src/diagnostics.ts +1 -1
- package/src/element-props.ts +90 -0
- package/src/elements.ts +919 -0
- package/src/gate-demand.ts +109 -0
- package/src/host-instance/index.ts +1 -1
- package/src/index.ts +54 -15
- package/src/modules/animated/create-animated-component.ts +73 -59
- package/src/modules/animated/index.ts +1 -1
- package/src/modules/image/index.ts +18 -0
- package/src/primitives/index.ts +47 -16
- package/src/primitives/shared.ts +11 -44
- package/src/register.ts +15 -0
- package/src/render/index.ts +4 -2
- package/src/renderer/index.ts +239 -80
- package/src/renderer/value-change.ts +8 -0
- package/build/angular/components/activity-indicator/index.android.d.ts +0 -13
- package/build/angular/components/activity-indicator/index.android.js +0 -29
- package/build/angular/components/activity-indicator/index.android.js.map +0 -1
- package/build/angular/components/activity-indicator/index.d.ts +0 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +0 -9
- package/build/angular/components/activity-indicator/index.ios.js +0 -26
- package/build/angular/components/activity-indicator/index.ios.js.map +0 -1
- package/build/angular/components/activity-indicator/index.js +0 -4
- package/build/angular/components/activity-indicator/index.js.map +0 -1
- package/build/angular/components/activity-indicator/shared.d.ts +0 -67
- package/build/angular/components/activity-indicator/shared.js +0 -260
- package/build/angular/components/activity-indicator/shared.js.map +0 -1
- package/build/angular/components/button.d.ts +0 -65
- package/build/angular/components/button.js +0 -351
- package/build/angular/components/button.js.map +0 -1
- package/build/angular/components/image/index.android.d.ts +0 -15
- package/build/angular/components/image/index.android.js +0 -91
- package/build/angular/components/image/index.android.js.map +0 -1
- package/build/angular/components/image/index.d.ts +0 -1
- package/build/angular/components/image/index.ios.d.ts +0 -15
- package/build/angular/components/image/index.ios.js +0 -91
- package/build/angular/components/image/index.ios.js.map +0 -1
- package/build/angular/components/image/index.js +0 -3
- package/build/angular/components/image/index.js.map +0 -1
- package/build/angular/components/image/shared.js.map +0 -1
- package/build/angular/components/image-background.d.ts +0 -82
- package/build/angular/components/image-background.js +0 -434
- package/build/angular/components/image-background.js.map +0 -1
- package/build/angular/components/input-accessory-view/index.d.ts +0 -65
- package/build/angular/components/input-accessory-view/index.js +0 -290
- package/build/angular/components/input-accessory-view/index.js.map +0 -1
- package/build/angular/components/pressable/index.d.ts +0 -129
- package/build/angular/components/pressable/index.js +0 -473
- package/build/angular/components/pressable/index.js.map +0 -1
- package/build/angular/components/refresh-control/index.d.ts +0 -90
- package/build/angular/components/refresh-control/index.js +0 -380
- package/build/angular/components/refresh-control/index.js.map +0 -1
- package/build/angular/components/safe-area-view/index.d.ts +0 -62
- package/build/angular/components/safe-area-view/index.js +0 -287
- package/build/angular/components/safe-area-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/index.android.d.ts +0 -20
- package/build/angular/components/scroll-view/index.android.js +0 -262
- package/build/angular/components/scroll-view/index.android.js.map +0 -1
- package/build/angular/components/scroll-view/index.d.ts +0 -3
- package/build/angular/components/scroll-view/index.ios.d.ts +0 -8
- package/build/angular/components/scroll-view/index.ios.js +0 -92
- package/build/angular/components/scroll-view/index.ios.js.map +0 -1
- package/build/angular/components/scroll-view/index.js +0 -6
- package/build/angular/components/scroll-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/projection.d.ts +0 -67
- package/build/angular/components/scroll-view/projection.js +0 -560
- package/build/angular/components/scroll-view/projection.js.map +0 -1
- package/build/angular/components/scroll-view/shared.d.ts +0 -239
- package/build/angular/components/scroll-view/shared.js +0 -832
- package/build/angular/components/scroll-view/shared.js.map +0 -1
- package/build/angular/components/scroll-view/sticky-header.d.ts +0 -40
- package/build/angular/components/scroll-view/sticky-header.js +0 -224
- package/build/angular/components/scroll-view/sticky-header.js.map +0 -1
- package/build/angular/components/switch/index.android.d.ts +0 -24
- package/build/angular/components/switch/index.android.js +0 -112
- package/build/angular/components/switch/index.android.js.map +0 -1
- package/build/angular/components/switch/index.d.ts +0 -1
- package/build/angular/components/switch/index.ios.d.ts +0 -23
- package/build/angular/components/switch/index.ios.js +0 -111
- package/build/angular/components/switch/index.ios.js.map +0 -1
- package/build/angular/components/switch/index.js +0 -4
- package/build/angular/components/switch/index.js.map +0 -1
- package/build/angular/components/switch/shared.d.ts +0 -49
- package/build/angular/components/switch/shared.js +0 -191
- package/build/angular/components/switch/shared.js.map +0 -1
- package/build/angular/components/text-input.d.ts +0 -161
- package/build/angular/components/text-input.js +0 -659
- package/build/angular/components/text-input.js.map +0 -1
- package/build/angular/components/touchable/index.d.ts +0 -276
- package/build/angular/components/touchable/index.js +0 -1422
- package/build/angular/components/touchable/index.js.map +0 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +0 -86
- package/build/angular/components/touchable-native-feedback/index.js +0 -407
- package/build/angular/components/touchable-native-feedback/index.js.map +0 -1
- package/src/components/activity-indicator/index.android.ts +0 -23
- package/src/components/activity-indicator/index.ios.ts +0 -21
- package/src/components/activity-indicator/index.ts +0 -4
- package/src/components/activity-indicator/shared.ts +0 -225
- package/src/components/button.ts +0 -220
- package/src/components/image/index.android.ts +0 -99
- package/src/components/image/index.ios.ts +0 -99
- package/src/components/image/index.ts +0 -3
- package/src/components/image-background.ts +0 -290
- package/src/components/input-accessory-view/index.ts +0 -240
- package/src/components/pressable/index.ts +0 -462
- package/src/components/refresh-control/index.ts +0 -340
- package/src/components/safe-area-view/index.ts +0 -231
- package/src/components/scroll-view/index.android.ts +0 -205
- package/src/components/scroll-view/index.ios.ts +0 -81
- package/src/components/scroll-view/index.ts +0 -7
- package/src/components/scroll-view/projection.ts +0 -713
- package/src/components/scroll-view/shared.ts +0 -1066
- package/src/components/scroll-view/sticky-header.ts +0 -267
- package/src/components/switch/index.android.ts +0 -113
- package/src/components/switch/index.ios.ts +0 -112
- package/src/components/switch/index.ts +0 -4
- package/src/components/switch/shared.ts +0 -284
- package/src/components/text-input.ts +0 -613
- package/src/components/touchable/index.ts +0 -913
- package/src/components/touchable-native-feedback/index.ts +0 -317
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// A dependency-free LEAF so `renderer/index.ts` can ask a view to re-check itself without importing
|
|
2
|
+
// the element directives, which import the renderer (same reasoning as `renderer/value-change.ts`
|
|
3
|
+
// and `anchor-host-registry.ts`).
|
|
4
|
+
//
|
|
5
|
+
// WHY ANYTHING IS NEEDED. Zoneless change detection is scheduled on a
|
|
6
|
+
// requestAnimationFrame/setTimeout RACE (`ChangeDetectionSchedulerImpl`, upstream
|
|
7
|
+
// `zoneless_scheduling_impl.ts`), so an app's state update from a native event lands a whole
|
|
8
|
+
// MACROTASK later. A bound template listener DOES notify Angular — that is one of the documented
|
|
9
|
+
// notification sources — so nothing is missing; what is wrong is the TIMING. Three engine behaviors
|
|
10
|
+
// read the app's answer back within the same MICROTASK turn:
|
|
11
|
+
//
|
|
12
|
+
// behaviors/text-input.ts `afterCommit` re-commands the native text from `props.value`
|
|
13
|
+
// behaviors/switch.ts `queueMicrotask` snap-back reads `props.value`
|
|
14
|
+
// behaviors/refresh-control.ts `queueMicrotask` snap-back reads `props.refreshing`
|
|
15
|
+
//
|
|
16
|
+
// Every other adapter's state update lands inside that turn; zoneless Angular cannot, so all three
|
|
17
|
+
// read the PRE-event value and UNDO the user. Device-reported 2026-09-11: `[(value)]` on a
|
|
18
|
+
// `<text-input>` commanded the stale text back after one keystroke, a `[(value)]` `<switch>` snapped
|
|
19
|
+
// straight off again, and pull-to-refresh stopped itself.
|
|
20
|
+
//
|
|
21
|
+
// WHY `ChangeDetectorRef.detectChanges()` AND NOT `ApplicationRef.tick()`. Both re-evaluate the
|
|
22
|
+
// binding; only one of them is safe to call from a native event. `tick()` emits `afterTick`
|
|
23
|
+
// UNCONDITIONALLY — `dirtyFlags === 0` included — and the scheduler's `afterTick` subscriber runs
|
|
24
|
+
// `switchToMicrotaskScheduler`, which sets `useMicrotaskScheduler` for the rest of the turn. Every
|
|
25
|
+
// notification raised while that flag is set is counted by `trackMicrotaskNotificationForDebugging`
|
|
26
|
+
// against `CONSECUTIVE_MICROTASK_NOTIFICATION_LIMIT` (100), and the ONLY reset is a notification
|
|
27
|
+
// raised while it is clear. A commit can dispatch further events (`onLayout`), each of which
|
|
28
|
+
// notifies, so forcing a tick per native event turns that into NG0103, "Angular could not stabilize
|
|
29
|
+
// because there were endless change notifications" — device-reported the same day, on the first fix
|
|
30
|
+
// for the paragraph above. `ViewRef.detectChanges()` sets `RefreshView` and calls
|
|
31
|
+
// `detectChangesInternal` (upstream `render3/view_ref.ts`): no `afterTick`, no scheduler
|
|
32
|
+
// notification, so it cannot open that window at all. It is also the narrower answer — the only
|
|
33
|
+
// view that has to be re-checked is the one holding the binding.
|
|
34
|
+
//
|
|
35
|
+
// The flush is registered by the element DIRECTIVE, which is the only thing here that owns a
|
|
36
|
+
// `ChangeDetectorRef` for that view. A tag written under `CUSTOM_ELEMENTS_SCHEMA` with no directive
|
|
37
|
+
// matched therefore gets no flush and keeps the stale-read behaviour — the directive is the
|
|
38
|
+
// supported route (`[(value)]` is NG8007 without it), and this leaf has no way to reach a view it
|
|
39
|
+
// was never handed.
|
|
40
|
+
//
|
|
41
|
+
// When a fourth behavior starts reading an app value back, it needs a name in the renderer's
|
|
42
|
+
// `READ_BACK_*` sets — the census is `queueMicrotask` / `afterCommit` in
|
|
43
|
+
// `core/components/src/behaviors`.
|
|
44
|
+
const viewFlushes = new WeakMap<object, () => void>();
|
|
45
|
+
|
|
46
|
+
const ON_PREFIX = /^on[A-Z]/;
|
|
47
|
+
|
|
48
|
+
// The one `onX` that fires per FRAME rather than per gesture — with sticky headers RN pins
|
|
49
|
+
// `scrollEventThrottle` to 1, i.e. 60 times a second. `markForCheck` walks to the ROOT, so wrapping
|
|
50
|
+
// it costs a full ancestor-screen template execution per frame: measured as the canary's ~37fps
|
|
51
|
+
// scroll (`components/virtualized-list/scroll-cost.test.ts`). Nothing loses its refresh — the list
|
|
52
|
+
// marks itself when its window moves, sticky rides the native driver, and a caller's own handler is
|
|
53
|
+
// typically an Animated.event touching no Angular state. Drag and momentum begin/end stay wrapped;
|
|
54
|
+
// once per gesture is not a hot path.
|
|
55
|
+
const PER_FRAME_CALLBACK = 'onScroll';
|
|
56
|
+
|
|
57
|
+
interface IViewMarker {
|
|
58
|
+
markForCheck(): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type ICallbackWrapper = (key: string, value: unknown) => unknown;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Makes an app callback handed to the engine as a PROP re-enter Angular's update loop.
|
|
65
|
+
*
|
|
66
|
+
* An `(event)` binding is wrapped by Angular's own `wrapListenerIn_markDirtyAndPreventDefault` and
|
|
67
|
+
* notifies on its own. A `[onPressMove]="fn"` prop is an `@Input` forwarded to the node, and the
|
|
68
|
+
* ENGINE calls it on event dispatch — Angular is told NOTHING, so a plain field mutation inside it
|
|
69
|
+
* dirties no view and the template reading it stays stale until something unrelated ticks.
|
|
70
|
+
* Device-reported 2026-09-11: a pan readout stuck at `dx 0 · dy 0` for a whole gesture, its real
|
|
71
|
+
* numbers arriving on the next button press.
|
|
72
|
+
*
|
|
73
|
+
* `markForCheck()` is one of the two notification sources Angular sanctions for an event source
|
|
74
|
+
* outside the framework (the other is writing a signal the template reads, which an ADAPTER cannot
|
|
75
|
+
* choose on the app's behalf). It flags the INJECTING view and every ancestor with RefreshView —
|
|
76
|
+
* the reach `detectChanges()` does not have — and notifies the scheduler.
|
|
77
|
+
*
|
|
78
|
+
* `flushViewFor` is the second, narrower half: three behaviors read the app's answer back inside
|
|
79
|
+
* the SAME microtask turn, and `markForCheck` only SCHEDULES. It is a no-op on a node that
|
|
80
|
+
* registered no flush.
|
|
81
|
+
*
|
|
82
|
+
* ONE wrapper per original handler, for the caller's lifetime. A fresh closure per push would make
|
|
83
|
+
* the engine store a new listener every time and would defeat every downstream identity check.
|
|
84
|
+
*/
|
|
85
|
+
export function createCallbackWrapper(
|
|
86
|
+
view: IViewMarker,
|
|
87
|
+
node: unknown,
|
|
88
|
+
): ICallbackWrapper {
|
|
89
|
+
const wrappers = new WeakMap<object, (...args: unknown[]) => unknown>();
|
|
90
|
+
return (key: string, value: unknown): unknown => {
|
|
91
|
+
if (!ON_PREFIX.test(key) || typeof value !== 'function') return value;
|
|
92
|
+
if (key === PER_FRAME_CALLBACK) return value;
|
|
93
|
+
const cached = wrappers.get(value);
|
|
94
|
+
if (cached !== undefined) return cached;
|
|
95
|
+
// Reflect.apply rather than a cast-to-signature local: `typeof value === 'function'` narrows to
|
|
96
|
+
// `Function`, which carries no call signature, and this repo does not use `as` to paper over it.
|
|
97
|
+
// `undefined` as the receiver preserves the previous unbound call.
|
|
98
|
+
const wrapper = (...args: unknown[]): unknown => {
|
|
99
|
+
const result: unknown = Reflect.apply(value, undefined, args);
|
|
100
|
+
view.markForCheck();
|
|
101
|
+
flushViewFor(node);
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
wrappers.set(value, wrapper);
|
|
105
|
+
return wrapper;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function registerViewFlush(node: object, flush: () => void): void {
|
|
110
|
+
viewFlushes.set(node, flush);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function unregisterViewFlush(node: object): void {
|
|
114
|
+
viewFlushes.delete(node);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function flushViewFor(node: unknown): void {
|
|
118
|
+
if (typeof node !== 'object' || node === null) return;
|
|
119
|
+
viewFlushes.get(node)?.();
|
|
120
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The prop surface of `<activity-indicator>`, for Angular.
|
|
2
|
+
//
|
|
3
|
+
// No component left to type — the element IS the tag, matched by `ActivityIndicatorElement`
|
|
4
|
+
// (`../elements.ts`). RN's ActivityIndicator is a centering `<view>` around a native spinner
|
|
5
|
+
// (ActivityIndicator.js:112) and takes no children, and the engine behavior now builds both nodes
|
|
6
|
+
// (`core/components/src/behaviors/activity-indicator/`). This stays exported because an app that
|
|
7
|
+
// wraps the tag in its own component types the bag it forwards against something.
|
|
8
|
+
//
|
|
9
|
+
// The five callbacks are omitted for the reason every Angular element type omits them: they are
|
|
10
|
+
// `(layout)` / `(accessibilityAction)` / … OUTPUT bindings here, not inputs. Everything else is the
|
|
11
|
+
// shared agnostic surface, re-exported rather than redeclared.
|
|
12
|
+
import type { IActivityIndicatorProps as ICoreActivityIndicatorProps } from '@symbiote-native/components';
|
|
13
|
+
|
|
14
|
+
export type IActivityIndicatorProps = Omit<
|
|
15
|
+
ICoreActivityIndicatorProps,
|
|
16
|
+
| 'onLayout'
|
|
17
|
+
| 'onAccessibilityAction'
|
|
18
|
+
| 'onAccessibilityTap'
|
|
19
|
+
| 'onMagicTap'
|
|
20
|
+
| 'onAccessibilityEscape'
|
|
21
|
+
>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The prop surface of `<button>`, for Angular.
|
|
2
|
+
//
|
|
3
|
+
// No component left to type — the element IS the tag, matched by `ButtonElement`
|
|
4
|
+
// (`../elements.ts`). RN's Button takes no children (`title` is a string prop, Button.js:363) and
|
|
5
|
+
// builds its own touchable > view > text subtree, which the engine behavior now builds instead
|
|
6
|
+
// (`core/components/src/behaviors/button.ts`). This stays exported because an app that wraps the
|
|
7
|
+
// tag in its own component types the bag it forwards against something.
|
|
8
|
+
//
|
|
9
|
+
// The five callbacks are omitted for the reason every Angular element type omits them: they are
|
|
10
|
+
// `(press)` / `(accessibilityAction)` / … OUTPUT bindings here, not inputs. Everything else is the
|
|
11
|
+
// shared agnostic surface, re-exported rather than redeclared.
|
|
12
|
+
//
|
|
13
|
+
// `ButtonElement` inherits the touchable's own inputs (`android_ripple`, `delayLongPress`,
|
|
14
|
+
// `activeOpacity`, …) because it extends `TouchableOpacityElement`. That is the element class, not
|
|
15
|
+
// this contract: RN's Button declares none of them, so they stay out of the type an app writes
|
|
16
|
+
// against.
|
|
17
|
+
import type { IButtonProps as ICoreButtonProps } from '@symbiote-native/components';
|
|
18
|
+
|
|
19
|
+
export type IButtonProps = Omit<
|
|
20
|
+
ICoreButtonProps,
|
|
21
|
+
| 'onPress'
|
|
22
|
+
| 'onAccessibilityAction'
|
|
23
|
+
| 'onAccessibilityTap'
|
|
24
|
+
| 'onMagicTap'
|
|
25
|
+
| 'onAccessibilityEscape'
|
|
26
|
+
>;
|
|
@@ -226,20 +226,21 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
226
226
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
227
227
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
228
228
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
229
|
+
[testID]="testID"
|
|
229
230
|
[style]="resolvedStyle"
|
|
230
231
|
[contentContainerStyle]="contentContainerStyle"
|
|
231
232
|
>
|
|
232
233
|
<ng-template vListItem let-row let-separators="separators">
|
|
233
|
-
<
|
|
234
|
+
<view [style]="rowStyle">
|
|
234
235
|
@for (cell of rowCells(row, separators); track cell.key) {
|
|
235
|
-
<
|
|
236
|
+
<view [style]="columnCellStyle">
|
|
236
237
|
<ng-container
|
|
237
238
|
[vListOutlet]="itemDir?.templateRef"
|
|
238
239
|
[vListOutletContext]="cell.context"
|
|
239
240
|
></ng-container>
|
|
240
|
-
</
|
|
241
|
+
</view>
|
|
241
242
|
}
|
|
242
|
-
</
|
|
243
|
+
</view>
|
|
243
244
|
</ng-template>
|
|
244
245
|
@if (headerDir !== undefined) {
|
|
245
246
|
<ng-template vListHeader>
|
|
@@ -309,6 +310,7 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
309
310
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
310
311
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
311
312
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
313
|
+
[testID]="testID"
|
|
312
314
|
[style]="resolvedStyle"
|
|
313
315
|
[contentContainerStyle]="contentContainerStyle"
|
|
314
316
|
>
|
|
@@ -379,6 +381,12 @@ export class FlatList<ItemT = unknown>
|
|
|
379
381
|
averageItemLength: number;
|
|
380
382
|
}>();
|
|
381
383
|
@Input({ required: true }) data!: readonly ItemT[];
|
|
384
|
+
// RN's FlatList spreads its props onto the VirtualizedList it renders, so `testID` lands on the
|
|
385
|
+
// scroll view. Angular has no spread, and this one was simply missing: `<FlatList testID="x">`
|
|
386
|
+
// compiled (a static attribute on a component tag is never checked), reached the non-painting
|
|
387
|
+
// anchor host, and committed nowhere — so the list was untargetable by a test or an e2e selector
|
|
388
|
+
// while looking perfectly healthy on screen. VirtualizedList has always declared it.
|
|
389
|
+
@Input() testID?: string;
|
|
382
390
|
@Input() numColumns?: number;
|
|
383
391
|
@Input() columnWrapperStyle?: IStyleProp<IViewStyle> | string;
|
|
384
392
|
@Input() keyExtractor?: (item: ItemT, index: number) => string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The prop surface of `<image-background>`, for Angular.
|
|
2
|
+
//
|
|
3
|
+
// No component left to type — the element IS the tag; the composition lives in the engine behavior
|
|
4
|
+
// (`core/components/src/behaviors/image-background.ts`). This stays exported because an app that
|
|
5
|
+
// wraps the tag in its own component types the bag it forwards against something, and because
|
|
6
|
+
// `elements.ts` indexes the element directive's input types off it.
|
|
7
|
+
//
|
|
8
|
+
// PER-ADAPTER, not shared: Angular takes children via `<ng-content>` and its class binding is the
|
|
9
|
+
// framework's own (<prop_types_split_agnostic_vs_per_adapter>).
|
|
10
|
+
import type { IImageProps } from '@symbiote-native/components';
|
|
11
|
+
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
12
|
+
|
|
13
|
+
export interface IAngularImageBackgroundProps extends Omit<
|
|
14
|
+
IImageProps,
|
|
15
|
+
'style'
|
|
16
|
+
> {
|
|
17
|
+
// The wrapper View's style; its width/height are reapplied to the inner Image so the image fills
|
|
18
|
+
// the box rather than collapsing to its source's intrinsic size. Bound through Angular's own
|
|
19
|
+
// styling engine, so `elements.ts` excludes it from the directive's inputs like every other tag.
|
|
20
|
+
style?: IStyleProp<IViewStyle>;
|
|
21
|
+
// Style merged onto the inner absolute-fill Image, after the proxied dimensions. A bare string
|
|
22
|
+
// is a class name and resolves through the shared style registry.
|
|
23
|
+
imageStyle?: IStyleProp<IViewStyle> | string;
|
|
24
|
+
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
// The input surface and prop fold `AnimatedImage` builds on. The `Image` COMPONENT that used to
|
|
2
|
+
// share it is gone (2026-09-10) — the primitive is the `<image>` tag, matched by `ImageElement`,
|
|
3
|
+
// and `registerImageBehavior` folds its source on that path. `Animated.Image` is not a primitive
|
|
4
|
+
// and stays a component, so what it needs from here survived the deletion; the folder collapsed to
|
|
5
|
+
// this one flat file with it (`symbiote-file-layout`).
|
|
1
6
|
import { EventEmitter, computed, signal } from '@angular/core';
|
|
7
|
+
import {
|
|
8
|
+
gateWanted,
|
|
9
|
+
injectGateDemand,
|
|
10
|
+
type IGatedAccessibilityEvent,
|
|
11
|
+
} from '../gate-demand';
|
|
2
12
|
import {
|
|
3
13
|
imageStatics,
|
|
4
14
|
renderImage,
|
|
@@ -162,6 +172,14 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
162
172
|
return typeof value === 'object' && value !== null ? value : undefined;
|
|
163
173
|
}
|
|
164
174
|
|
|
175
|
+
// Narrows a value out of the untyped animated prop bag. A runtime guard rather than a cast: the
|
|
176
|
+
// bag is built at runtime from merged animated values, so nothing upstream has proven the shape.
|
|
177
|
+
export function isImageEventCallback(
|
|
178
|
+
value: unknown,
|
|
179
|
+
): value is (event: ISymbioteEvent) => void {
|
|
180
|
+
return typeof value === 'function';
|
|
181
|
+
}
|
|
182
|
+
|
|
165
183
|
export function resolveImageProps(
|
|
166
184
|
input: Record<string, unknown>,
|
|
167
185
|
): Record<string, unknown> {
|
|
@@ -382,28 +400,32 @@ export abstract class ImageBase {
|
|
|
382
400
|
readonly progress = new EventEmitter<ISymbioteEvent>();
|
|
383
401
|
readonly partialLoad = new EventEmitter<ISymbioteEvent>();
|
|
384
402
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (!
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
// Null unless an adapter wrapper renders this Image in its own template — ImageBackground does.
|
|
404
|
+
// An app's own `<Image>` gets null and answers from its own `.observed`.
|
|
405
|
+
private readonly gateDemand = injectGateDemand();
|
|
406
|
+
|
|
407
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
408
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`): native fires them only when the committed
|
|
409
|
+
// payload carries a FUNCTION at that key. A template `(accessibilityAction)="..."` binding on the
|
|
410
|
+
// intrinsic host lights the gate unconditionally; this reaches the app two ways — a plain
|
|
411
|
+
// `[onAccessibilityAction]` @Input callback, or the `accessibilityAction` @Output — and the gate
|
|
412
|
+
// must light only when at least one of the two is actually wired. Returned `undefined` means
|
|
413
|
+
// `setEventListener` never sees a function, so the gate stays dark.
|
|
414
|
+
protected gatedAccessibilityHandler(
|
|
415
|
+
name: IGatedAccessibilityEvent,
|
|
416
|
+
callback: ((event: ISymbioteEvent) => void) | undefined,
|
|
417
|
+
): ((event: Event) => void) | undefined {
|
|
418
|
+
const emitter = this[name];
|
|
419
|
+
// A WRAPPER's binding is not a subscriber, and `.observed` cannot tell the two apart.
|
|
420
|
+
// ImageBackground renders this component and binds all four, so without the demand every
|
|
421
|
+
// Image inside one lit its gates. See `gate-demand.ts`.
|
|
422
|
+
if (!gateWanted(this.gateDemand, name, emitter) && callback === undefined)
|
|
423
|
+
return undefined;
|
|
424
|
+
return event => {
|
|
425
|
+
if (!isSymbioteEvent(event)) return;
|
|
426
|
+
callback?.(event);
|
|
427
|
+
emitter.emit(event);
|
|
428
|
+
};
|
|
407
429
|
}
|
|
408
430
|
|
|
409
431
|
handleLoadStart(event: Event): void {
|
|
@@ -499,10 +521,19 @@ export abstract class ImageBase {
|
|
|
499
521
|
ariaValueMin: this.ariaValueMin,
|
|
500
522
|
ariaValueNow: this.ariaValueNow,
|
|
501
523
|
ariaValueText: this.ariaValueText,
|
|
502
|
-
onAccessibilityAction: this.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
524
|
+
onAccessibilityAction: this.gatedAccessibilityHandler(
|
|
525
|
+
'accessibilityAction',
|
|
526
|
+
this.onAccessibilityAction,
|
|
527
|
+
),
|
|
528
|
+
onAccessibilityTap: this.gatedAccessibilityHandler(
|
|
529
|
+
'accessibilityTap',
|
|
530
|
+
this.onAccessibilityTap,
|
|
531
|
+
),
|
|
532
|
+
onMagicTap: this.gatedAccessibilityHandler('magicTap', this.onMagicTap),
|
|
533
|
+
onAccessibilityEscape: this.gatedAccessibilityHandler(
|
|
534
|
+
'accessibilityEscape',
|
|
535
|
+
this.onAccessibilityEscape,
|
|
536
|
+
),
|
|
506
537
|
onLoadStart: this.onLoadStart,
|
|
507
538
|
onLoad: this.onLoad,
|
|
508
539
|
onLoadEnd: this.onLoadEnd,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The `<input-accessory-view>` tag's prop surface. The tag is matched by
|
|
2
|
+
// `InputAccessoryViewElement` (`../elements`) and commits one `RCTInputAccessoryView`; its whole
|
|
3
|
+
// wrapper was a prop FOLD with no aliasing at all (`registerInputAccessoryViewBehavior`), so it was
|
|
4
|
+
// deleted 2026-09-10 and the behavior applies the same mapping on the tag's own path.
|
|
5
|
+
//
|
|
6
|
+
// The type stays for a component forwarding a bag, and because `IAngularInputAccessoryViewProps` is
|
|
7
|
+
// public API.
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
IAccessibilityProps,
|
|
11
|
+
IAriaProps,
|
|
12
|
+
} from '@symbiote-native/components';
|
|
13
|
+
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
14
|
+
|
|
15
|
+
export interface IAngularInputAccessoryViewProps
|
|
16
|
+
extends IAccessibilityProps, IAriaProps {
|
|
17
|
+
// The id a TextInput's inputAccessoryViewID points at to dock above its keyboard.
|
|
18
|
+
nativeID?: string;
|
|
19
|
+
backgroundColor?: string;
|
|
20
|
+
style?: IStyleProp<IViewStyle>;
|
|
21
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Angular twin of React's setState / Vue's reactive ref), ngOnDestroy tears the subscriptions down,
|
|
7
7
|
// and the wrapper's onLayout measures the frame that feeds the next event's inset. The user
|
|
8
8
|
// children nest under the wrapper (or, for 'position', an inner View) via <ng-content>. No native
|
|
9
|
-
// host of its own — it wraps
|
|
9
|
+
// host of its own — it wraps view — so this stays a flat single file.
|
|
10
10
|
//
|
|
11
11
|
// Full parity: behavior 'height'|'position'|'padding', enabled, keyboardVerticalOffset,
|
|
12
12
|
// contentContainerStyle, onLayout, plus the full a11y/aria surface every View carries.
|
|
@@ -96,23 +96,22 @@ export type IAngularKeyboardAvoidingViewInputs = Omit<
|
|
|
96
96
|
// The wrapper carries a11y + onLayout; 'position' ('nested') pushes the children in an inner
|
|
97
97
|
// View by `bottom: inset`, the wrapper modes adjust the single wrapper directly. Only one @if
|
|
98
98
|
// branch is instantiated, so each branch's <ng-content> projects the children unambiguously.
|
|
99
|
+
// `layout` stays a plain, unconditional template binding: unlike the four accessibility events
|
|
100
|
+
// below, this component READS its own onLayout internally (handleLayout measures the frame that
|
|
101
|
+
// feeds the next keyboard event's inset math — see keyboard-avoiding-view-rn-contract.md), so the
|
|
102
|
+
// gate must stay lit regardless of whether the app subscribes to the `layout` @Output(). Gating it
|
|
103
|
+
// on `.observed` would silently break the inset fixpoint correction the moment an app didn't
|
|
104
|
+
// listen to `layout`.
|
|
99
105
|
template: `
|
|
100
|
-
<
|
|
101
|
-
[symbioteHostProps]="hostProps"
|
|
102
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
103
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
104
|
-
(magicTap)="emit(magicTap, $event)"
|
|
105
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
106
|
-
(layout)="handleLayout($event)"
|
|
107
|
-
>
|
|
106
|
+
<view [symbioteHostProps]="hostProps" (layout)="handleLayout($event)">
|
|
108
107
|
@if (isNested) {
|
|
109
|
-
<
|
|
108
|
+
<view [style]="innerStyle">
|
|
110
109
|
<ng-content></ng-content>
|
|
111
|
-
</
|
|
110
|
+
</view>
|
|
112
111
|
} @else {
|
|
113
112
|
<ng-content></ng-content>
|
|
114
113
|
}
|
|
115
|
-
</
|
|
114
|
+
</view>
|
|
116
115
|
`,
|
|
117
116
|
})
|
|
118
117
|
export class KeyboardAvoidingView
|
|
@@ -187,7 +186,7 @@ export class KeyboardAvoidingView
|
|
|
187
186
|
|
|
188
187
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
189
188
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
190
|
-
// onto (see anchorHostStyle's doc comment) — NOT a ViewChild into the inner
|
|
189
|
+
// onto (see anchorHostStyle's doc comment) — NOT a ViewChild into the inner view (this
|
|
191
190
|
// component has none; the wrapper IS the outer template node hostProps binds onto).
|
|
192
191
|
private readonly elementRef = inject(ElementRef);
|
|
193
192
|
|
|
@@ -296,16 +295,28 @@ export class KeyboardAvoidingView
|
|
|
296
295
|
nativeID: this.nativeID,
|
|
297
296
|
accessible: this.accessible,
|
|
298
297
|
...this.folded,
|
|
298
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
299
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
300
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
301
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
299
302
|
};
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
// Forward an engine event to the matching @Output(), narrowing the template's untyped $event
|
|
303
|
-
// first.
|
|
304
|
-
|
|
305
|
-
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
306
|
+
// first.
|
|
307
|
+
private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
306
308
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
307
309
|
}
|
|
308
310
|
|
|
311
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
312
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`). `.observed`-gated, unlike `layout` above which
|
|
313
|
+
// this component needs unconditionally for its own internal frame measurement.
|
|
314
|
+
private eventEmitterHandler(
|
|
315
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
316
|
+
): ((event: unknown) => void) | undefined {
|
|
317
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
309
320
|
// Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
|
|
310
321
|
// host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
|
|
311
322
|
get folded(): Partial<IAngularKeyboardAvoidingViewProps> {
|
|
@@ -109,21 +109,17 @@ export type IAngularModalInputs = Omit<
|
|
|
109
109
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
110
110
|
template: `
|
|
111
111
|
@if (shouldRender) {
|
|
112
|
-
<
|
|
112
|
+
<modal
|
|
113
113
|
[symbioteHostProps]="hostProps()"
|
|
114
114
|
(show)="show.emit()"
|
|
115
115
|
(dismiss)="dismiss.emit()"
|
|
116
116
|
(requestClose)="requestClose.emit()"
|
|
117
117
|
(orientationChange)="emit(orientationChange, $event)"
|
|
118
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
119
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
120
|
-
(magicTap)="emit(magicTap, $event)"
|
|
121
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
122
118
|
>
|
|
123
|
-
<
|
|
119
|
+
<view [style]="containerStyle" [symbioteHostProps]="containerProps">
|
|
124
120
|
<ng-content></ng-content>
|
|
125
|
-
</
|
|
126
|
-
</
|
|
121
|
+
</view>
|
|
122
|
+
</modal>
|
|
127
123
|
}
|
|
128
124
|
`,
|
|
129
125
|
})
|
|
@@ -190,8 +186,8 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
190
186
|
|
|
191
187
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
192
188
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
193
|
-
// onto (see anchorHostStyle's doc comment) — NOT the inner `
|
|
194
|
-
// that hosts the user children; the outer `
|
|
189
|
+
// onto (see anchorHostStyle's doc comment) — NOT the inner `view [style]="containerStyle"`
|
|
190
|
+
// that hosts the user children; the outer `modal` node (bound via `hostProps` below) is
|
|
195
191
|
// the real primitive the anchor sits in front of.
|
|
196
192
|
private readonly elementRef = inject(ElementRef);
|
|
197
193
|
|
|
@@ -265,7 +261,7 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
265
261
|
this.hostPropsRevision.update(revision => revision + 1);
|
|
266
262
|
}
|
|
267
263
|
|
|
268
|
-
// renderModal's own MODAL_HOST_STYLE (position:'absolute') is the outer
|
|
264
|
+
// renderModal's own MODAL_HOST_STYLE (position:'absolute') is the outer modal node's
|
|
269
265
|
// style; the anchor's class-derived style goes FIRST, that resolved style SECOND —
|
|
270
266
|
// flattenStyle's later-wins collapse keeps the modal's own style winning over its ambient class.
|
|
271
267
|
//
|
|
@@ -279,18 +275,43 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
279
275
|
return {
|
|
280
276
|
...descriptorProps,
|
|
281
277
|
style: [anchorHostStyle(this.elementRef), descriptorProps.style],
|
|
278
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
279
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
280
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
281
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
282
282
|
};
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
+
// Through the props bag rather than `[collapsable]="false"`: `view` matches `ViewHost`, a real
|
|
286
|
+
// component, so a binding it does not declare is NG8002 under `ngc` — and `CUSTOM_ELEMENTS_SCHEMA`
|
|
287
|
+
// does not rescue a binding on a MATCHED component. Invisible to `tsc` and to vitest; only a real
|
|
288
|
+
// AOT build says so. Frozen and shared, so the directive's own diff sees one unchanged object.
|
|
289
|
+
readonly containerProps: Readonly<Record<string, unknown>> = Object.freeze({
|
|
290
|
+
collapsable: false,
|
|
291
|
+
});
|
|
292
|
+
|
|
285
293
|
get containerStyle(): unknown {
|
|
286
294
|
const container = this.descriptor.children[0];
|
|
287
295
|
return typeof container === 'string' ? undefined : container.props.style;
|
|
288
296
|
}
|
|
289
297
|
|
|
298
|
+
// NOT private: `(orientationChange)="emit(...)"` in the template above calls it, and a template
|
|
299
|
+
// can only reach public members. `tsc` does not typecheck Angular templates, so `private` here
|
|
300
|
+
// compiles clean and fails only under `ngc --compilationMode partial`.
|
|
290
301
|
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
291
302
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
306
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`) — `show`/`dismiss`/`requestClose`/
|
|
307
|
+
// `orientationChange` are NOT (Modal's own MODAL_EVENTS), so those stay unconditional template
|
|
308
|
+
// bindings above. `.observed`-gated, mirroring Pressable's.
|
|
309
|
+
private eventEmitterHandler(
|
|
310
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
311
|
+
): ((event: unknown) => void) | undefined {
|
|
312
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
294
315
|
// Typed as the a11y intersection WITH the string index (the bag renderModal spreads into the
|
|
295
316
|
// host props), so resolveAccessibilityProps's result stays assignable to passthrough.
|
|
296
317
|
private accessibilityInputs(): IAccessibilityProps &
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The `<pressable>` tag's prop surface. The tag is matched by `PressableElement`
|
|
2
|
+
// (`../elements`) and carries the press machine as an engine behavior
|
|
3
|
+
// (`registerPressableBehavior`), so the wrapper that used to run it in Angular was deleted
|
|
4
|
+
// 2026-09-10.
|
|
5
|
+
//
|
|
6
|
+
// The type stays for a component forwarding a bag, for the four `Touchable*` prop types that share
|
|
7
|
+
// this base, and because `IAngularPressableProps` is public API. Mirrors React's IPressableProps
|
|
8
|
+
// minus children, which Angular takes via `<ng-content>`, and minus the render-prop `children`
|
|
9
|
+
// React allows.
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
IAccessibilityProps,
|
|
13
|
+
IAriaProps,
|
|
14
|
+
IPressableAndroidRippleConfig,
|
|
15
|
+
IPressHandler,
|
|
16
|
+
IPressState,
|
|
17
|
+
IRectOffset,
|
|
18
|
+
} from '@symbiote-native/components';
|
|
19
|
+
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
IPressState,
|
|
23
|
+
IPressableAndroidRippleConfig,
|
|
24
|
+
} from '@symbiote-native/components';
|
|
25
|
+
|
|
26
|
+
export interface IAngularPressableProps
|
|
27
|
+
extends IAccessibilityProps, IAriaProps {
|
|
28
|
+
onPress?: IPressHandler;
|
|
29
|
+
onPressIn?: IPressHandler;
|
|
30
|
+
onPressOut?: IPressHandler;
|
|
31
|
+
onPressMove?: IPressHandler;
|
|
32
|
+
onLongPress?: IPressHandler;
|
|
33
|
+
delayLongPress?: number;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
cancelable?: boolean;
|
|
36
|
+
hitSlop?: IRectOffset;
|
|
37
|
+
pressRetentionOffset?: IRectOffset;
|
|
38
|
+
unstable_pressDelay?: number;
|
|
39
|
+
android_ripple?: IPressableAndroidRippleConfig;
|
|
40
|
+
android_disableSound?: boolean;
|
|
41
|
+
onHoverIn?: IPressHandler;
|
|
42
|
+
onHoverOut?: IPressHandler;
|
|
43
|
+
delayHoverIn?: number;
|
|
44
|
+
delayHoverOut?: number;
|
|
45
|
+
testID?: string;
|
|
46
|
+
nativeID?: string;
|
|
47
|
+
hasTVPreferredFocus?: boolean;
|
|
48
|
+
nextFocusDown?: number;
|
|
49
|
+
nextFocusForward?: number;
|
|
50
|
+
nextFocusLeft?: number;
|
|
51
|
+
nextFocusRight?: number;
|
|
52
|
+
nextFocusUp?: number;
|
|
53
|
+
style?:
|
|
54
|
+
IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The half an Angular template writes as plain `[prop]` bindings: everything minus the callbacks a
|
|
58
|
+
// tag takes as `(event)` instead. The four `Touchable*` prop types are built over THIS rather than
|
|
59
|
+
// over the whole surface, for that reason.
|
|
60
|
+
export type IAngularPressableInputs = Omit<
|
|
61
|
+
IAngularPressableProps,
|
|
62
|
+
| 'onPress'
|
|
63
|
+
| 'onPressIn'
|
|
64
|
+
| 'onPressOut'
|
|
65
|
+
| 'onPressMove'
|
|
66
|
+
| 'onLongPress'
|
|
67
|
+
| 'onHoverIn'
|
|
68
|
+
| 'onHoverOut'
|
|
69
|
+
| 'onAccessibilityAction'
|
|
70
|
+
| 'onAccessibilityTap'
|
|
71
|
+
| 'onMagicTap'
|
|
72
|
+
| 'onAccessibilityEscape'
|
|
73
|
+
>;
|