@symbiote-native/angular 0.0.0-canary-20260724081123

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 (276) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/babel-linker.cjs +7 -0
  4. package/babel-register-composed.cjs +127 -0
  5. package/bin/symbiote-angular-dev.cjs +93 -0
  6. package/build/angular/anchor-host-registry.d.ts +2 -0
  7. package/build/angular/anchor-host-registry.js +84 -0
  8. package/build/angular/anchor-host-registry.js.map +1 -0
  9. package/build/angular/bootstrap.d.ts +7 -0
  10. package/build/angular/bootstrap.js +15 -0
  11. package/build/angular/bootstrap.js.map +1 -0
  12. package/build/angular/components/activity-indicator/index.android.d.ts +12 -0
  13. package/build/angular/components/activity-indicator/index.android.js +21 -0
  14. package/build/angular/components/activity-indicator/index.android.js.map +1 -0
  15. package/build/angular/components/activity-indicator/index.d.ts +1 -0
  16. package/build/angular/components/activity-indicator/index.ios.d.ts +8 -0
  17. package/build/angular/components/activity-indicator/index.ios.js +21 -0
  18. package/build/angular/components/activity-indicator/index.ios.js.map +1 -0
  19. package/build/angular/components/activity-indicator/index.js +4 -0
  20. package/build/angular/components/activity-indicator/index.js.map +1 -0
  21. package/build/angular/components/activity-indicator/shared.d.ts +67 -0
  22. package/build/angular/components/activity-indicator/shared.js +260 -0
  23. package/build/angular/components/activity-indicator/shared.js.map +1 -0
  24. package/build/angular/components/button.d.ts +63 -0
  25. package/build/angular/components/button.js +332 -0
  26. package/build/angular/components/button.js.map +1 -0
  27. package/build/angular/components/flat-list/index.d.ts +122 -0
  28. package/build/angular/components/flat-list/index.js +688 -0
  29. package/build/angular/components/flat-list/index.js.map +1 -0
  30. package/build/angular/components/image/index.android.d.ts +10 -0
  31. package/build/angular/components/image/index.android.js +59 -0
  32. package/build/angular/components/image/index.android.js.map +1 -0
  33. package/build/angular/components/image/index.d.ts +1 -0
  34. package/build/angular/components/image/index.ios.d.ts +10 -0
  35. package/build/angular/components/image/index.ios.js +59 -0
  36. package/build/angular/components/image/index.ios.js.map +1 -0
  37. package/build/angular/components/image/index.js +3 -0
  38. package/build/angular/components/image/index.js.map +1 -0
  39. package/build/angular/components/image/shared.d.ts +101 -0
  40. package/build/angular/components/image/shared.js +406 -0
  41. package/build/angular/components/image/shared.js.map +1 -0
  42. package/build/angular/components/image-background.d.ts +81 -0
  43. package/build/angular/components/image-background.js +418 -0
  44. package/build/angular/components/image-background.js.map +1 -0
  45. package/build/angular/components/input-accessory-view/index.d.ts +60 -0
  46. package/build/angular/components/input-accessory-view/index.js +251 -0
  47. package/build/angular/components/input-accessory-view/index.js.map +1 -0
  48. package/build/angular/components/keyboard-avoiding-view/index.d.ts +82 -0
  49. package/build/angular/components/keyboard-avoiding-view/index.js +358 -0
  50. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -0
  51. package/build/angular/components/modal/index.d.ts +92 -0
  52. package/build/angular/components/modal/index.js +360 -0
  53. package/build/angular/components/modal/index.js.map +1 -0
  54. package/build/angular/components/pressable/index.d.ts +123 -0
  55. package/build/angular/components/pressable/index.js +420 -0
  56. package/build/angular/components/pressable/index.js.map +1 -0
  57. package/build/angular/components/refresh-control/index.d.ts +86 -0
  58. package/build/angular/components/refresh-control/index.js +342 -0
  59. package/build/angular/components/refresh-control/index.js.map +1 -0
  60. package/build/angular/components/safe-area-view/index.d.ts +57 -0
  61. package/build/angular/components/safe-area-view/index.js +248 -0
  62. package/build/angular/components/safe-area-view/index.js.map +1 -0
  63. package/build/angular/components/scroll-view/index.android.d.ts +21 -0
  64. package/build/angular/components/scroll-view/index.android.js +235 -0
  65. package/build/angular/components/scroll-view/index.android.js.map +1 -0
  66. package/build/angular/components/scroll-view/index.d.ts +3 -0
  67. package/build/angular/components/scroll-view/index.ios.d.ts +9 -0
  68. package/build/angular/components/scroll-view/index.ios.js +86 -0
  69. package/build/angular/components/scroll-view/index.ios.js.map +1 -0
  70. package/build/angular/components/scroll-view/index.js +6 -0
  71. package/build/angular/components/scroll-view/index.js.map +1 -0
  72. package/build/angular/components/scroll-view/projection.d.ts +32 -0
  73. package/build/angular/components/scroll-view/projection.js +334 -0
  74. package/build/angular/components/scroll-view/projection.js.map +1 -0
  75. package/build/angular/components/scroll-view/shared.d.ts +230 -0
  76. package/build/angular/components/scroll-view/shared.js +682 -0
  77. package/build/angular/components/scroll-view/shared.js.map +1 -0
  78. package/build/angular/components/scroll-view/sticky-header.d.ts +40 -0
  79. package/build/angular/components/scroll-view/sticky-header.js +225 -0
  80. package/build/angular/components/scroll-view/sticky-header.js.map +1 -0
  81. package/build/angular/components/section-list/index.d.ts +116 -0
  82. package/build/angular/components/section-list/index.js +641 -0
  83. package/build/angular/components/section-list/index.js.map +1 -0
  84. package/build/angular/components/switch/index.android.d.ts +20 -0
  85. package/build/angular/components/switch/index.android.js +70 -0
  86. package/build/angular/components/switch/index.android.js.map +1 -0
  87. package/build/angular/components/switch/index.d.ts +1 -0
  88. package/build/angular/components/switch/index.ios.d.ts +19 -0
  89. package/build/angular/components/switch/index.ios.js +69 -0
  90. package/build/angular/components/switch/index.ios.js.map +1 -0
  91. package/build/angular/components/switch/index.js +4 -0
  92. package/build/angular/components/switch/index.js.map +1 -0
  93. package/build/angular/components/switch/shared.d.ts +40 -0
  94. package/build/angular/components/switch/shared.js +127 -0
  95. package/build/angular/components/switch/shared.js.map +1 -0
  96. package/build/angular/components/text-input.d.ts +153 -0
  97. package/build/angular/components/text-input.js +599 -0
  98. package/build/angular/components/text-input.js.map +1 -0
  99. package/build/angular/components/touchable/index.d.ts +245 -0
  100. package/build/angular/components/touchable/index.js +1186 -0
  101. package/build/angular/components/touchable/index.js.map +1 -0
  102. package/build/angular/components/touchable-native-feedback/index.d.ts +83 -0
  103. package/build/angular/components/touchable-native-feedback/index.js +371 -0
  104. package/build/angular/components/touchable-native-feedback/index.js.map +1 -0
  105. package/build/angular/components/virtualized-list/directives.d.ts +54 -0
  106. package/build/angular/components/virtualized-list/directives.js +153 -0
  107. package/build/angular/components/virtualized-list/directives.js.map +1 -0
  108. package/build/angular/components/virtualized-list/index.d.ts +253 -0
  109. package/build/angular/components/virtualized-list/index.js +1031 -0
  110. package/build/angular/components/virtualized-list/index.js.map +1 -0
  111. package/build/angular/components/virtualized-section-list/directives.d.ts +37 -0
  112. package/build/angular/components/virtualized-section-list/directives.js +76 -0
  113. package/build/angular/components/virtualized-section-list/directives.js.map +1 -0
  114. package/build/angular/components/virtualized-section-list/index.d.ts +178 -0
  115. package/build/angular/components/virtualized-section-list/index.js +720 -0
  116. package/build/angular/components/virtualized-section-list/index.js.map +1 -0
  117. package/build/angular/components.d.ts +39 -0
  118. package/build/angular/components.js +38 -0
  119. package/build/angular/components.js.map +1 -0
  120. package/build/angular/create-portal/index.d.ts +22 -0
  121. package/build/angular/create-portal/index.js +74 -0
  122. package/build/angular/create-portal/index.js.map +1 -0
  123. package/build/angular/create-tunnel/index.d.ts +34 -0
  124. package/build/angular/create-tunnel/index.js +126 -0
  125. package/build/angular/create-tunnel/index.js.map +1 -0
  126. package/build/angular/descriptor-to-angular/index.d.ts +22 -0
  127. package/build/angular/descriptor-to-angular/index.js +142 -0
  128. package/build/angular/descriptor-to-angular/index.js.map +1 -0
  129. package/build/angular/host-instance/index.d.ts +2 -0
  130. package/build/angular/host-instance/index.js +37 -0
  131. package/build/angular/host-instance/index.js.map +1 -0
  132. package/build/angular/index.d.ts +22 -0
  133. package/build/angular/index.js +46 -0
  134. package/build/angular/index.js.map +1 -0
  135. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +14 -0
  136. package/build/angular/modules/animated/animated-leaf-binder/index.js +91 -0
  137. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +1 -0
  138. package/build/angular/modules/animated/animated-leaf-binder.d.ts +14 -0
  139. package/build/angular/modules/animated/animated-leaf-binder.js +91 -0
  140. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -0
  141. package/build/angular/modules/animated/create-animated-component.d.ts +59 -0
  142. package/build/angular/modules/animated/create-animated-component.js +364 -0
  143. package/build/angular/modules/animated/create-animated-component.js.map +1 -0
  144. package/build/angular/modules/animated/index.d.ts +51 -0
  145. package/build/angular/modules/animated/index.js +59 -0
  146. package/build/angular/modules/animated/index.js.map +1 -0
  147. package/build/angular/modules/app-registry/index.d.ts +7 -0
  148. package/build/angular/modules/app-registry/index.js +23 -0
  149. package/build/angular/modules/app-registry/index.js.map +1 -0
  150. package/build/angular/modules/status-bar/index.d.ts +20 -0
  151. package/build/angular/modules/status-bar/index.js +59 -0
  152. package/build/angular/modules/status-bar/index.js.map +1 -0
  153. package/build/angular/primitives/index.d.ts +63 -0
  154. package/build/angular/primitives/index.js +189 -0
  155. package/build/angular/primitives/index.js.map +1 -0
  156. package/build/angular/primitives/shared.d.ts +88 -0
  157. package/build/angular/primitives/shared.js +171 -0
  158. package/build/angular/primitives/shared.js.map +1 -0
  159. package/build/angular/render/index.d.ts +11 -0
  160. package/build/angular/render/index.js +171 -0
  161. package/build/angular/render/index.js.map +1 -0
  162. package/build/angular/renderer/index.d.ts +37 -0
  163. package/build/angular/renderer/index.js +301 -0
  164. package/build/angular/renderer/index.js.map +1 -0
  165. package/build/angular/services/color-scheme.service.d.ts +16 -0
  166. package/build/angular/services/color-scheme.service.js +31 -0
  167. package/build/angular/services/color-scheme.service.js.map +1 -0
  168. package/build/angular/services/index.d.ts +2 -0
  169. package/build/angular/services/index.js +3 -0
  170. package/build/angular/services/index.js.map +1 -0
  171. package/build/angular/services/window-dimensions.service.d.ts +16 -0
  172. package/build/angular/services/window-dimensions.service.js +42 -0
  173. package/build/angular/services/window-dimensions.service.js.map +1 -0
  174. package/metro-config.cjs +48 -0
  175. package/metro-css-parser.cjs +13 -0
  176. package/package.json +80 -0
  177. package/src/__tests__/responder-change-detection.test.ts +102 -0
  178. package/src/__tests__/responder-nested-cd.test.ts +113 -0
  179. package/src/anchor-host-registry.ts +88 -0
  180. package/src/angular-gaps.test.ts +142 -0
  181. package/src/angular-imports.test.ts +100 -0
  182. package/src/bootstrap.ts +26 -0
  183. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +44 -0
  184. package/src/components/activity-indicator/activity-indicator.test.ts +131 -0
  185. package/src/components/activity-indicator/index.android.ts +16 -0
  186. package/src/components/activity-indicator/index.ios.ts +17 -0
  187. package/src/components/activity-indicator/index.ts +4 -0
  188. package/src/components/activity-indicator/shared.ts +206 -0
  189. package/src/components/button.ts +193 -0
  190. package/src/components/flat-list/column-wrapper-style-class.test.ts +116 -0
  191. package/src/components/flat-list/flat-list-array-style.test.ts +131 -0
  192. package/src/components/flat-list/flat-list-class-style.test.ts +74 -0
  193. package/src/components/flat-list/flat-list-scroll-containment.test.ts +107 -0
  194. package/src/components/flat-list/flat-list.test.ts +99 -0
  195. package/src/components/flat-list/index.ts +574 -0
  196. package/src/components/image/image-class-style.test.ts +46 -0
  197. package/src/components/image/index.android.ts +48 -0
  198. package/src/components/image/index.ios.ts +48 -0
  199. package/src/components/image/index.ts +3 -0
  200. package/src/components/image/shared.ts +479 -0
  201. package/src/components/image-background-class-style.test.ts +48 -0
  202. package/src/components/image-background-style-class.test.ts +79 -0
  203. package/src/components/image-background.ts +270 -0
  204. package/src/components/input-accessory-view/index.ts +185 -0
  205. package/src/components/input-accessory-view/input-accessory-view.test.ts +43 -0
  206. package/src/components/keyboard-avoiding-view/index.ts +303 -0
  207. package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +54 -0
  208. package/src/components/modal/index.ts +299 -0
  209. package/src/components/modal/modal.test.ts +43 -0
  210. package/src/components/pressable/index.ts +373 -0
  211. package/src/components/pressable/pressable.test.ts +85 -0
  212. package/src/components/refresh-control/index.ts +284 -0
  213. package/src/components/refresh-control/refresh-control.test.ts +41 -0
  214. package/src/components/safe-area-view/index.ts +177 -0
  215. package/src/components/safe-area-view/safe-area-view.test.ts +43 -0
  216. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +123 -0
  217. package/src/components/scroll-view/content-container-style-class.test.ts +75 -0
  218. package/src/components/scroll-view/index.android.ts +190 -0
  219. package/src/components/scroll-view/index.ios.ts +79 -0
  220. package/src/components/scroll-view/index.ts +7 -0
  221. package/src/components/scroll-view/projection.ts +414 -0
  222. package/src/components/scroll-view/scroll-view-class-style.test.ts +134 -0
  223. package/src/components/scroll-view/scroll-view-projection.test.ts +194 -0
  224. package/src/components/scroll-view/shared.ts +844 -0
  225. package/src/components/scroll-view/sticky-header.ts +255 -0
  226. package/src/components/section-list/index.ts +445 -0
  227. package/src/components/switch/android-switch-class.test.ts +42 -0
  228. package/src/components/switch/index.android.ts +61 -0
  229. package/src/components/switch/index.ios.ts +60 -0
  230. package/src/components/switch/index.ts +4 -0
  231. package/src/components/switch/shared.ts +197 -0
  232. package/src/components/switch/switch.test.ts +92 -0
  233. package/src/components/text-input-class-style.test.ts +54 -0
  234. package/src/components/text-input.ts +531 -0
  235. package/src/components/touchable/index.ts +629 -0
  236. package/src/components/touchable/touchable.test.ts +102 -0
  237. package/src/components/touchable-native-feedback/index.ts +254 -0
  238. package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +49 -0
  239. package/src/components/virtualized-list/directives.ts +168 -0
  240. package/src/components/virtualized-list/index.ts +992 -0
  241. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +76 -0
  242. package/src/components/virtualized-section-list/directives.ts +90 -0
  243. package/src/components/virtualized-section-list/index.ts +601 -0
  244. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +60 -0
  245. package/src/components.ts +149 -0
  246. package/src/create-portal/create-portal.test.ts +90 -0
  247. package/src/create-portal/index.ts +74 -0
  248. package/src/create-tunnel/create-tunnel.test.ts +120 -0
  249. package/src/create-tunnel/index.ts +136 -0
  250. package/src/descriptor-to-angular/descriptor-outlet.test.ts +94 -0
  251. package/src/descriptor-to-angular/index.ts +190 -0
  252. package/src/host-instance/host-instance.test.ts +49 -0
  253. package/src/host-instance/index.ts +36 -0
  254. package/src/index.ts +261 -0
  255. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +116 -0
  256. package/src/modules/animated/animated-leaf-binder/index.ts +101 -0
  257. package/src/modules/animated/animated-leaf-binder.test.ts +116 -0
  258. package/src/modules/animated/animated-leaf-binder.ts +101 -0
  259. package/src/modules/animated/animated-scroll-view.test.ts +102 -0
  260. package/src/modules/animated/create-animated-component.ts +365 -0
  261. package/src/modules/animated/index.ts +105 -0
  262. package/src/modules/app-registry/app-registry.test.ts +106 -0
  263. package/src/modules/app-registry/index.ts +60 -0
  264. package/src/modules/status-bar/index.ts +57 -0
  265. package/src/modules/status-bar/status-bar.test.ts +50 -0
  266. package/src/primitives/index.ts +119 -0
  267. package/src/primitives/shared.ts +190 -0
  268. package/src/render/index.ts +235 -0
  269. package/src/render/render.test.ts +147 -0
  270. package/src/renderer/index.ts +347 -0
  271. package/src/renderer/renderer.test.ts +203 -0
  272. package/src/services/color-scheme.service.ts +27 -0
  273. package/src/services/index.ts +2 -0
  274. package/src/services/services.test.ts +163 -0
  275. package/src/services/window-dimensions.service.ts +41 -0
  276. package/tsconfig.angular.base.json +30 -0
@@ -0,0 +1,42 @@
1
+ import { DestroyRef, inject, Injectable, signal } from '@angular/core';
2
+ import { Dimensions } from '@symbiote-native/engine';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Angular twin of React's `useWindowDimensions` and Vue's `useWindowDimensions` composable. The
6
+ * service holds a Signal that tracks window metrics; consumers read it via `inject`:
7
+ *
8
+ * readonly dimensions = inject(WindowDimensionsService).dimensions;
9
+ *
10
+ * State and lifecycle live in DI, not in a function closure.
11
+ */
12
+ export class WindowDimensionsService {
13
+ dimensions = signal(Dimensions.get('window'), /* @ts-ignore */
14
+ ...(ngDevMode ? [{ debugName: "dimensions" }] : /* istanbul ignore next */ []));
15
+ constructor() {
16
+ const destroyRef = inject(DestroyRef);
17
+ const handleChange = (window) => {
18
+ const current = this.dimensions();
19
+ if (current.width !== window.width ||
20
+ current.height !== window.height ||
21
+ current.scale !== window.scale ||
22
+ current.fontScale !== window.fontScale) {
23
+ this.dimensions.set(window);
24
+ }
25
+ };
26
+ const subscription = Dimensions.addEventListener('change', (set) => {
27
+ handleChange(set.window);
28
+ });
29
+ // Re-check once after subscribing to close the gap between construction and the listener.
30
+ handleChange(Dimensions.get('window'));
31
+ destroyRef.onDestroy(() => {
32
+ subscription.remove();
33
+ });
34
+ }
35
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: WindowDimensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
36
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: WindowDimensionsService, providedIn: 'root' });
37
+ }
38
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: WindowDimensionsService, decorators: [{
39
+ type: Injectable,
40
+ args: [{ providedIn: 'root' }]
41
+ }], ctorParameters: () => [] });
42
+ //# sourceMappingURL=window-dimensions.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window-dimensions.service.js","sourceRoot":"","sources":["../../../src/services/window-dimensions.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,UAAU,EAA6C,MAAM,yBAAyB,CAAC;;AAEhG;;;;;;;GAOG;AAEH,MAAM,OAAO,uBAAuB;IACzB,UAAU,GAAG,MAAM,CAAkB,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;mFAAC,CAAC;IAExE;QACE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAEtC,MAAM,YAAY,GAAG,CAAC,MAAuB,EAAQ,EAAE;YACrD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAClC,IACE,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK;gBAC9B,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;gBAChC,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK;gBAC9B,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,EACtC,CAAC;gBACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,GAAmB,EAAE,EAAE;YACjF,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QACH,0FAA0F;QAC1F,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEvC,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE;YACxB,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;uGA3BU,uBAAuB;2GAAvB,uBAAuB,cADV,MAAM;;2FACnB,uBAAuB;kBADnC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -0,0 +1,48 @@
1
+ // Angular's AOT pipeline needs ONE Metro-side accommodation beyond the plain css-parser
2
+ // transformer: ngc mirrors this app's whole source tree into its own outDir (see
3
+ // tsconfig.angular.base.json's `outDir` convention, "build/angular" by default) but only ever
4
+ // compiles .ts — a relative style import (`import './App.css'`) survives untouched in the
5
+ // compiled .js, still pointing at the ORIGINAL source location, which ngc never copies there.
6
+ // Metro resolves that relative specifier against the COMPILED file's own location
7
+ // (<outDir>/...), so without this it 404s on a file that was never created there. This redirects
8
+ // such an import back to the real source file instead of copying it — a copy would need its own
9
+ // re-copy on every CSS edit during `ngc --watch` (which only re-runs on .ts changes), while this
10
+ // redirect lets Metro's own watchFolders pick up a source-file CSS edit for free. Applies
11
+ // identically to a release `react-native bundle`, which resolves through this same config.
12
+ //
13
+ // watchFolders/extraNodeModules are intentionally NOT included here — those are monorepo-only
14
+ // pnpm-workspace concerns (deduping a single react/@angular/core copy across packages), not
15
+ // relevant to an external app installing @symbiote-native/angular from npm; a consumer keeps
16
+ // those local to their own metro.config.js if it needs them at all.
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ function withSymbioteAngularMetroConfig(defaultConfig, projectRoot, { outDir = 'build/angular' } = {}) {
21
+ const buildRoot = path.join(projectRoot, ...outDir.split('/'));
22
+
23
+ return {
24
+ resolver: {
25
+ // Teach Metro that a style file is a source file (the transformer above turns it into a
26
+ // module). scss/sass/less/styl are optional preprocessor sources, reduced to plain CSS
27
+ // before the same transformer runs.
28
+ sourceExts: [...defaultConfig.resolver.sourceExts, 'css', 'scss', 'sass', 'less', 'styl'],
29
+ resolveRequest: (context, moduleName, platform) => {
30
+ const isRelativeStyleImport =
31
+ /^\.\.?\//.test(moduleName) && /\.(css|scss|sass|less|styl)$/.test(moduleName);
32
+ if (isRelativeStyleImport) {
33
+ const originDir = path.dirname(context.originModulePath);
34
+ if (originDir === buildRoot || originDir.startsWith(buildRoot + path.sep)) {
35
+ const sourceDir = path.join(projectRoot, path.relative(buildRoot, originDir));
36
+ const sourceFile = path.resolve(sourceDir, moduleName);
37
+ if (fs.existsSync(sourceFile)) {
38
+ return { type: 'sourceFile', filePath: sourceFile };
39
+ }
40
+ }
41
+ }
42
+ return context.resolveRequest(context, moduleName, platform);
43
+ },
44
+ },
45
+ };
46
+ }
47
+
48
+ module.exports = { withSymbioteAngularMetroConfig };
@@ -0,0 +1,13 @@
1
+ // Re-exports @symbiote-native/css-parser verbatim from INSIDE this package, so a consuming app's own
2
+ // require('@symbiote-native/angular/metro-css-parser') resolves css-parser relative to THIS file's
3
+ // location (adapters/angular/node_modules, where css-parser IS a real dependency — see
4
+ // package.json), not relative to the app's own node_modules, which does not and should not
5
+ // need to declare @symbiote-native/css-parser itself. Node resolves each require() relative to the
6
+ // requiring FILE's own location, so this indirection is what actually removes the extra install
7
+ // step, not pnpm hoisting (which does not propagate this transitively across workspace packages
8
+ // the way a flat classic node_modules would). .cjs, not .js: this package is "type": "module",
9
+ // and Metro's babelTransformerPath loading expects a require()-able module.
10
+ // createCssMetroTransformer is a factory, not a ready transformer — Metro's
11
+ // babelTransformerPath needs the actual {transform, getCacheKey} object it returns,
12
+ // not the css-parser package barrel.
13
+ module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@symbiote-native/angular",
3
+ "version": "0.0.0-canary-20260724081123",
4
+ "description": "SymbioteNative's Angular adapter — a Renderer2/RendererFactory2 with DOM-less bootstrap driving real native iOS/Android views through the same engine as the other adapters.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "adapters/angular"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/adapters/angular#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
+ },
15
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "type": "module",
17
+ "main": "src/index.ts",
18
+ "module": "src/index.ts",
19
+ "types": "src/index.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./build/angular/index.d.ts",
23
+ "react-native": "./build/angular/index.js",
24
+ "default": "./src/index.ts"
25
+ },
26
+ "./bootstrap": {
27
+ "types": "./build/angular/bootstrap.d.ts",
28
+ "react-native": "./build/angular/bootstrap.js",
29
+ "default": "./src/bootstrap.ts"
30
+ },
31
+ "./metro-css-parser": "./metro-css-parser.cjs",
32
+ "./metro-config": "./metro-config.cjs",
33
+ "./babel-linker": "./babel-linker.cjs",
34
+ "./babel-register-composed": "./babel-register-composed.cjs",
35
+ "./tsconfig.angular.base.json": "./tsconfig.angular.base.json"
36
+ },
37
+ "files": [
38
+ "src",
39
+ "build",
40
+ "bin",
41
+ "metro-css-parser.cjs",
42
+ "metro-config.cjs",
43
+ "babel-linker.cjs",
44
+ "babel-register-composed.cjs",
45
+ "tsconfig.angular.base.json"
46
+ ],
47
+ "bin": {
48
+ "symbiote-angular-dev": "./bin/symbiote-angular-dev.cjs"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "@angular/compiler-cli": "^22",
55
+ "@react-native/typescript-config": "0.86.0",
56
+ "@symbiote-native/components": "0.3.0",
57
+ "@symbiote-native/css-parser": "0.2.3"
58
+ },
59
+ "peerDependencies": {
60
+ "@angular/core": ">=20",
61
+ "@symbiote-native/engine": ">=0.1.7",
62
+ "react-native": ">=0.86"
63
+ },
64
+ "devDependencies": {
65
+ "@angular/compiler": "^22",
66
+ "@angular/core": "^22",
67
+ "@babel/core": "^7.25.2",
68
+ "@types/babel__core": "^7.20.5",
69
+ "@types/node": "^26.0.0",
70
+ "typescript": "~6.0.0",
71
+ "@symbiote-native/engine": "0.1.7",
72
+ "@symbiote-native/test-utils": "0.1.6"
73
+ },
74
+ "scripts": {
75
+ "clean": "rm -rf build",
76
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
77
+ "typecheck": "tsc --build",
78
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
79
+ }
80
+ }
@@ -0,0 +1,102 @@
1
+ // A flat-bag `onX` responder callback is invoked by the engine's event dispatch, entirely
2
+ // outside Angular. In Angular 20 components compile as SignalView (not CheckAlways), so a plain
3
+ // state mutation inside such a callback dirties nothing and the template stays stale — the "pan
4
+ // does nothing" bug, while React/Vue repainted the same demo. SymbioteHostPropsDirective now
5
+ // calls `cdr.markForCheck()` after each such callback (flags the component + ancestors with
6
+ // RefreshView and notifies the scheduler), so the mutation repaints. This is the flat-root case;
7
+ // responder-nested-cd.test.ts covers the App→child nesting the real app has.
8
+
9
+ import '@angular/compiler';
10
+ import { Component } from '@angular/core';
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
13
+
14
+ import { mount, unmount } from '../render';
15
+ import { ViewHost as View, TextHost as Text, SymbioteHostPropsDirective } from '../primitives';
16
+
17
+ const ROOT_TAG = 970;
18
+ const TOUCH_START = 'topTouchStart';
19
+ const TOUCH_MOVE = 'topTouchMove';
20
+ const TOUCH_END = 'topTouchEnd';
21
+
22
+ const fabric = installFabric();
23
+
24
+ // Fabric is clone-on-write: a prop update yields a NEW node object in the committed tree,
25
+ // never in `created`. Walk the live committed child-set for post-mutation assertions.
26
+ function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
27
+ const stack = [...fabric.committed];
28
+ while (stack.length > 0) {
29
+ const node = stack.pop();
30
+ if (node === undefined) continue;
31
+ if (predicate(node)) return node;
32
+ stack.push(...node.children);
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ function statusText(testID: string): string | undefined {
38
+ const node = findCommitted(n => n.props.testID === testID);
39
+ const raw = node?.children[0];
40
+ return typeof raw?.props.text === 'string' ? raw.props.text : undefined;
41
+ }
42
+
43
+ // The stable SymbioteNode (event target). Its identity and listener map survive clone-on-
44
+ // write, so firing on the first-created handle reaches the live listeners.
45
+ function handleFor(testID: string): unknown {
46
+ const node = fabric.find(n => n.props.testID === testID);
47
+ if (!node) throw new Error(`no node created with testID=${testID}`);
48
+ return node.instanceHandle;
49
+ }
50
+
51
+ const flush = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
52
+
53
+ @Component({
54
+ selector: 'symbiote-responder-cd-host',
55
+ standalone: true,
56
+ imports: [View, Text, SymbioteHostPropsDirective],
57
+ template: `
58
+ <View [symbioteHostProps]="handlers"></View>
59
+ <Text [symbioteHostProps]="statusProps">{{ status }}</Text>
60
+ `,
61
+ })
62
+ class ResponderCdHost {
63
+ status = 'idle';
64
+ statusProps = { testID: 'status' };
65
+ handlers = {
66
+ testID: 'chip',
67
+ onStartShouldSetResponder: () => true,
68
+ onResponderGrant: () => {
69
+ this.status = 'granted';
70
+ },
71
+ onResponderMove: () => {
72
+ this.status = 'moving';
73
+ },
74
+ onResponderRelease: () => {
75
+ this.status = 'released';
76
+ },
77
+ };
78
+ }
79
+
80
+ beforeEach(() => fabric.reset());
81
+ afterEach(() => unmount(ROOT_TAG));
82
+
83
+ describe('Angular responder callback triggers change detection', () => {
84
+ it('re-renders bound state mutated inside a flat-bag responder callback', async () => {
85
+ mount(ROOT_TAG, ResponderCdHost);
86
+ await flush();
87
+ expect(statusText('status')).toBe('idle');
88
+
89
+ const chip = handleFor('chip');
90
+ fabric.fireEvent(chip, TOUCH_START);
91
+ await flush();
92
+ expect(statusText('status')).toBe('granted');
93
+
94
+ fabric.fireEvent(chip, TOUCH_MOVE);
95
+ await flush();
96
+ expect(statusText('status')).toBe('moving');
97
+
98
+ fabric.fireEvent(chip, TOUCH_END);
99
+ await flush();
100
+ expect(statusText('status')).toBe('released');
101
+ });
102
+ });
@@ -0,0 +1,113 @@
1
+ // The device-faithful shape: a ROOT component whose template embeds a CHILD component that
2
+ // owns the responder state + the {{status}} binding (App -> ResponderDemo). Angular 20 compiles
3
+ // components as SignalView (not CheckAlways), so a plain (non-signal) state mutation inside a
4
+ // flat-bag responder callback does NOT dirty the child's reactive consumer, and the scheduler's
5
+ // root detectChanges() will NOT descend into it — the child's {{status}} used to stay stale
6
+ // ("pan does nothing"). SymbioteHostPropsDirective now refreshes its own host component's view
7
+ // right after the callback runs, so the nested child repaints.
8
+
9
+ import '@angular/compiler';
10
+ import { Component } from '@angular/core';
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
13
+
14
+ import { mount, unmount } from '../render';
15
+ import { ViewHost as View, TextHost as Text, SymbioteHostPropsDirective } from '../primitives';
16
+ import { registerComposedComponent } from '../anchor-host-registry';
17
+
18
+ const ROOT_TAG = 972;
19
+ const TOUCH_START = 'topTouchStart';
20
+ const TOUCH_MOVE = 'topTouchMove';
21
+ const TOUCH_END = 'topTouchEnd';
22
+
23
+ registerComposedComponent('nested-responder-inner');
24
+
25
+ const fabric = installFabric();
26
+
27
+ function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
28
+ const stack = [...fabric.committed];
29
+ while (stack.length > 0) {
30
+ const node = stack.pop();
31
+ if (node === undefined) continue;
32
+ if (predicate(node)) return node;
33
+ stack.push(...node.children);
34
+ }
35
+ return undefined;
36
+ }
37
+
38
+ function statusText(testID: string): string | undefined {
39
+ const node = findCommitted(n => n.props.testID === testID);
40
+ const raw = node?.children[0];
41
+ return typeof raw?.props.text === 'string' ? raw.props.text : undefined;
42
+ }
43
+
44
+ function handleFor(testID: string): unknown {
45
+ const node = fabric.find(n => n.props.testID === testID);
46
+ if (!node) throw new Error(`no node created with testID=${testID}`);
47
+ return node.instanceHandle;
48
+ }
49
+
50
+ const flush = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
51
+
52
+ @Component({
53
+ selector: 'nested-responder-inner',
54
+ standalone: true,
55
+ imports: [View, Text, SymbioteHostPropsDirective],
56
+ template: `
57
+ <View [symbioteHostProps]="handlers"></View>
58
+ <Text [symbioteHostProps]="statusProps">{{ status }}</Text>
59
+ `,
60
+ })
61
+ class NestedResponderInner {
62
+ status = 'idle';
63
+ statusProps = { testID: 'nested-status' };
64
+ handlers = {
65
+ testID: 'nested-chip',
66
+ onStartShouldSetResponder: () => true,
67
+ onResponderGrant: () => {
68
+ this.status = 'granted';
69
+ },
70
+ onResponderMove: () => {
71
+ this.status = 'moving';
72
+ },
73
+ onResponderRelease: () => {
74
+ this.status = 'released';
75
+ },
76
+ };
77
+ }
78
+
79
+ @Component({
80
+ selector: 'nested-responder-outer',
81
+ standalone: true,
82
+ imports: [View, NestedResponderInner],
83
+ template: `
84
+ <View>
85
+ <nested-responder-inner></nested-responder-inner>
86
+ </View>
87
+ `,
88
+ })
89
+ class NestedResponderOuter {}
90
+
91
+ beforeEach(() => fabric.reset());
92
+ afterEach(() => unmount(ROOT_TAG));
93
+
94
+ describe('Angular nested-child responder CD', () => {
95
+ it('re-renders a nested child component`s state mutated in a flat-bag responder callback', async () => {
96
+ mount(ROOT_TAG, NestedResponderOuter);
97
+ await flush();
98
+ expect(statusText('nested-status')).toBe('idle');
99
+
100
+ const chip = handleFor('nested-chip');
101
+ fabric.fireEvent(chip, TOUCH_START);
102
+ await flush();
103
+ expect(statusText('nested-status')).toBe('granted');
104
+
105
+ fabric.fireEvent(chip, TOUCH_MOVE);
106
+ await flush();
107
+ expect(statusText('nested-status')).toBe('moving');
108
+
109
+ fabric.fireEvent(chip, TOUCH_END);
110
+ await flush();
111
+ expect(statusText('nested-status')).toBe('released');
112
+ });
113
+ });
@@ -0,0 +1,88 @@
1
+ // Registry of composed-component selectors that must NOT paint a native view of their own.
2
+ //
3
+ // A dependency-free LEAF module (imports nothing from the adapter barrel `./index` or
4
+ // `./components`, both deeply require-cyclic). Both consumers reach it by RELATIVE path — the
5
+ // renderer imports `isAnchorHostComponent` from `../anchor-host-registry`, the barrel re-exports
6
+ // `registerComposedComponent` from `./anchor-host-registry` — so Metro keys it to ONE module
7
+ // instance / ONE Set. Do NOT give it a package `exports` subpath and inject THAT bare specifier
8
+ // into app code: a subpath resolves via a different path than the relative imports under pnpm
9
+ // symlinks, which splits the Set in two.
10
+ //
11
+ // Housing the registry here rather than inline in `renderer/index.ts` keeps it out of the require
12
+ // cycle (cheap insurance against a load-order strand). The bug that actually surfaced this,
13
+ // though, was STALE BUILD ARTIFACTS, not the cycle: `ngc -p` never deletes orphaned outputs, so
14
+ // after the renderer moved `renderer.ts` → `renderer/index.ts` the orphaned `build/angular/
15
+ // renderer.js` lingered and SHADOWED `build/angular/renderer/index.js` (a file beats a folder in
16
+ // Node/Metro resolution), giving the bundle a second, stale registry Set — fixed by cleaning
17
+ // `build/` before every ngc build (see each Angular package's `clean` script). Full record:
18
+ // angular-adapter §11c, symbiote-dev-examples §5e.
19
+
20
+ // Lowercased at construction — isAnchorHostComponent and registerComposedComponent both normalize
21
+ // to lowercase (Angular lowercases a dynamically-mounted component's selector at runtime, see
22
+ // angular-adapter §11a), so the literal entries below must match that or every capitalized one
23
+ // (all but the handful already written lowercase) silently never matches .has() and falls through
24
+ // to a real Fabric createNode — the exact "Unimplemented component" / extra-wrapper-node bug this
25
+ // Set exists to prevent.
26
+ const ANCHOR_HOST_COMPONENTS: Set<string> = new Set(
27
+ [
28
+ // Composed Angular components render their real Fabric descriptor tree from the template;
29
+ // their Angular host element is only a framework bookkeeping node and must not paint. This
30
+ // is NOT limited to adapter-authored components — ANY custom composed @Component used as a
31
+ // plain <Tag> inside another template needs its selector listed here too, or Angular's
32
+ // automatic host-element creation for it falls through to a raw Fabric createNode call with
33
+ // an unrecognized view name, which paints RN's own "Unimplemented component: <Tag>" fallback
34
+ // view instead (a real device-visible bug, not a silent no-op). This Set holds only
35
+ // adapter/engine-owned selectors; app code and third-party packages self-register their own
36
+ // composed components through registerComposedComponent instead of being hardcoded here.
37
+ 'ActivityIndicator',
38
+ 'Button',
39
+ 'FlatList',
40
+ 'AnimatedView',
41
+ 'symbiote-animated-view',
42
+ 'AnimatedText',
43
+ 'symbiote-animated-text',
44
+ 'AnimatedImage',
45
+ 'symbiote-animated-image',
46
+ 'AnimatedScrollView',
47
+ 'symbiote-animated-scroll-view',
48
+ 'symbiote-descriptor-outlet',
49
+ 'tunnel-out',
50
+ 'Image',
51
+ 'ImageBackground',
52
+ 'InputAccessoryView',
53
+ 'KeyboardAvoidingView',
54
+ 'Modal',
55
+ 'Pressable',
56
+ 'RefreshControl',
57
+ 'SafeAreaView',
58
+ 'ScrollView',
59
+ 'ScrollViewStickyHeader',
60
+ 'SectionList',
61
+ 'symbiote-sticky-header',
62
+ 'StatusBar',
63
+ 'Switch',
64
+ 'Text',
65
+ 'TextInput',
66
+ 'TouchableHighlight',
67
+ 'TouchableNativeFeedback',
68
+ 'TouchableOpacity',
69
+ 'TouchableWithoutFeedback',
70
+ 'VirtualizedList',
71
+ 'VirtualizedSectionList',
72
+ 'symbiote-pressable',
73
+ ].map(selector => selector.toLowerCase()),
74
+ );
75
+
76
+ // A composed component's selector must be registered so createElement gives it a non-painting
77
+ // anchor host (see the Set comment above). Tests register their own child-component selectors
78
+ // through this at module load; app-authored composed components self-register the same way via
79
+ // the babel-register-composed plugin.
80
+ export function registerComposedComponent(selector: string): void {
81
+ ANCHOR_HOST_COMPONENTS.add(selector.toLowerCase());
82
+ }
83
+
84
+ // True when `engineName` is a composed selector that must anchor-host (not paint). Case-
85
+ // insensitive — the Set is lowercased at construction, so callers pass the raw selector.
86
+ export function isAnchorHostComponent(engineName: string): boolean {
87
+ return ANCHOR_HOST_COMPONENTS.has(engineName.toLowerCase());
88
+ }
@@ -0,0 +1,142 @@
1
+ /// <reference types="node" />
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ const readSource = (path: string): string => readFileSync(path, 'utf8');
7
+
8
+ describe('Angular adapter gap regressions', () => {
9
+ it('VirtualizedList exposes accessibility and aria inputs and forwards them to ScrollView', () => {
10
+ const source = readSource('adapters/angular/src/components/virtualized-list/index.ts');
11
+
12
+ expect(source).toContain('@Input() accessibilityLabel?: string');
13
+ expect(source).toContain('@Input() ariaBusy?: boolean');
14
+ expect(source).toContain('[accessibilityLabel]="foldedAccessibility.accessibilityLabel"');
15
+ expect(source).toContain('[accessibilityState]="foldedAccessibility.accessibilityState"');
16
+ expect(source).toContain('[accessibilityRole]="foldedAccessibility.accessibilityRole"');
17
+ });
18
+
19
+ it('Animated namespace exposes AOT-safe built-in entries and documents runtime HOC as a non-goal', () => {
20
+ const animatedSource = readSource('adapters/angular/src/modules/animated/index.ts');
21
+ const componentSource = readSource(
22
+ 'adapters/angular/src/modules/animated/create-animated-component.ts',
23
+ );
24
+
25
+ expect(componentSource).toContain('export const AnimatedFlatList');
26
+ expect(componentSource).toContain('export const AnimatedSectionList');
27
+ expect(componentSource).toContain('if (base === View) return AnimatedView');
28
+ expect(componentSource).toContain('if (base === Text) return AnimatedText');
29
+ expect(componentSource).toContain('if (base === Image) return AnimatedImage');
30
+ expect(componentSource).toContain('if (base === ScrollView) return AnimatedScrollView');
31
+ expect(componentSource).toContain('Angular cannot synthesize a component at runtime');
32
+ expect(componentSource).toContain('no JIT under AOT/Metro');
33
+ expect(componentSource).toContain(
34
+ 'Author an explicit standalone @Component extending AnimatedComponentBase',
35
+ );
36
+ expect(animatedSource).toContain('View: AnimatedView');
37
+ expect(animatedSource).toContain('Text: AnimatedText');
38
+ expect(animatedSource).toContain('Image: AnimatedImage');
39
+ expect(animatedSource).toContain('ScrollView: AnimatedScrollView');
40
+ expect(animatedSource).toContain('FlatList: AnimatedFlatList');
41
+ expect(animatedSource).toContain('SectionList: AnimatedSectionList');
42
+ });
43
+
44
+ it('AnimatedImage uses composed Image props and normalized load events', () => {
45
+ const source = readSource('adapters/angular/src/modules/animated/create-animated-component.ts');
46
+
47
+ expect(source).toContain('export class AnimatedImage extends ImageBase');
48
+ expect(source).toContain('inputs: ANIMATED_IMAGE_INPUTS');
49
+ expect(source).toContain('outputs: IMAGE_OUTPUTS');
50
+ expect(source).toContain('const resolved = resolveImageProps(reduced)');
51
+ expect(source).toContain('(load)="handleLoad($event)"');
52
+ expect(source).toContain('(error)="handleError($event)"');
53
+ });
54
+
55
+ it('Image prop resolution is shared with AnimatedImage', () => {
56
+ const imageSource = readSource('adapters/angular/src/components/image/shared.ts');
57
+
58
+ expect(imageSource).toContain('export function resolveImageProps');
59
+ expect(imageSource).toContain('get imageProps(): Record<string, unknown>');
60
+ expect(imageSource).toContain('return resolveImageProps(this.imageInputProps)');
61
+ });
62
+
63
+ it('Button forwards its full accessibility and TV-focus surface through TouchableOpacity', () => {
64
+ const buttonSource = readSource('adapters/angular/src/components/button.ts');
65
+ const touchableSource = readSource('adapters/angular/src/components/touchable/index.ts');
66
+ const pressableSource = readSource('adapters/angular/src/components/pressable/index.ts');
67
+
68
+ for (const binding of [
69
+ '[accessible]="true"',
70
+ '[accessibilityLabelledBy]="accessibilityLabelledBy"',
71
+ '[importantForAccessibility]="importantForAccessibility"',
72
+ '[accessibilityLiveRegion]="accessibilityLiveRegion"',
73
+ '[screenReaderFocusable]="screenReaderFocusable"',
74
+ '[accessibilityViewIsModal]="accessibilityViewIsModal"',
75
+ '[accessibilityElementsHidden]="accessibilityElementsHidden"',
76
+ '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
77
+ '[accessibilityLanguage]="accessibilityLanguage"',
78
+ '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
79
+ '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
80
+ '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
81
+ '(accessibilityAction)="accessibilityAction.emit($event)"',
82
+ '(accessibilityTap)="accessibilityTap.emit($event)"',
83
+ '(magicTap)="magicTap.emit($event)"',
84
+ '(accessibilityEscape)="accessibilityEscape.emit($event)"',
85
+ '[ariaModal]="ariaModal"',
86
+ '[ariaValueMax]="ariaValueMax"',
87
+ '[ariaValueMin]="ariaValueMin"',
88
+ '[ariaValueNow]="ariaValueNow"',
89
+ '[ariaValueText]="ariaValueText"',
90
+ '[hasTVPreferredFocus]="hasTVPreferredFocus"',
91
+ '[nextFocusDown]="nextFocusDown"',
92
+ '[nextFocusForward]="nextFocusForward"',
93
+ '[nextFocusLeft]="nextFocusLeft"',
94
+ '[nextFocusRight]="nextFocusRight"',
95
+ '[nextFocusUp]="nextFocusUp"',
96
+ ]) {
97
+ expect(buttonSource).toContain(binding);
98
+ }
99
+
100
+ for (const binding of [
101
+ '[accessible]="accessible"',
102
+ '[accessibilityLabelledBy]="accessibilityLabelledBy"',
103
+ '[importantForAccessibility]="importantForAccessibility"',
104
+ '[accessibilityLiveRegion]="accessibilityLiveRegion"',
105
+ '[screenReaderFocusable]="screenReaderFocusable"',
106
+ '[accessibilityViewIsModal]="accessibilityViewIsModal"',
107
+ '[accessibilityElementsHidden]="accessibilityElementsHidden"',
108
+ '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
109
+ '[accessibilityLanguage]="accessibilityLanguage"',
110
+ '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
111
+ '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
112
+ '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
113
+ '(accessibilityAction)="accessibilityAction.emit($event)"',
114
+ '(accessibilityTap)="accessibilityTap.emit($event)"',
115
+ '(magicTap)="magicTap.emit($event)"',
116
+ '(accessibilityEscape)="accessibilityEscape.emit($event)"',
117
+ '[ariaModal]="ariaModal"',
118
+ '[ariaValueMax]="ariaValueMax"',
119
+ '[ariaValueMin]="ariaValueMin"',
120
+ '[ariaValueNow]="ariaValueNow"',
121
+ '[ariaValueText]="ariaValueText"',
122
+ '[hasTVPreferredFocus]="hasTVPreferredFocus"',
123
+ '[nextFocusDown]="nextFocusDown"',
124
+ '[nextFocusForward]="nextFocusForward"',
125
+ '[nextFocusLeft]="nextFocusLeft"',
126
+ '[nextFocusRight]="nextFocusRight"',
127
+ '[nextFocusUp]="nextFocusUp"',
128
+ ]) {
129
+ expect(touchableSource).toContain(binding);
130
+ }
131
+
132
+ // Pressable forwards its resolved props through the shared SymbioteHostPropsDirective
133
+ // (adapters/angular/src/primitives/shared.ts) rather than one `[prop]="x"` binding per
134
+ // key, so the contract to check is: the binding exists, and the `hostProps` getter it
135
+ // reads from actually assembles `accessible` / the folded accessibility bag / TV-focus.
136
+ expect(pressableSource).toContain('[symbioteHostProps]="hostProps"');
137
+ expect(pressableSource).toContain('accessible: this.accessible');
138
+ expect(pressableSource).toContain('...this.foldedAccessibility');
139
+ expect(pressableSource).toContain('hasTVPreferredFocus: this.hasTVPreferredFocus');
140
+ expect(pressableSource).toContain('(accessibilityAction)="emit(accessibilityAction, $event)"');
141
+ });
142
+ });