@symbiote-native/angular 1.0.0 → 2.0.1

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.
Files changed (248) hide show
  1. package/README.md +13 -10
  2. package/babel-register-composed.cjs +41 -19
  3. package/bin/symbiote-angular-dev.cjs +16 -4
  4. package/build/angular/anchor-host-registry.js +1 -17
  5. package/build/angular/anchor-host-registry.js.map +1 -1
  6. package/build/angular/change-detection-flush.d.ts +31 -0
  7. package/build/angular/change-detection-flush.js +110 -0
  8. package/build/angular/change-detection-flush.js.map +1 -0
  9. package/build/angular/components/activity-indicator-props.d.ts +2 -0
  10. package/build/angular/components/activity-indicator-props.js +2 -0
  11. package/build/angular/components/activity-indicator-props.js.map +1 -0
  12. package/build/angular/components/button-props.d.ts +2 -0
  13. package/build/angular/components/button-props.js +2 -0
  14. package/build/angular/components/button-props.js.map +1 -0
  15. package/build/angular/components/flat-list/index.d.ts +2 -1
  16. package/build/angular/components/flat-list/index.js +22 -10
  17. package/build/angular/components/flat-list/index.js.map +1 -1
  18. package/build/angular/components/image-background-props.d.ts +6 -0
  19. package/build/angular/components/image-background-props.js +2 -0
  20. package/build/angular/components/image-background-props.js.map +1 -0
  21. package/build/angular/components/{image/shared.d.ts → image-shared.d.ts} +1 -1
  22. package/build/angular/components/{image/shared.js → image-shared.js} +7 -2
  23. package/build/angular/components/image-shared.js.map +1 -0
  24. package/build/angular/components/input-accessory-view-props.d.ts +7 -0
  25. package/build/angular/components/input-accessory-view-props.js +9 -0
  26. package/build/angular/components/input-accessory-view-props.js.map +1 -0
  27. package/build/angular/components/keyboard-avoiding-view/index.js +11 -17
  28. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  29. package/build/angular/components/modal/index.d.ts +1 -0
  30. package/build/angular/components/modal/index.js +19 -12
  31. package/build/angular/components/modal/index.js.map +1 -1
  32. package/build/angular/components/pressable-props.d.ts +32 -0
  33. package/build/angular/components/pressable-props.js +11 -0
  34. package/build/angular/components/pressable-props.js.map +1 -0
  35. package/build/angular/components/refresh-control-props.d.ts +16 -0
  36. package/build/angular/components/refresh-control-props.js +14 -0
  37. package/build/angular/components/refresh-control-props.js.map +1 -0
  38. package/build/angular/components/safe-area-view-props.d.ts +7 -0
  39. package/build/angular/components/safe-area-view-props.js +11 -0
  40. package/build/angular/components/safe-area-view-props.js.map +1 -0
  41. package/build/angular/components/scroll-view-props.d.ts +76 -0
  42. package/build/angular/components/scroll-view-props.js +18 -0
  43. package/build/angular/components/scroll-view-props.js.map +1 -0
  44. package/build/angular/components/text-input-props.d.ts +45 -0
  45. package/build/angular/components/text-input-props.js +18 -0
  46. package/build/angular/components/text-input-props.js.map +1 -0
  47. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +6 -0
  48. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js +2 -0
  49. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js.map +1 -0
  50. package/build/angular/components/touchable-props.d.ts +14 -0
  51. package/build/angular/components/touchable-props.js +13 -0
  52. package/build/angular/components/touchable-props.js.map +1 -0
  53. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -0
  54. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js +2 -0
  55. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js.map +1 -0
  56. package/build/angular/components/virtualized-list/index.d.ts +5 -1
  57. package/build/angular/components/virtualized-list/index.js +366 -280
  58. package/build/angular/components/virtualized-list/index.js.map +1 -1
  59. package/build/angular/components.d.ts +20 -26
  60. package/build/angular/components.js +12 -14
  61. package/build/angular/components.js.map +1 -1
  62. package/build/angular/create-portal/index.d.ts +2 -2
  63. package/build/angular/create-portal/index.js +5 -5
  64. package/build/angular/create-tunnel/index.js +1 -1
  65. package/build/angular/element-props.d.ts +53 -0
  66. package/build/angular/element-props.js +2 -0
  67. package/build/angular/element-props.js.map +1 -0
  68. package/build/angular/elements.d.ts +528 -0
  69. package/build/angular/elements.js +1444 -0
  70. package/build/angular/elements.js.map +1 -0
  71. package/build/angular/host-instance/index.js +1 -1
  72. package/build/angular/index.d.ts +4 -2
  73. package/build/angular/index.js +15 -1
  74. package/build/angular/index.js.map +1 -1
  75. package/build/angular/modules/animated/create-animated-component.d.ts +5 -6
  76. package/build/angular/modules/animated/create-animated-component.js +60 -73
  77. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  78. package/build/angular/modules/animated/index.js +1 -1
  79. package/build/angular/modules/animated/index.js.map +1 -1
  80. package/build/angular/modules/image/index.d.ts +3 -0
  81. package/build/angular/modules/image/index.js +16 -0
  82. package/build/angular/modules/image/index.js.map +1 -0
  83. package/build/angular/primitives/index.d.ts +18 -18
  84. package/build/angular/primitives/index.js +40 -36
  85. package/build/angular/primitives/index.js.map +1 -1
  86. package/build/angular/primitives/shared.d.ts +2 -3
  87. package/build/angular/primitives/shared.js +8 -53
  88. package/build/angular/primitives/shared.js.map +1 -1
  89. package/build/angular/register.d.ts +1 -1
  90. package/build/angular/register.js +12 -31
  91. package/build/angular/register.js.map +1 -1
  92. package/build/angular/render/index.js +3 -3
  93. package/build/angular/render/index.js.map +1 -1
  94. package/build/angular/renderer/index.js +113 -67
  95. package/build/angular/renderer/index.js.map +1 -1
  96. package/build/angular/renderer/value-change.d.ts +2 -0
  97. package/build/angular/renderer/value-change.js +9 -0
  98. package/build/angular/renderer/value-change.js.map +1 -0
  99. package/metro-config.cjs +54 -20
  100. package/metro-css-parser.cjs +2 -1
  101. package/package.json +5 -14
  102. package/src/anchor-host-registry.ts +1 -17
  103. package/src/change-detection-flush.ts +120 -0
  104. package/src/components/activity-indicator-props.ts +21 -0
  105. package/src/components/button-props.ts +26 -0
  106. package/src/components/flat-list/index.ts +12 -4
  107. package/src/components/image-background-props.ts +24 -0
  108. package/src/components/{image/shared.ts → image-shared.ts} +6 -1
  109. package/src/components/input-accessory-view-props.ts +21 -0
  110. package/src/components/keyboard-avoiding-view/index.ts +6 -9
  111. package/src/components/modal/index.ts +15 -7
  112. package/src/components/pressable-props.ts +73 -0
  113. package/src/components/refresh-control-props.ts +43 -0
  114. package/src/components/safe-area-view-props.ts +28 -0
  115. package/src/components/scroll-view-props.ts +109 -0
  116. package/src/components/text-input-props.ts +72 -0
  117. package/src/components/touchable-native-feedback/touchable-native-feedback-props.ts +23 -0
  118. package/src/components/touchable-props.ts +30 -0
  119. package/src/components/touchable-without-feedback/touchable-without-feedback-props.ts +21 -0
  120. package/src/components/virtualized-list/index.ts +239 -156
  121. package/src/components.ts +84 -35
  122. package/src/create-portal/index.ts +5 -5
  123. package/src/create-tunnel/index.ts +1 -1
  124. package/src/diagnostics.ts +1 -1
  125. package/src/element-props.ts +90 -0
  126. package/src/elements.ts +919 -0
  127. package/src/host-instance/index.ts +1 -1
  128. package/src/index.ts +52 -15
  129. package/src/modules/animated/create-animated-component.ts +50 -55
  130. package/src/modules/animated/index.ts +1 -1
  131. package/src/modules/image/index.ts +18 -0
  132. package/src/primitives/index.ts +22 -18
  133. package/src/primitives/shared.ts +11 -44
  134. package/src/register.ts +12 -40
  135. package/src/render/index.ts +3 -3
  136. package/src/renderer/index.ts +122 -73
  137. package/src/renderer/value-change.ts +8 -0
  138. package/babel-lower-host-primitives.cjs +0 -636
  139. package/build/angular/components/activity-indicator/index.android.d.ts +0 -13
  140. package/build/angular/components/activity-indicator/index.android.js +0 -29
  141. package/build/angular/components/activity-indicator/index.android.js.map +0 -1
  142. package/build/angular/components/activity-indicator/index.d.ts +0 -1
  143. package/build/angular/components/activity-indicator/index.ios.d.ts +0 -9
  144. package/build/angular/components/activity-indicator/index.ios.js +0 -26
  145. package/build/angular/components/activity-indicator/index.ios.js.map +0 -1
  146. package/build/angular/components/activity-indicator/index.js +0 -4
  147. package/build/angular/components/activity-indicator/index.js.map +0 -1
  148. package/build/angular/components/activity-indicator/shared.d.ts +0 -67
  149. package/build/angular/components/activity-indicator/shared.js +0 -260
  150. package/build/angular/components/activity-indicator/shared.js.map +0 -1
  151. package/build/angular/components/button.d.ts +0 -68
  152. package/build/angular/components/button.js +0 -360
  153. package/build/angular/components/button.js.map +0 -1
  154. package/build/angular/components/image/index.android.d.ts +0 -15
  155. package/build/angular/components/image/index.android.js +0 -83
  156. package/build/angular/components/image/index.android.js.map +0 -1
  157. package/build/angular/components/image/index.d.ts +0 -1
  158. package/build/angular/components/image/index.ios.d.ts +0 -15
  159. package/build/angular/components/image/index.ios.js +0 -83
  160. package/build/angular/components/image/index.ios.js.map +0 -1
  161. package/build/angular/components/image/index.js +0 -3
  162. package/build/angular/components/image/index.js.map +0 -1
  163. package/build/angular/components/image/shared.js.map +0 -1
  164. package/build/angular/components/image-background.d.ts +0 -85
  165. package/build/angular/components/image-background.js +0 -443
  166. package/build/angular/components/image-background.js.map +0 -1
  167. package/build/angular/components/input-accessory-view/index.d.ts +0 -66
  168. package/build/angular/components/input-accessory-view/index.js +0 -287
  169. package/build/angular/components/input-accessory-view/index.js.map +0 -1
  170. package/build/angular/components/pressable/index.d.ts +0 -134
  171. package/build/angular/components/pressable/index.js +0 -514
  172. package/build/angular/components/pressable/index.js.map +0 -1
  173. package/build/angular/components/refresh-control/index.d.ts +0 -91
  174. package/build/angular/components/refresh-control/index.js +0 -382
  175. package/build/angular/components/refresh-control/index.js.map +0 -1
  176. package/build/angular/components/safe-area-view/index.d.ts +0 -64
  177. package/build/angular/components/safe-area-view/index.js +0 -287
  178. package/build/angular/components/safe-area-view/index.js.map +0 -1
  179. package/build/angular/components/scroll-view/index.android.d.ts +0 -20
  180. package/build/angular/components/scroll-view/index.android.js +0 -262
  181. package/build/angular/components/scroll-view/index.android.js.map +0 -1
  182. package/build/angular/components/scroll-view/index.d.ts +0 -3
  183. package/build/angular/components/scroll-view/index.ios.d.ts +0 -8
  184. package/build/angular/components/scroll-view/index.ios.js +0 -92
  185. package/build/angular/components/scroll-view/index.ios.js.map +0 -1
  186. package/build/angular/components/scroll-view/index.js +0 -6
  187. package/build/angular/components/scroll-view/index.js.map +0 -1
  188. package/build/angular/components/scroll-view/projection.d.ts +0 -67
  189. package/build/angular/components/scroll-view/projection.js +0 -560
  190. package/build/angular/components/scroll-view/projection.js.map +0 -1
  191. package/build/angular/components/scroll-view/shared.d.ts +0 -241
  192. package/build/angular/components/scroll-view/shared.js +0 -848
  193. package/build/angular/components/scroll-view/shared.js.map +0 -1
  194. package/build/angular/components/scroll-view/sticky-header.d.ts +0 -40
  195. package/build/angular/components/scroll-view/sticky-header.js +0 -224
  196. package/build/angular/components/scroll-view/sticky-header.js.map +0 -1
  197. package/build/angular/components/switch/index.android.d.ts +0 -24
  198. package/build/angular/components/switch/index.android.js +0 -112
  199. package/build/angular/components/switch/index.android.js.map +0 -1
  200. package/build/angular/components/switch/index.d.ts +0 -1
  201. package/build/angular/components/switch/index.ios.d.ts +0 -23
  202. package/build/angular/components/switch/index.ios.js +0 -111
  203. package/build/angular/components/switch/index.ios.js.map +0 -1
  204. package/build/angular/components/switch/index.js +0 -4
  205. package/build/angular/components/switch/index.js.map +0 -1
  206. package/build/angular/components/switch/shared.d.ts +0 -49
  207. package/build/angular/components/switch/shared.js +0 -191
  208. package/build/angular/components/switch/shared.js.map +0 -1
  209. package/build/angular/components/text-input.d.ts +0 -167
  210. package/build/angular/components/text-input.js +0 -684
  211. package/build/angular/components/text-input.js.map +0 -1
  212. package/build/angular/components/touchable/index.d.ts +0 -283
  213. package/build/angular/components/touchable/index.js +0 -1453
  214. package/build/angular/components/touchable/index.js.map +0 -1
  215. package/build/angular/components/touchable-native-feedback/index.d.ts +0 -87
  216. package/build/angular/components/touchable-native-feedback/index.js +0 -409
  217. package/build/angular/components/touchable-native-feedback/index.js.map +0 -1
  218. package/build/angular/state-style.d.ts +0 -9
  219. package/build/angular/state-style.js +0 -39
  220. package/build/angular/state-style.js.map +0 -1
  221. package/metro-transformer.cjs +0 -69
  222. package/src/components/activity-indicator/index.android.ts +0 -23
  223. package/src/components/activity-indicator/index.ios.ts +0 -21
  224. package/src/components/activity-indicator/index.ts +0 -4
  225. package/src/components/activity-indicator/shared.ts +0 -225
  226. package/src/components/button.ts +0 -237
  227. package/src/components/image/index.android.ts +0 -95
  228. package/src/components/image/index.ios.ts +0 -95
  229. package/src/components/image/index.ts +0 -3
  230. package/src/components/image-background.ts +0 -308
  231. package/src/components/input-accessory-view/index.ts +0 -246
  232. package/src/components/pressable/index.ts +0 -522
  233. package/src/components/refresh-control/index.ts +0 -349
  234. package/src/components/safe-area-view/index.ts +0 -247
  235. package/src/components/scroll-view/index.android.ts +0 -205
  236. package/src/components/scroll-view/index.ios.ts +0 -81
  237. package/src/components/scroll-view/index.ts +0 -7
  238. package/src/components/scroll-view/projection.ts +0 -713
  239. package/src/components/scroll-view/shared.ts +0 -1093
  240. package/src/components/scroll-view/sticky-header.ts +0 -267
  241. package/src/components/switch/index.android.ts +0 -113
  242. package/src/components/switch/index.ios.ts +0 -112
  243. package/src/components/switch/index.ts +0 -4
  244. package/src/components/switch/shared.ts +0 -284
  245. package/src/components/text-input.ts +0 -669
  246. package/src/components/touchable/index.ts +0 -956
  247. package/src/components/touchable-native-feedback/index.ts +0 -327
  248. package/src/state-style.ts +0 -39
@@ -1,522 +0,0 @@
1
- import {
2
- CUSTOM_ELEMENTS_SCHEMA,
3
- ChangeDetectionStrategy,
4
- ChangeDetectorRef,
5
- Component,
6
- ElementRef,
7
- EventEmitter,
8
- computed,
9
- inject,
10
- Input,
11
- Output,
12
- signal,
13
- ViewChild,
14
- type DoCheck,
15
- type OnChanges,
16
- type OnDestroy,
17
- } from '@angular/core';
18
- import {
19
- createPressHandlers,
20
- createPressRuntime,
21
- disposePressRuntime,
22
- DEFAULT_DELAY_LONG_PRESS_MS,
23
- DEFAULT_MIN_PRESS_DURATION_MS,
24
- isTerminationAllowed,
25
- resolveAccessibilityProps,
26
- resolveDisabledAccessibilityState,
27
- rippleProps,
28
- shouldClaimResponder,
29
- shouldSuppressPress,
30
- type IAccessibilityProps,
31
- type IAccessibilityStateValue,
32
- type IAriaProps,
33
- type IPressableAndroidRippleConfig,
34
- type IPressHandler,
35
- type IPressHost,
36
- type IPressMachineConfig,
37
- type IPressRuntime,
38
- type IPressState,
39
- type IRectOffset,
40
- } from '@symbiote-native/components';
41
- import {
42
- dlog,
43
- isSymbioteNode,
44
- measure,
45
- type ISymbioteEvent,
46
- type ISymbioteNode,
47
- type IStyleProp,
48
- type IViewStyle,
49
- } from '@symbiote-native/engine';
50
- import {
51
- anchorHostStyle,
52
- SymbioteHostPropsDirective,
53
- SymbioteStyleInputDirective,
54
- ViewHost,
55
- } from '../../primitives';
56
- import {
57
- gateWanted,
58
- injectGateDemand,
59
- type IGatedAccessibilityEvent,
60
- } from '../../gate-demand';
61
-
62
- export type {
63
- IPressState,
64
- IPressableAndroidRippleConfig,
65
- } from '@symbiote-native/components';
66
-
67
- // The full logical Pressable surface, callback-shaped press/hover events included. Touchable* and
68
- // TouchableNativeFeedback still take their OWN onPress/onPressIn/... as @Input() callbacks (that is
69
- // a separate, not-yet-converted decision) and derive their public prop types from this - so it keeps
70
- // the callback fields even though the Pressable component below no longer implements them directly.
71
- export interface IAngularPressableProps
72
- extends IAccessibilityProps, IAriaProps {
73
- onPress?: IPressHandler;
74
- onPressIn?: IPressHandler;
75
- onPressOut?: IPressHandler;
76
- onPressMove?: IPressHandler;
77
- onLongPress?: IPressHandler;
78
- delayLongPress?: number;
79
- disabled?: boolean;
80
- cancelable?: boolean;
81
- hitSlop?: IRectOffset;
82
- pressRetentionOffset?: IRectOffset;
83
- unstable_pressDelay?: number;
84
- android_ripple?: IPressableAndroidRippleConfig;
85
- android_disableSound?: boolean;
86
- onHoverIn?: IPressHandler;
87
- onHoverOut?: IPressHandler;
88
- delayHoverIn?: number;
89
- delayHoverOut?: number;
90
- testID?: string;
91
- nativeID?: string;
92
- hasTVPreferredFocus?: boolean;
93
- nextFocusDown?: number;
94
- nextFocusForward?: number;
95
- nextFocusLeft?: number;
96
- nextFocusRight?: number;
97
- nextFocusUp?: number;
98
- style?:
99
- IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
100
- }
101
-
102
- // What the Pressable component itself takes as plain @Input()s: the full surface minus the press/
103
- // hover events and the four accessibility callbacks, all of which it exposes as real @Output()
104
- // EventEmitters instead (see the class below) - the one place in the family where the
105
- // callback-vs-EventEmitter tradeoff is actually fixed.
106
- export type IAngularPressableInputs = Omit<
107
- IAngularPressableProps,
108
- | 'onPress'
109
- | 'onPressIn'
110
- | 'onPressOut'
111
- | 'onPressMove'
112
- | 'onLongPress'
113
- | 'onHoverIn'
114
- | 'onHoverOut'
115
- | 'onAccessibilityAction'
116
- | 'onAccessibilityTap'
117
- | 'onMagicTap'
118
- | 'onAccessibilityEscape'
119
- >;
120
-
121
- function isStyleFn(
122
- value: unknown,
123
- ): value is (state: IPressState) => IStyleProp<IViewStyle> {
124
- return typeof value === 'function';
125
- }
126
-
127
- function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
128
- return typeof event === 'object' && event !== null && 'nativeEvent' in event
129
- ? (event as ISymbioteEvent)
130
- : undefined;
131
- }
132
-
133
- @Component({
134
- // Single-name selector, matching TextInput's own ('TextInput', text-input.ts) rather than
135
- // View/Text's dual one. The alternate `symbiote-pressable` spelling was vestigial — nothing in
136
- // this component's own template ever renders that tag (it renders `symbiote-view`, below), and
137
- // no app or example code invokes the composed component by that literal tag (verified by grep,
138
- // 2026-08-31). Its only effect was to force `symbiote-pressable` into
139
- // `ANCHOR_HOST_COMPONENTS` unconditionally, which collided with the tag's OTHER, load-bearing
140
- // meaning — the host-behavior registry key (`PRESSABLE_TAG`,
141
- // core/components/src/behaviors/pressable.ts) a lowered `<Pressable>` must carry to get the
142
- // press machine. That collision is exactly why `DIAGNOSTIC_LOWERED_PRESSABLE_TAG` existed
143
- // (renderer/index.ts) — dropping the vestigial spelling here removes the collision at its
144
- // source, so the escape hatch is retired rather than worked around.
145
- selector: 'Pressable',
146
- standalone: true,
147
- hostDirectives: [
148
- { directive: SymbioteStyleInputDirective, inputs: ['style'] },
149
- ],
150
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
151
- imports: [ViewHost, SymbioteHostPropsDirective],
152
- template: `
153
- <symbiote-view
154
- #host
155
- [symbioteHostProps]="hostProps()"
156
- (press)="handlePress($event)"
157
- (pressIn)="handlePressIn($event)"
158
- (pressOut)="handlePressOut($event)"
159
- (responderMove)="handleResponderMove($event)"
160
- (startShouldSetResponder)="claimResponder()"
161
- (responderTerminationRequest)="allowTermination()"
162
- >
163
- <ng-content></ng-content>
164
- </symbiote-view>
165
- `,
166
- changeDetection: ChangeDetectionStrategy.OnPush,
167
- })
168
- export class Pressable
169
- implements IAngularPressableInputs, OnChanges, OnDestroy, DoCheck
170
- {
171
- // The press/hover lifecycle as real Angular events: `(press)="onTap($event)"`, not
172
- // `[onPress]="onTap"`. createPressHandlers still wants plain IPressHandler callbacks, so
173
- // emitterHandler() below adapts each EventEmitter into one - only while something is actually
174
- // subscribed (`.observed`), so an unbound onLongPress still skips arming the timer, exactly as
175
- // the old @Input()-absent case did.
176
- @Output() readonly press = new EventEmitter<ISymbioteEvent>();
177
- @Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
178
- @Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
179
- @Output() readonly pressMove = new EventEmitter<ISymbioteEvent>();
180
- @Output() readonly longPress = new EventEmitter<ISymbioteEvent>();
181
- @Output() readonly hoverIn = new EventEmitter<ISymbioteEvent>();
182
- @Output() readonly hoverOut = new EventEmitter<ISymbioteEvent>();
183
- // The four accessibility callbacks as real Angular events, app-facing. UNLIKE press/hover above,
184
- // these are NOT bound in the template at all — `accessibilityAction`/`accessibilityTap`/
185
- // `magicTap`/`accessibilityEscape` are `onX`-gated Fabric events
186
- // (`.claude/rules/fabric-boolean-event-gates.md`): the engine only emits them when the committed
187
- // payload carries a FUNCTION at that key, so a `(accessibilityAction)="..."` template binding —
188
- // always present once written — would light the gate on every instance regardless of whether the
189
- // app ever subscribes. Routed through `hostProps()` instead (`accessibilityEmitterHandler`
190
- // below), the same `.observed`-conditional shape `emitterHandler` already uses for press/hover,
191
- // so the gate lights only when something is actually listening.
192
- @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
193
- @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
194
- @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
195
- @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
196
- @Input() delayLongPress?: number;
197
- @Input() disabled?: boolean;
198
- @Input() cancelable?: boolean;
199
- @Input() hitSlop?: IRectOffset;
200
- @Input() pressRetentionOffset?: IRectOffset;
201
- @Input() unstable_pressDelay?: number;
202
- /** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
203
- @Input() __minPressDuration = DEFAULT_MIN_PRESS_DURATION_MS;
204
- @Input() android_ripple?: IPressableAndroidRippleConfig;
205
- @Input() android_disableSound?: boolean;
206
- @Input() delayHoverIn?: number;
207
- @Input() delayHoverOut?: number;
208
- @Input() testID?: string;
209
- @Input() nativeID?: string;
210
- @Input() hasTVPreferredFocus?: boolean;
211
- @Input() nextFocusDown?: number;
212
- @Input() nextFocusForward?: number;
213
- @Input() nextFocusLeft?: number;
214
- @Input() nextFocusRight?: number;
215
- @Input() nextFocusUp?: number;
216
- @Input() style?: IAngularPressableProps['style'];
217
- @Input() accessible?: boolean;
218
- @Input() accessibilityLabel?: string;
219
- @Input() accessibilityHint?: string;
220
- @Input() accessibilityRole?: IAngularPressableProps['accessibilityRole'];
221
- @Input() accessibilityState?: IAccessibilityStateValue;
222
- @Input() accessibilityValue?: IAngularPressableProps['accessibilityValue'];
223
- @Input()
224
- accessibilityActions?: IAngularPressableProps['accessibilityActions'];
225
- @Input() accessibilityLabelledBy?: string | string[];
226
- @Input()
227
- importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
228
- @Input()
229
- accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
230
- @Input() screenReaderFocusable?: boolean;
231
- @Input() accessibilityViewIsModal?: boolean;
232
- @Input() accessibilityElementsHidden?: boolean;
233
- @Input() accessibilityIgnoresInvertColors?: boolean;
234
- @Input() accessibilityLanguage?: string;
235
- @Input() accessibilityRespondsToUserInteraction?: boolean;
236
- @Input() accessibilityShowsLargeContentViewer?: boolean;
237
- @Input() accessibilityLargeContentTitle?: string;
238
- @Input() ariaLabel?: string;
239
- @Input() ariaBusy?: boolean;
240
- @Input() ariaChecked?: boolean | 'mixed';
241
- @Input() ariaDisabled?: boolean;
242
- @Input() ariaExpanded?: boolean;
243
- @Input() ariaHidden?: boolean;
244
- @Input() ariaLabelledBy?: string;
245
- @Input() ariaLive?: IAriaProps['aria-live'];
246
- @Input() ariaSelected?: boolean;
247
- @Input() ariaModal?: boolean;
248
- @Input() ariaValueMax?: number;
249
- @Input() ariaValueMin?: number;
250
- @Input() ariaValueNow?: number;
251
- @Input() ariaValueText?: string;
252
- @Input() id?: string;
253
- @Input() role?: IAngularPressableProps['role'];
254
-
255
- @ViewChild('host', { read: ElementRef })
256
- private hostElement?: ElementRef<unknown>;
257
-
258
- // INTERNAL mutable state, driven by the press machine rather than by an @Input, so ngOnChanges
259
- // can never cover it - it carries its OWN signal, which is what lets the memoized bag below stay
260
- // correct across a press. Exposed as a plain boolean getter so a use site's `#ref.pressed` keeps
261
- // reading a boolean and the public surface is unchanged.
262
- private readonly pressedState = signal(false);
263
-
264
- get pressed(): boolean {
265
- return this.pressedState();
266
- }
267
-
268
- private readonly runtime: IPressRuntime = createPressRuntime();
269
- private readonly host: IPressHost = {
270
- setPressed: pressed => {
271
- this.pressedState.set(pressed);
272
- this.changeDetector.detectChanges();
273
- },
274
- getMeasureFn: () => {
275
- const node = this.hostNode;
276
- if (node === undefined) return undefined;
277
- return callback => measure(node, callback);
278
- },
279
- schedule: (callback, ms) => {
280
- const id = setTimeout(callback, ms);
281
- return () => clearTimeout(id);
282
- },
283
- now: Date.now,
284
- };
285
-
286
- private readonly changeDetector = inject(ChangeDetectorRef);
287
- // Null unless an adapter wrapper renders this Pressable in its own template. See
288
- // `gate-demand.ts`; an app's own `<Pressable>` — including one projected INTO a wrapper — gets
289
- // null and falls back to its own `.observed`.
290
- private readonly gateDemand = injectGateDemand();
291
- // This component's OWN host - the non-painting anchor `class="..."` at the use site resolves
292
- // onto (see anchorHostStyle's doc comment) - NOT `hostElement` above, which targets the real
293
- // inner `symbiote-view` one level down.
294
- private readonly elementRef = inject(ElementRef);
295
-
296
- // Bridges the non-reactive @Input fields above into the reactive graph, so the computed() bag
297
- // below can memoize a value derived from them. Signal inputs would make this unnecessary, but
298
- // `input()` is visible only to the AOT compiler and this package's unit suite runs on JIT - see
299
- // the `angular-adapter-change-detection` skill, §6. Mirrors ScrollView's identical bridge
300
- // (components/scroll-view/shared.ts), including its constraint: ONLY safe for a bag whose every
301
- // dependency is an @Input or carries its own signal, never for plain internal mutable state.
302
- private readonly inputsRevision = signal(0);
303
-
304
- // The anchor's class-derived style is the one hostProps dependency that is NOT an @Input:
305
- // `class="..."`/`[class.x]`/`[ngClass]` at the use site resolves through the renderer's
306
- // addClass/removeClass onto the anchor node (anchorHostStyle's doc comment), so it never appears
307
- // in SimpleChanges and ngOnChanges cannot cover it. The engine's commitClassStyle allocates a
308
- // fresh [classStyle, explicitStyle] array on exactly the passes where a class token actually
309
- // moved, so signal.set's own Object.is check makes an unchanged poll a no-op that dirties nothing.
310
- private readonly anchorStyle = signal<unknown>(undefined);
311
-
312
- // ngDoCheck is the only hook that observes the anchor AFTER the parent wrote it. Angular flushes
313
- // a node's pre-order hooks at the next `advance` PAST it (hooks.ts callHooks runs "until that
314
- // node index exclusive") or in refreshView's post-template flush - both strictly after the
315
- // parent's classProp on this element, and strictly before this component's own view refreshes.
316
- // Angular also nulls the active consumer around lifecycle hooks (render3/hooks.ts), so this
317
- // write registers no dependency on the caller's view and cannot throw NG0600.
318
- ngDoCheck(): void {
319
- this.anchorStyle.set(anchorHostStyle(this.elementRef));
320
- }
321
-
322
- ngOnDestroy(): void {
323
- disposePressRuntime(this.runtime);
324
- }
325
-
326
- ngOnChanges(): void {
327
- // Tells the computed bag below that an @Input moved - plain class fields are not reactive on
328
- // their own. Must stay in ngOnChanges: that is the single moment Angular has finished writing
329
- // every changed input for this pass.
330
- this.inputsRevision.update(revision => revision + 1);
331
- }
332
-
333
- get resolvedStyle(): IStyleProp<IViewStyle> | undefined {
334
- const state: IPressState = { pressed: this.pressed };
335
- return isStyleFn(this.style) ? this.style(state) : this.style;
336
- }
337
-
338
- get foldedAccessibility(): Partial<IAngularPressableProps> {
339
- return resolveAccessibilityProps({
340
- accessibilityLabel: this.accessibilityLabel,
341
- accessibilityHint: this.accessibilityHint,
342
- accessibilityRole: this.accessibilityRole,
343
- accessibilityState: resolveDisabledAccessibilityState(
344
- this.accessibilityState,
345
- this.disabled,
346
- ),
347
- accessibilityValue: this.accessibilityValue,
348
- accessibilityActions: this.accessibilityActions,
349
- accessibilityLabelledBy: this.accessibilityLabelledBy,
350
- importantForAccessibility: this.importantForAccessibility,
351
- accessibilityLiveRegion: this.accessibilityLiveRegion,
352
- screenReaderFocusable: this.screenReaderFocusable,
353
- accessibilityViewIsModal: this.accessibilityViewIsModal,
354
- accessibilityElementsHidden: this.accessibilityElementsHidden,
355
- accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
356
- accessibilityLanguage: this.accessibilityLanguage,
357
- accessibilityRespondsToUserInteraction:
358
- this.accessibilityRespondsToUserInteraction,
359
- accessibilityShowsLargeContentViewer:
360
- this.accessibilityShowsLargeContentViewer,
361
- accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
362
- 'aria-label': this.ariaLabel,
363
- 'aria-busy': this.ariaBusy,
364
- 'aria-checked': this.ariaChecked,
365
- 'aria-disabled': this.ariaDisabled,
366
- 'aria-expanded': this.ariaExpanded,
367
- 'aria-hidden': this.ariaHidden,
368
- 'aria-labelledby': this.ariaLabelledBy,
369
- 'aria-live': this.ariaLive,
370
- 'aria-selected': this.ariaSelected,
371
- 'aria-modal': this.ariaModal,
372
- 'aria-valuemax': this.ariaValueMax,
373
- 'aria-valuemin': this.ariaValueMin,
374
- 'aria-valuenow': this.ariaValueNow,
375
- 'aria-valuetext': this.ariaValueText,
376
- id: this.id,
377
- role: this.role,
378
- });
379
- }
380
-
381
- get resolvedRippleProps(): Record<string, unknown> | undefined {
382
- return this.android_ripple === undefined
383
- ? undefined
384
- : rippleProps(this.android_ripple);
385
- }
386
-
387
- // Flat bag for `[symbioteHostProps]` (Renderer2.setProperty per key) - see primitives/shared.ts.
388
- //
389
- // MEASURED 2026-08-16: as a getter this rebuilt the bag on every refresh of this view, and a
390
- // press dirties this very view (SymbioteHostPropsDirective wraps each flat-bag onX with
391
- // markForCheck, and setPressed calls detectChanges outright), so `[symbioteHostProps]`'s
392
- // reference check failed every time and re-pushed every key through Renderer2.setProperty ->
393
- // routeProp for an identical bag. Memoized, an unchanged bag costs one reference compare.
394
- readonly hostProps = computed<Record<string, unknown>>(() => {
395
- this.inputsRevision();
396
- return {
397
- testID: this.testID,
398
- nativeID: this.nativeID,
399
- hasTVPreferredFocus: this.hasTVPreferredFocus,
400
- nextFocusDown: this.nextFocusDown,
401
- nextFocusForward: this.nextFocusForward,
402
- nextFocusLeft: this.nextFocusLeft,
403
- nextFocusRight: this.nextFocusRight,
404
- nextFocusUp: this.nextFocusUp,
405
- style: [this.anchorStyle(), this.resolvedStyle],
406
- accessible: this.accessible,
407
- ...this.foldedAccessibility,
408
- onAccessibilityAction: this.accessibilityEmitterHandler(
409
- 'accessibilityAction',
410
- ),
411
- onAccessibilityTap: this.accessibilityEmitterHandler('accessibilityTap'),
412
- onMagicTap: this.accessibilityEmitterHandler('magicTap'),
413
- onAccessibilityEscape: this.accessibilityEmitterHandler(
414
- 'accessibilityEscape',
415
- ),
416
- android_disableSound: this.android_disableSound,
417
- nativeBackgroundAndroid:
418
- this.resolvedRippleProps?.nativeBackgroundAndroid,
419
- nativeForegroundAndroid:
420
- this.resolvedRippleProps?.nativeForegroundAndroid,
421
- };
422
- });
423
-
424
- handlePressIn(event: unknown): void {
425
- const symbioteEvent = asSymbioteEvent(event);
426
- if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
427
- return;
428
- this.handlers.handlePressIn(symbioteEvent);
429
- }
430
-
431
- handlePressOut(event: unknown): void {
432
- const symbioteEvent = asSymbioteEvent(event);
433
- if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
434
- return;
435
- this.handlers.handlePressOut(symbioteEvent);
436
- }
437
-
438
- handlePress(event: unknown): void {
439
- const symbioteEvent = asSymbioteEvent(event);
440
- if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
441
- return;
442
- this.handlers.handlePress(symbioteEvent);
443
- }
444
-
445
- handleResponderMove(event: unknown): void {
446
- const symbioteEvent = asSymbioteEvent(event);
447
- if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
448
- return;
449
- this.handlers.handleResponderMove(symbioteEvent);
450
- }
451
-
452
- claimResponder(): boolean {
453
- return shouldClaimResponder(this.disabled);
454
- }
455
-
456
- allowTermination(): boolean {
457
- return isTerminationAllowed(this.cancelable);
458
- }
459
-
460
- private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
461
- const symbioteEvent = asSymbioteEvent(event);
462
- if (symbioteEvent !== undefined) emitter.emit(symbioteEvent);
463
- }
464
-
465
- // Hover has no event on a touch host (no pointer-enter/leave); this only records that someone
466
- // is listening anyway, mirroring core's noteHoverNoop but off `.observed` instead of an @Input.
467
- private noteHoverNoop(): void {
468
- if (this.hoverIn.observed || this.hoverOut.observed) {
469
- dlog(
470
- 'Pressable hover is a no-op on this host (no pointer-enter/leave event)',
471
- );
472
- }
473
- }
474
-
475
- // Wraps an @Output() as an IPressHandler only while it has a subscriber, so an unbound
476
- // (longPress) still skips arming the timer, matching createPressHandlers' "undefined means
477
- // nobody cares" contract.
478
- private emitterHandler(
479
- emitter: EventEmitter<ISymbioteEvent>,
480
- ): IPressHandler | undefined {
481
- return emitter.observed ? event => emitter.emit(event) : undefined;
482
- }
483
-
484
- // The accessibility twin of emitterHandler, feeding hostProps()'s flat bag instead of
485
- // createPressHandlers' config — see the accessibilityAction @Output()'s own comment for why
486
- // these four specifically cannot be plain template `(x)="..."` bindings. `undefined` here means
487
- // `setEventListener` never sees a function, so the gated prop's flag never lights
488
- // (`fabric-boolean-event-gates.md`, "The flag follows the VALUE, not the key").
489
- private accessibilityEmitterHandler(
490
- name: IGatedAccessibilityEvent,
491
- ): ((event: unknown) => void) | undefined {
492
- const emitter = this[name];
493
- // A WRAPPER's binding is not a subscriber, and `.observed` cannot tell the two apart. When a
494
- // demand is supplied from above it decides instead — see `gate-demand.ts` for why the channel
495
- // is DI rather than an `@Input`.
496
- return gateWanted(this.gateDemand, name, emitter)
497
- ? event => this.emit(emitter, event)
498
- : undefined;
499
- }
500
-
501
- private get handlers() {
502
- this.noteHoverNoop();
503
- const config: IPressMachineConfig = {
504
- onPress: this.emitterHandler(this.press),
505
- onPressIn: this.emitterHandler(this.pressIn),
506
- onPressOut: this.emitterHandler(this.pressOut),
507
- onPressMove: this.emitterHandler(this.pressMove),
508
- onLongPress: this.emitterHandler(this.longPress),
509
- delayLongPress: this.delayLongPress ?? DEFAULT_DELAY_LONG_PRESS_MS,
510
- unstable_pressDelay: this.unstable_pressDelay ?? 0,
511
- minPressDuration: this.__minPressDuration,
512
- hitSlop: this.hitSlop,
513
- pressRetentionOffset: this.pressRetentionOffset,
514
- };
515
- return createPressHandlers(config, this.runtime, this.host);
516
- }
517
-
518
- private get hostNode(): ISymbioteNode | undefined {
519
- const native = this.hostElement?.nativeElement;
520
- return isSymbioteNode(native) ? native : undefined;
521
- }
522
- }