@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,349 +0,0 @@
1
- // RefreshControl, the Angular lifecycle half. On iOS this is the PullToRefreshView Fabric node that
2
- // lives INSIDE a ScrollView (a childless sibling before the content container); on Android it is
3
- // AndroidSwipeRefreshLayout and WRAPS the scroll view, receiving it through <ng-content>. There is
4
- // no JS-side platform renaming and no shared render fn — every prop forwards straight to the
5
- // native node, which reads what it understands and ignores the rest, so Android-only and iOS-only
6
- // prop families ride down harmlessly on both. This folds aria/role through the shared
7
- // resolveAccessibilityProps and maps the native props + a11y + onRefresh onto the
8
- // symbiote-refresh-control host, children via <ng-content>. One composed component covers both
9
- // platforms, so this stays a flat single file.
10
- //
11
- // `refreshing` is a controlled prop: the parent owns it and pushes it down each commit; native
12
- // reports the gesture via the direct `topRefresh` event, which the engine routes to the host's
13
- // `refresh` listener (Angular blocks [onX] property bindings; events flow through (event) only).
14
-
15
- import {
16
- CUSTOM_ELEMENTS_SCHEMA,
17
- ChangeDetectionStrategy,
18
- Component,
19
- computed,
20
- ElementRef,
21
- EventEmitter,
22
- inject,
23
- Input,
24
- Output,
25
- signal,
26
- ViewChild,
27
- type AfterViewInit,
28
- type DoCheck,
29
- type OnChanges,
30
- type OnInit,
31
- type SimpleChanges,
32
- } from '@angular/core';
33
- import {
34
- resolveAccessibilityProps,
35
- type IAccessibilityProps,
36
- type IAccessibilityStateValue,
37
- type IAriaProps,
38
- } from '@symbiote-native/components';
39
- import {
40
- dispatchViewCommand,
41
- dlog,
42
- isSymbioteEvent,
43
- isSymbioteNode,
44
- type IStyleProp,
45
- type ISymbioteEvent,
46
- type IViewStyle,
47
- } from '@symbiote-native/engine';
48
- import {
49
- anchorHostStyle,
50
- RefreshControlHost,
51
- SymbioteHostPropsDirective,
52
- SymbioteStyleInputDirective,
53
- } from '../../primitives';
54
-
55
- // Record<string, unknown> already tolerates `style` holding the [anchorHostStyle, this.style]
56
- // array anchorHostStyle's merge produces (see hostProps below) — no widening needed.
57
- type IHostProps = Record<string, unknown>;
58
-
59
- // Mirrors React's IRefreshControlProps minus children (Angular takes the Android-wrapped scroll view
60
- // via <ng-content>), declared per-adapter over the shared accessibility base since the framework-specific
61
- // children slot keeps it from being fully shared across adapters. Read the React reference for the
62
- // native prop names.
63
- export interface IAngularRefreshControlProps
64
- extends IAccessibilityProps, IAriaProps {
65
- refreshing: boolean;
66
- // RN's onRefresh is `() => void | Promise<void>`, the handler may be async; the promise is
67
- // fire-and-forget (native already starts refreshing on the gesture).
68
- onRefresh?: () => void | Promise<void>;
69
- tintColor?: string;
70
- title?: string;
71
- titleColor?: string;
72
- progressViewOffset?: number;
73
- // Android-only spinner styling (RN RefreshControlPropsAndroid): `colors` are the indicator's
74
- // animated stroke colors, `progressBackgroundColor` the disc behind it, `size` the diameter preset.
75
- // AndroidSwipeRefreshLayout reads them; PullToRefreshView on iOS ignores unknown props.
76
- colors?: readonly string[];
77
- progressBackgroundColor?: string;
78
- size?: 'default' | 'large';
79
- // Android-only native prop forwarded to AndroidSwipeRefreshLayout; iOS native never reads it.
80
- enabled?: boolean;
81
- // The Android scroll-view wrap injects the layout half of the style onto this host; iOS leaves it
82
- // unset (the RefreshControl is a childless sibling). Harmless to forward on both.
83
- style?: IStyleProp<IViewStyle>;
84
- }
85
-
86
- // What the RefreshControl component itself takes as plain @Input()s: the full surface minus the
87
- // refresh/accessibility events, which it exposes as real @Output() EventEmitters instead (mirrors
88
- // Pressable's IAngularPressableInputs in pressable/index.ts).
89
- export type IAngularRefreshControlInputs = Omit<
90
- IAngularRefreshControlProps,
91
- | 'onRefresh'
92
- | 'onAccessibilityAction'
93
- | 'onAccessibilityTap'
94
- | 'onMagicTap'
95
- | 'onAccessibilityEscape'
96
- >;
97
-
98
- @Component({
99
- selector: 'RefreshControl',
100
- standalone: true,
101
- hostDirectives: [
102
- { directive: SymbioteStyleInputDirective, inputs: ['style'] },
103
- ],
104
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
105
- imports: [RefreshControlHost, SymbioteHostPropsDirective],
106
- changeDetection: ChangeDetectionStrategy.OnPush,
107
- template: `
108
- <symbiote-refresh-control
109
- #host
110
- [symbioteHostProps]="hostProps()"
111
- (refresh)="handleRefresh()"
112
- >
113
- <ng-content></ng-content>
114
- </symbiote-refresh-control>
115
- `,
116
- })
117
- export class RefreshControl
118
- implements
119
- IAngularRefreshControlInputs,
120
- OnInit,
121
- OnChanges,
122
- DoCheck,
123
- AfterViewInit
124
- {
125
- // Controlled prop the parent owns; required to match the React reference surface.
126
- @Input({ required: true }) refreshing!: boolean;
127
- // RN's onRefresh is `() => void | Promise<void>`, so the callback shape allows an async handler;
128
- // the @Output() itself only signals the gesture (fire-and-forget, native already starts refreshing).
129
- @Output() readonly refresh = new EventEmitter<void>();
130
- @Input() tintColor?: string;
131
- @Input() title?: string;
132
- @Input() titleColor?: string;
133
- @Input() progressViewOffset?: number;
134
- @Input() colors?: readonly string[];
135
- @Input() progressBackgroundColor?: string;
136
- @Input() size?: 'default' | 'large';
137
- @Input() enabled?: boolean;
138
- @Input() style?: IStyleProp<IViewStyle>;
139
- @Input() testID?: string;
140
- @Input() nativeID?: string;
141
- @Input() accessible?: boolean;
142
- @Input() accessibilityLabel?: string;
143
- @Input() accessibilityHint?: string;
144
- @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
145
- @Input() accessibilityState?: IAccessibilityStateValue;
146
- @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
147
- @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
148
- @Input() accessibilityLabelledBy?: string | string[];
149
- @Input()
150
- importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
151
- @Input()
152
- accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
153
- @Input() screenReaderFocusable?: boolean;
154
- @Input() accessibilityViewIsModal?: boolean;
155
- @Input() accessibilityElementsHidden?: boolean;
156
- @Input() accessibilityIgnoresInvertColors?: boolean;
157
- @Input() accessibilityLanguage?: string;
158
- @Input() accessibilityRespondsToUserInteraction?: boolean;
159
- @Input() accessibilityShowsLargeContentViewer?: boolean;
160
- @Input() accessibilityLargeContentTitle?: string;
161
- @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
162
- @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
163
- @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
164
- @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
165
- @Input() role?: IAriaProps['role'];
166
- @Input('aria-label') ariaLabel?: string;
167
- @Input('aria-labelledby') ariaLabelledBy?: string;
168
- @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
169
- @Input('aria-hidden') ariaHidden?: boolean;
170
- @Input('aria-busy') ariaBusy?: boolean;
171
- @Input('aria-checked') ariaChecked?: boolean | 'mixed';
172
- @Input('aria-disabled') ariaDisabled?: boolean;
173
- @Input('aria-expanded') ariaExpanded?: boolean;
174
- @Input('aria-selected') ariaSelected?: boolean;
175
- @Input('aria-modal') ariaModal?: boolean;
176
- @Input('aria-valuemax') ariaValueMax?: number;
177
- @Input('aria-valuemin') ariaValueMin?: number;
178
- @Input('aria-valuenow') ariaValueNow?: number;
179
- @Input('aria-valuetext') ariaValueText?: string;
180
-
181
- // The inner symbiote-refresh-control primitive — NOT this component's own anchor host. Used for
182
- // the imperative dispatchViewCommand calls below.
183
- @ViewChild('host') private host?: RefreshControlHost;
184
-
185
- // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
186
- // onto (see anchorHostStyle's doc comment) — distinct from `host` above, which targets the real
187
- // inner `symbiote-refresh-control` primitive one level down.
188
- private readonly elementRef = inject(ElementRef);
189
-
190
- private lastNativeRefreshing = false;
191
- private refreshNativeNode: unknown;
192
-
193
- // Bridges the non-reactive @Input fields `hostProps` reads into the reactive graph, so it can
194
- // memoize. Plain fields read inside a computed() are UNTRACKED - something must signal "a
195
- // dependency changed" or the bag goes stale. Signal inputs would do this natively, but `input()`
196
- // is visible only to the AOT compiler and this package's unit suite runs on JIT (see the
197
- // `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
198
- // Safe here because `refreshing` is a CONTROLLED @Input - the parent owns it, nothing in this
199
- // class assigns it. The internal `lastNativeRefreshing` / `refreshNativeNode` fields drive the
200
- // native handshake only; neither is read by the bag, so neither may bump this.
201
- private readonly hostPropsRevision = signal(0);
202
- // What the anchor's class-derived style was when the bag was last built (identity, not value).
203
- private lastAnchorStyle: unknown;
204
-
205
- ngOnInit(): void {
206
- dlog('RefreshControl -> PullToRefreshView');
207
- dlog(`RefreshControl refreshing=${String(this.refreshing)}`);
208
- if (this.enabled !== undefined)
209
- dlog(`RefreshControl enabled=${String(this.enabled)} (Android-only)`);
210
- if (this.refresh.observed) dlog('RefreshControl refresh listener wired');
211
- }
212
-
213
- ngAfterViewInit(): void {
214
- this.lastNativeRefreshing = this.refreshing;
215
- }
216
-
217
- ngOnChanges(changes: SimpleChanges): void {
218
- // Before the `refreshing`-only early-return below: this is the single moment Angular has
219
- // finished writing every changed @Input, and `hostProps` depends on the whole input surface.
220
- this.hostPropsRevision.update(revision => revision + 1);
221
- const refreshing = changes.refreshing;
222
- if (refreshing === undefined) return;
223
- if (refreshing.firstChange) {
224
- this.lastNativeRefreshing = this.refreshing;
225
- return;
226
- }
227
- this.syncNativeRefreshing(
228
- this.refreshNativeNode ?? this.host?.nativeElement,
229
- );
230
- }
231
-
232
- // Native starts the spinner before JS runs. Mirror RN's RefreshControl controlled-component
233
- // handshake: remember that native is refreshing, call the user's callback, then after Angular has
234
- // had a chance to propagate `[refreshing]`, force native back to the JS value if it stayed false.
235
- handleRefresh(nativeNode: unknown = this.host?.nativeElement): void {
236
- this.refreshNativeNode = nativeNode;
237
- this.lastNativeRefreshing = true;
238
- this.refresh.emit();
239
- queueMicrotask(() => this.syncNativeRefreshing(nativeNode));
240
- }
241
-
242
- private syncNativeRefreshing(nativeNode: unknown): void {
243
- if (this.refreshing === this.lastNativeRefreshing) return;
244
- if (!isSymbioteNode(nativeNode)) return;
245
- dispatchViewCommand(nativeNode, 'setNativeRefreshing', [this.refreshing]);
246
- this.lastNativeRefreshing = this.refreshing;
247
- }
248
-
249
- // Forward an engine event to the matching @Output(), narrowing the template's untyped $event
250
- // first. The accessibility* events arrive on the engine's structural event channel.
251
- private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
252
- if (isSymbioteEvent(event)) emitter.emit(event);
253
- }
254
-
255
- // The four accessibility events are boolean-GATED Fabric events
256
- // (`.claude/rules/fabric-boolean-event-gates.md`): a template binding here lit the gate on every
257
- // instance whether or not an app ever subscribed. `.observed`-gated, mirroring Pressable's.
258
- private eventEmitterHandler(
259
- emitter: EventEmitter<ISymbioteEvent>,
260
- ): ((event: unknown) => void) | undefined {
261
- return emitter.observed ? event => this.emit(emitter, event) : undefined;
262
- }
263
-
264
- // The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
265
- // resolves through the renderer's addClass onto this component's anchor host (see
266
- // anchorHostStyle's doc comment), so it never shows up in SimpleChanges and ngOnChanges alone
267
- // would leave a later class toggle stranded. ngDoCheck runs at exactly the cadence the old
268
- // getter was re-read, and bumps only on a real identity change.
269
- ngDoCheck(): void {
270
- const anchorStyle = anchorHostStyle(this.elementRef);
271
- if (anchorStyle === this.lastAnchorStyle) return;
272
- this.lastAnchorStyle = anchorStyle;
273
- this.hostPropsRevision.update(revision => revision + 1);
274
- }
275
-
276
- // The full native + a11y prop bag applied onto the host in one shot via
277
- // [symbioteHostProps], instead of enumerating each key as its own template binding. The
278
- // anchor's class-derived style goes FIRST, this component's own explicit `style` @Input SECOND
279
- // — flattenStyle's later-wins collapse keeps an explicit [style] winning over its ambient class.
280
- //
281
- // A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
282
- // and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
283
- // re-pushes every key through renderer.setProperty -> the engine's prop routing. A pull gesture
284
- // dirties this view through its own `(refresh)` binding, which is exactly when that waste hit.
285
- readonly hostProps = computed<IHostProps>(() => {
286
- this.hostPropsRevision();
287
- return {
288
- refreshing: this.refreshing,
289
- tintColor: this.tintColor,
290
- title: this.title,
291
- titleColor: this.titleColor,
292
- progressViewOffset: this.progressViewOffset,
293
- colors: this.colors,
294
- progressBackgroundColor: this.progressBackgroundColor,
295
- size: this.size,
296
- enabled: this.enabled,
297
- style: [anchorHostStyle(this.elementRef), this.style],
298
- testID: this.testID,
299
- nativeID: this.nativeID,
300
- accessible: this.accessible,
301
- ...this.folded,
302
- onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
303
- onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
304
- onMagicTap: this.eventEmitterHandler(this.magicTap),
305
- onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
306
- };
307
- });
308
-
309
- // Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
310
- // host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
311
- get folded(): Partial<IAngularRefreshControlProps> {
312
- return resolveAccessibilityProps({
313
- accessibilityLabel: this.accessibilityLabel,
314
- accessibilityHint: this.accessibilityHint,
315
- accessibilityRole: this.accessibilityRole,
316
- accessibilityState: this.accessibilityState,
317
- accessibilityValue: this.accessibilityValue,
318
- accessibilityActions: this.accessibilityActions,
319
- accessibilityLabelledBy: this.accessibilityLabelledBy,
320
- importantForAccessibility: this.importantForAccessibility,
321
- accessibilityLiveRegion: this.accessibilityLiveRegion,
322
- screenReaderFocusable: this.screenReaderFocusable,
323
- accessibilityViewIsModal: this.accessibilityViewIsModal,
324
- accessibilityElementsHidden: this.accessibilityElementsHidden,
325
- accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
326
- accessibilityLanguage: this.accessibilityLanguage,
327
- accessibilityRespondsToUserInteraction:
328
- this.accessibilityRespondsToUserInteraction,
329
- accessibilityShowsLargeContentViewer:
330
- this.accessibilityShowsLargeContentViewer,
331
- accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
332
- role: this.role,
333
- 'aria-label': this.ariaLabel,
334
- 'aria-labelledby': this.ariaLabelledBy,
335
- 'aria-live': this.ariaLive,
336
- 'aria-hidden': this.ariaHidden,
337
- 'aria-busy': this.ariaBusy,
338
- 'aria-checked': this.ariaChecked,
339
- 'aria-disabled': this.ariaDisabled,
340
- 'aria-expanded': this.ariaExpanded,
341
- 'aria-selected': this.ariaSelected,
342
- 'aria-modal': this.ariaModal,
343
- 'aria-valuemax': this.ariaValueMax,
344
- 'aria-valuemin': this.ariaValueMin,
345
- 'aria-valuenow': this.ariaValueNow,
346
- 'aria-valuetext': this.ariaValueText,
347
- });
348
- }
349
- }
@@ -1,247 +0,0 @@
1
- // SafeAreaView, the Angular lifecycle half. A plain view whose native side insets its
2
- // children to the safe area (notch, rounded corners, system bars). There is no JS-side
3
- // translation — RN renders the native RCTSafeAreaView and lets the host do the inset math —
4
- // so this folds aria/role through the shared resolveAccessibilityProps and maps style +
5
- // a11y + onLayout straight onto the symbiote-safe-area-view host, children via <ng-content>.
6
- // The Angular twin of the React/Vue SafeAreaView. No platform branch (one Fabric name both
7
- // platforms), so this stays a flat single file.
8
-
9
- import {
10
- CUSTOM_ELEMENTS_SCHEMA,
11
- ChangeDetectionStrategy,
12
- Component,
13
- computed,
14
- ElementRef,
15
- EventEmitter,
16
- inject,
17
- Input,
18
- Output,
19
- signal,
20
- type DoCheck,
21
- type OnChanges,
22
- } from '@angular/core';
23
- import {
24
- resolveAccessibilityProps,
25
- type IAccessibilityProps,
26
- type IAccessibilityStateValue,
27
- type IAriaProps,
28
- } from '@symbiote-native/components';
29
- import {
30
- isSymbioteEvent,
31
- type IStyleProp,
32
- type ISymbioteEvent,
33
- type IViewStyle,
34
- } from '@symbiote-native/engine';
35
- import {
36
- anchorHostStyle,
37
- SafeAreaViewHost,
38
- SymbioteHostPropsDirective,
39
- SymbioteStyleInputDirective,
40
- } from '../../primitives';
41
-
42
- // Mirrors React's ISafeAreaViewProps minus children (Angular takes children via <ng-content>),
43
- // declared per-adapter over the shared accessibility base since the framework-specific children
44
- // slot keeps it from being fully shared across adapters.
45
- export interface IAngularSafeAreaViewProps
46
- extends IAccessibilityProps, IAriaProps {
47
- style?: IStyleProp<IViewStyle>;
48
- // `id` — accepted here and folded to `nativeID` by the shared plan, matching upstream, whose
49
- // SafeAreaView takes the full ViewProps surface. Added 2026-09-01 with `ID_ALIAS` on the spec
50
- // entry, deliberately as ONE change: the alias without this prop would make lowering fold a key
51
- // no spelling of the component accepts, and this prop without the alias would send a raw `id` to
52
- // Fabric, which declares no such key on any of these views.
53
- id?: string;
54
- onLayout?: (event: ISymbioteEvent) => void;
55
- }
56
-
57
- // What the component itself takes as plain @Input()s: the full surface minus onLayout and the
58
- // accessibility callbacks, which it exposes as real @Output() EventEmitters instead (see the
59
- // class below), mirroring Pressable's IAngularPressableInputs split.
60
- export type IAngularSafeAreaViewInputs = Omit<
61
- IAngularSafeAreaViewProps,
62
- | 'onLayout'
63
- | 'onAccessibilityAction'
64
- | 'onAccessibilityTap'
65
- | 'onMagicTap'
66
- | 'onAccessibilityEscape'
67
- >;
68
-
69
- @Component({
70
- selector: 'SafeAreaView',
71
- standalone: true,
72
- hostDirectives: [
73
- { directive: SymbioteStyleInputDirective, inputs: ['style'] },
74
- ],
75
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
76
- imports: [SafeAreaViewHost, SymbioteHostPropsDirective],
77
- changeDetection: ChangeDetectionStrategy.OnPush,
78
- template: `
79
- <symbiote-safe-area-view [symbioteHostProps]="hostProps()">
80
- <ng-content></ng-content>
81
- </symbiote-safe-area-view>
82
- `,
83
- })
84
- export class SafeAreaView
85
- implements IAngularSafeAreaViewInputs, OnChanges, DoCheck
86
- {
87
- @Input() style?: IStyleProp<IViewStyle>;
88
- // Real @Output()s, not `[onX]="…"` callbacks. Safe to name `layout` the same as the native
89
- // `layout` event fired inside this component's own template — the engine's bubble() treats
90
- // ANCHOR_HOST_COMPONENTS as transparent to listener lookup, so there is no double-fire.
91
- // `layout` plus the four accessibility events are all boolean-GATED Fabric events
92
- // (`.claude/rules/fabric-boolean-event-gates.md`): native fires them only when the committed
93
- // payload carries a FUNCTION at that key, so a template binding here lit the gate on every
94
- // instance whether or not an app ever subscribed. Moved into `hostProps()`'s flat bag below,
95
- // `.observed`-gated — the shape `Pressable`'s `accessibilityEmitterHandler` already uses.
96
- @Output() readonly layout = new EventEmitter<ISymbioteEvent>();
97
- @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
98
- @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
99
- @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
100
- @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
101
- @Input() testID?: string;
102
- @Input() nativeID?: string;
103
- @Input() accessible?: boolean;
104
- @Input() accessibilityLabel?: string;
105
- @Input() accessibilityHint?: string;
106
- @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
107
- @Input() accessibilityState?: IAccessibilityStateValue;
108
- @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
109
- @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
110
- @Input() accessibilityLabelledBy?: string | string[];
111
- @Input()
112
- importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
113
- @Input()
114
- accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
115
- @Input() screenReaderFocusable?: boolean;
116
- @Input() accessibilityViewIsModal?: boolean;
117
- @Input() accessibilityElementsHidden?: boolean;
118
- @Input() accessibilityIgnoresInvertColors?: boolean;
119
- @Input() accessibilityLanguage?: string;
120
- @Input() accessibilityRespondsToUserInteraction?: boolean;
121
- @Input() accessibilityShowsLargeContentViewer?: boolean;
122
- @Input() accessibilityLargeContentTitle?: string;
123
- @Input() role?: IAriaProps['role'];
124
- @Input('aria-label') ariaLabel?: string;
125
- @Input('aria-labelledby') ariaLabelledBy?: string;
126
- @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
127
- @Input('aria-hidden') ariaHidden?: boolean;
128
- @Input('aria-busy') ariaBusy?: boolean;
129
- @Input('aria-checked') ariaChecked?: boolean | 'mixed';
130
- @Input('aria-disabled') ariaDisabled?: boolean;
131
- @Input('aria-expanded') ariaExpanded?: boolean;
132
- @Input('aria-selected') ariaSelected?: boolean;
133
- @Input('aria-modal') ariaModal?: boolean;
134
- @Input('aria-valuemax') ariaValueMax?: number;
135
- @Input('aria-valuemin') ariaValueMin?: number;
136
- @Input('aria-valuenow') ariaValueNow?: number;
137
- @Input('aria-valuetext') ariaValueText?: string;
138
-
139
- // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
140
- // onto (see anchorHostStyle's doc comment). SafeAreaView has no inner ViewChild, so this is
141
- // the only ElementRef in the class.
142
- private readonly elementRef = inject(ElementRef);
143
-
144
- // Forward an engine event to the matching @Output(), narrowing the untyped event first.
145
- private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
146
- if (isSymbioteEvent(event)) emitter.emit(event);
147
- }
148
-
149
- // `.observed`-gated, mirroring Pressable's `accessibilityEmitterHandler`: `undefined` here
150
- // means `setEventListener` never sees a function, so the gated prop's flag never lights
151
- // (`fabric-boolean-event-gates.md`, "The flag follows the VALUE, not the key").
152
- private eventEmitterHandler(
153
- emitter: EventEmitter<ISymbioteEvent>,
154
- ): ((event: unknown) => void) | undefined {
155
- return emitter.observed ? event => this.emit(emitter, event) : undefined;
156
- }
157
-
158
- // Bridges the non-reactive fields `hostProps` reads into the reactive graph, so it can memoize.
159
- // Plain fields read inside a computed() are UNTRACKED - something must signal "a dependency
160
- // changed" or the bag goes stale. Signal inputs would do this natively, but `input()` is visible
161
- // only to the AOT compiler and this package's unit suite runs on JIT (see the
162
- // `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
163
- // TWO bump sites, both required - see ngOnChanges and ngDoCheck below.
164
- private readonly hostPropsRevision = signal(0);
165
- // What the anchor's class-derived style was when the bag was last built (identity, not value).
166
- private lastAnchorStyle: unknown;
167
-
168
- ngOnChanges(): void {
169
- // The single moment Angular has finished writing every changed @Input for this pass.
170
- this.hostPropsRevision.update(revision => revision + 1);
171
- }
172
-
173
- // The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
174
- // resolves through the renderer's addClass onto this component's anchor host (see
175
- // anchorHostStyle's doc comment), so it never shows up in SimpleChanges - ngOnChanges alone
176
- // would strand a class toggled after mount (regression-covered by safe-area-view.test.ts's
177
- // class-toggle case, which fails the moment this hook stops bumping). ngDoCheck runs at exactly
178
- // the cadence the old getter was re-read, and bumps only on a real identity change, so an
179
- // unchanged anchor still costs nothing.
180
- ngDoCheck(): void {
181
- const anchorStyle = anchorHostStyle(this.elementRef);
182
- if (anchorStyle === this.lastAnchorStyle) return;
183
- this.lastAnchorStyle = anchorStyle;
184
- this.hostPropsRevision.update(revision => revision + 1);
185
- }
186
-
187
- // Folds style/testID/nativeID/accessible alongside the accessibility*/aria-*/role surface into
188
- // one bag for [symbioteHostProps] — resolveAccessibilityProps takes the whole props object (not
189
- // just the accessibility slice) and returns it with aria-*/role folded into accessibility*, so
190
- // the host node never sees an aria-* key (native ignores them), same transform React's
191
- // SafeAreaView runs over its whole `rawProps`. The anchor's class-derived style goes FIRST, this
192
- // component's own explicit `style` @Input SECOND — flattenStyle's later-wins collapse keeps an
193
- // explicit [style] winning over its ambient class.
194
- //
195
- // A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
196
- // and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
197
- // re-pushes every key through renderer.setProperty -> the engine's prop routing. Memoized, an
198
- // a11y/layout event that only dirties this view costs nothing.
199
- readonly hostProps = computed<Record<string, unknown>>(() => {
200
- this.hostPropsRevision();
201
- return resolveAccessibilityProps({
202
- style: [anchorHostStyle(this.elementRef), this.style],
203
- testID: this.testID,
204
- nativeID: this.nativeID,
205
- accessible: this.accessible,
206
- accessibilityLabel: this.accessibilityLabel,
207
- accessibilityHint: this.accessibilityHint,
208
- accessibilityRole: this.accessibilityRole,
209
- accessibilityState: this.accessibilityState,
210
- accessibilityValue: this.accessibilityValue,
211
- accessibilityActions: this.accessibilityActions,
212
- accessibilityLabelledBy: this.accessibilityLabelledBy,
213
- importantForAccessibility: this.importantForAccessibility,
214
- accessibilityLiveRegion: this.accessibilityLiveRegion,
215
- screenReaderFocusable: this.screenReaderFocusable,
216
- accessibilityViewIsModal: this.accessibilityViewIsModal,
217
- accessibilityElementsHidden: this.accessibilityElementsHidden,
218
- accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
219
- accessibilityLanguage: this.accessibilityLanguage,
220
- accessibilityRespondsToUserInteraction:
221
- this.accessibilityRespondsToUserInteraction,
222
- accessibilityShowsLargeContentViewer:
223
- this.accessibilityShowsLargeContentViewer,
224
- accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
225
- role: this.role,
226
- 'aria-label': this.ariaLabel,
227
- 'aria-labelledby': this.ariaLabelledBy,
228
- 'aria-live': this.ariaLive,
229
- 'aria-hidden': this.ariaHidden,
230
- 'aria-busy': this.ariaBusy,
231
- 'aria-checked': this.ariaChecked,
232
- 'aria-disabled': this.ariaDisabled,
233
- 'aria-expanded': this.ariaExpanded,
234
- 'aria-selected': this.ariaSelected,
235
- 'aria-modal': this.ariaModal,
236
- 'aria-valuemax': this.ariaValueMax,
237
- 'aria-valuemin': this.ariaValueMin,
238
- 'aria-valuenow': this.ariaValueNow,
239
- 'aria-valuetext': this.ariaValueText,
240
- onLayout: this.eventEmitterHandler(this.layout),
241
- onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
242
- onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
243
- onMagicTap: this.eventEmitterHandler(this.magicTap),
244
- onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
245
- });
246
- });
247
- }