@symbiote-native/angular 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/build/angular/components/activity-indicator/index.android.d.ts +12 -0
  4. package/build/angular/components/activity-indicator/index.android.js +21 -0
  5. package/build/angular/components/activity-indicator/index.android.js.map +1 -0
  6. package/build/angular/components/activity-indicator/index.d.ts +1 -0
  7. package/build/angular/components/activity-indicator/index.ios.d.ts +8 -0
  8. package/build/angular/components/activity-indicator/index.ios.js +21 -0
  9. package/build/angular/components/activity-indicator/index.ios.js.map +1 -0
  10. package/build/angular/components/activity-indicator/index.js +4 -0
  11. package/build/angular/components/activity-indicator/index.js.map +1 -0
  12. package/build/angular/components/activity-indicator/shared.d.ts +67 -0
  13. package/build/angular/components/activity-indicator/shared.js +260 -0
  14. package/build/angular/components/activity-indicator/shared.js.map +1 -0
  15. package/build/angular/components/button.d.ts +63 -0
  16. package/build/angular/components/button.js +332 -0
  17. package/build/angular/components/button.js.map +1 -0
  18. package/build/angular/components/drawer-layout-android/index.android.d.ts +34 -0
  19. package/build/angular/components/drawer-layout-android/index.android.js +291 -0
  20. package/build/angular/components/drawer-layout-android/index.android.js.map +1 -0
  21. package/build/angular/components/drawer-layout-android/index.d.ts +11 -0
  22. package/build/angular/components/drawer-layout-android/index.js +109 -0
  23. package/build/angular/components/drawer-layout-android/index.js.map +1 -0
  24. package/build/angular/components/drawer-layout-android/shared.d.ts +74 -0
  25. package/build/angular/components/drawer-layout-android/shared.js +138 -0
  26. package/build/angular/components/drawer-layout-android/shared.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 +689 -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 +408 -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.d.ts +59 -0
  46. package/build/angular/components/input-accessory-view.js +266 -0
  47. package/build/angular/components/input-accessory-view.js.map +1 -0
  48. package/build/angular/components/keyboard-avoiding-view.d.ts +82 -0
  49. package/build/angular/components/keyboard-avoiding-view.js +363 -0
  50. package/build/angular/components/keyboard-avoiding-view.js.map +1 -0
  51. package/build/angular/components/modal.d.ts +92 -0
  52. package/build/angular/components/modal.js +365 -0
  53. package/build/angular/components/modal.js.map +1 -0
  54. package/build/angular/components/pressable/index.d.ts +123 -0
  55. package/build/angular/components/pressable/index.js +421 -0
  56. package/build/angular/components/pressable/index.js.map +1 -0
  57. package/build/angular/components/refresh-control.d.ts +86 -0
  58. package/build/angular/components/refresh-control.js +347 -0
  59. package/build/angular/components/refresh-control.js.map +1 -0
  60. package/build/angular/components/safe-area-view.d.ts +57 -0
  61. package/build/angular/components/safe-area-view.js +253 -0
  62. package/build/angular/components/safe-area-view.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 +312 -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 +679 -0
  77. package/build/angular/components/scroll-view/shared.js.map +1 -0
  78. package/build/angular/components/scroll-view/sticky-header.d.ts +41 -0
  79. package/build/angular/components/scroll-view/sticky-header.js +208 -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 +604 -0
  98. package/build/angular/components/text-input.js.map +1 -0
  99. package/build/angular/components/touchable-native-feedback.d.ts +83 -0
  100. package/build/angular/components/touchable-native-feedback.js +376 -0
  101. package/build/angular/components/touchable-native-feedback.js.map +1 -0
  102. package/build/angular/components/touchable.d.ts +248 -0
  103. package/build/angular/components/touchable.js +1206 -0
  104. package/build/angular/components/touchable.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 +154 -0
  107. package/build/angular/components/virtualized-list/directives.js.map +1 -0
  108. package/build/angular/components/virtualized-list/index.d.ts +268 -0
  109. package/build/angular/components/virtualized-list/index.js +1162 -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 +77 -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 +722 -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.d.ts +22 -0
  121. package/build/angular/create-portal.js +74 -0
  122. package/build/angular/create-portal.js.map +1 -0
  123. package/build/angular/create-tunnel.d.ts +34 -0
  124. package/build/angular/create-tunnel.js +126 -0
  125. package/build/angular/create-tunnel.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 +21 -0
  133. package/build/angular/index.js +42 -0
  134. package/build/angular/index.js.map +1 -0
  135. package/build/angular/modules/animated/create-animated-component.d.ts +68 -0
  136. package/build/angular/modules/animated/create-animated-component.js +429 -0
  137. package/build/angular/modules/animated/create-animated-component.js.map +1 -0
  138. package/build/angular/modules/animated/index.d.ts +51 -0
  139. package/build/angular/modules/animated/index.js +59 -0
  140. package/build/angular/modules/animated/index.js.map +1 -0
  141. package/build/angular/modules/app-registry/index.d.ts +7 -0
  142. package/build/angular/modules/app-registry/index.js +23 -0
  143. package/build/angular/modules/app-registry/index.js.map +1 -0
  144. package/build/angular/modules/status-bar.d.ts +20 -0
  145. package/build/angular/modules/status-bar.js +59 -0
  146. package/build/angular/modules/status-bar.js.map +1 -0
  147. package/build/angular/primitives/index.d.ts +63 -0
  148. package/build/angular/primitives/index.js +189 -0
  149. package/build/angular/primitives/index.js.map +1 -0
  150. package/build/angular/primitives/shared.d.ts +89 -0
  151. package/build/angular/primitives/shared.js +172 -0
  152. package/build/angular/primitives/shared.js.map +1 -0
  153. package/build/angular/render.d.ts +11 -0
  154. package/build/angular/render.js +161 -0
  155. package/build/angular/render.js.map +1 -0
  156. package/build/angular/renderer.d.ts +38 -0
  157. package/build/angular/renderer.js +360 -0
  158. package/build/angular/renderer.js.map +1 -0
  159. package/build/angular/services/color-scheme.service.d.ts +16 -0
  160. package/build/angular/services/color-scheme.service.js +31 -0
  161. package/build/angular/services/color-scheme.service.js.map +1 -0
  162. package/build/angular/services/index.d.ts +2 -0
  163. package/build/angular/services/index.js +3 -0
  164. package/build/angular/services/index.js.map +1 -0
  165. package/build/angular/services/window-dimensions.service.d.ts +16 -0
  166. package/build/angular/services/window-dimensions.service.js +42 -0
  167. package/build/angular/services/window-dimensions.service.js.map +1 -0
  168. package/build/components/activity-indicator/index.android.d.ts +9 -0
  169. package/build/components/activity-indicator/index.android.js +64 -0
  170. package/build/components/activity-indicator/index.d.ts +1 -0
  171. package/build/components/activity-indicator/index.ios.d.ts +5 -0
  172. package/build/components/activity-indicator/index.ios.js +64 -0
  173. package/build/components/activity-indicator/index.js +3 -0
  174. package/build/components/activity-indicator/shared.d.ts +64 -0
  175. package/build/components/activity-indicator/shared.js +424 -0
  176. package/build/components/button.d.ts +60 -0
  177. package/build/components/button.js +456 -0
  178. package/build/components/drawer-layout-android/index.android.d.ts +29 -0
  179. package/build/components/drawer-layout-android/index.android.js +326 -0
  180. package/build/components/drawer-layout-android/index.d.ts +8 -0
  181. package/build/components/drawer-layout-android/index.js +145 -0
  182. package/build/components/drawer-layout-android/shared.d.ts +71 -0
  183. package/build/components/drawer-layout-android/shared.js +205 -0
  184. package/build/components/flat-list/index.d.ts +119 -0
  185. package/build/components/flat-list/index.js +692 -0
  186. package/build/components/image/index.android.d.ts +7 -0
  187. package/build/components/image/index.android.js +88 -0
  188. package/build/components/image/index.d.ts +1 -0
  189. package/build/components/image/index.ios.d.ts +7 -0
  190. package/build/components/image/index.ios.js +88 -0
  191. package/build/components/image/index.js +2 -0
  192. package/build/components/image/shared.d.ts +101 -0
  193. package/build/components/image/shared.js +407 -0
  194. package/build/components/image-background.d.ts +78 -0
  195. package/build/components/image-background.js +583 -0
  196. package/build/components/input-accessory-view.d.ts +56 -0
  197. package/build/components/input-accessory-view.js +407 -0
  198. package/build/components/keyboard-avoiding-view.d.ts +79 -0
  199. package/build/components/keyboard-avoiding-view.js +509 -0
  200. package/build/components/modal.d.ts +89 -0
  201. package/build/components/modal.js +534 -0
  202. package/build/components/pressable/index.d.ts +120 -0
  203. package/build/components/pressable/index.js +640 -0
  204. package/build/components/refresh-control.d.ts +83 -0
  205. package/build/components/refresh-control.js +514 -0
  206. package/build/components/safe-area-view.d.ts +54 -0
  207. package/build/components/safe-area-view.js +393 -0
  208. package/build/components/scroll-view/index.android.d.ts +16 -0
  209. package/build/components/scroll-view/index.android.js +228 -0
  210. package/build/components/scroll-view/index.d.ts +3 -0
  211. package/build/components/scroll-view/index.ios.d.ts +6 -0
  212. package/build/components/scroll-view/index.ios.js +113 -0
  213. package/build/components/scroll-view/index.js +5 -0
  214. package/build/components/scroll-view/projection.d.ts +32 -0
  215. package/build/components/scroll-view/projection.js +311 -0
  216. package/build/components/scroll-view/shared.d.ts +225 -0
  217. package/build/components/scroll-view/shared.js +757 -0
  218. package/build/components/scroll-view/sticky-header.d.ts +38 -0
  219. package/build/components/scroll-view/sticky-header.js +257 -0
  220. package/build/components/section-list/index.d.ts +113 -0
  221. package/build/components/section-list/index.js +768 -0
  222. package/build/components/switch/index.android.d.ts +17 -0
  223. package/build/components/switch/index.android.js +107 -0
  224. package/build/components/switch/index.d.ts +1 -0
  225. package/build/components/switch/index.ios.d.ts +16 -0
  226. package/build/components/switch/index.ios.js +106 -0
  227. package/build/components/switch/index.js +3 -0
  228. package/build/components/switch/shared.d.ts +37 -0
  229. package/build/components/switch/shared.js +176 -0
  230. package/build/components/text-input.d.ts +150 -0
  231. package/build/components/text-input.js +826 -0
  232. package/build/components/touchable-native-feedback.d.ts +80 -0
  233. package/build/components/touchable-native-feedback.js +550 -0
  234. package/build/components/touchable.d.ts +241 -0
  235. package/build/components/touchable.js +1672 -0
  236. package/build/components/virtualized-list/directives.d.ts +41 -0
  237. package/build/components/virtualized-list/directives.js +250 -0
  238. package/build/components/virtualized-list/index.d.ts +265 -0
  239. package/build/components/virtualized-list/index.js +1344 -0
  240. package/build/components/virtualized-section-list/directives.d.ts +28 -0
  241. package/build/components/virtualized-section-list/directives.js +149 -0
  242. package/build/components/virtualized-section-list/index.d.ts +175 -0
  243. package/build/components/virtualized-section-list/index.js +854 -0
  244. package/build/components.d.ts +39 -0
  245. package/build/components.js +37 -0
  246. package/build/create-portal.d.ts +17 -0
  247. package/build/create-portal.js +128 -0
  248. package/build/create-tunnel.d.ts +29 -0
  249. package/build/create-tunnel.js +181 -0
  250. package/build/descriptor-to-angular/index.d.ts +19 -0
  251. package/build/descriptor-to-angular/index.js +186 -0
  252. package/build/host-instance/index.d.ts +2 -0
  253. package/build/host-instance/index.js +36 -0
  254. package/build/index.d.ts +21 -0
  255. package/build/index.js +41 -0
  256. package/build/modules/animated/create-animated-component.d.ts +57 -0
  257. package/build/modules/animated/create-animated-component.js +490 -0
  258. package/build/modules/animated/index.d.ts +51 -0
  259. package/build/modules/animated/index.js +58 -0
  260. package/build/modules/app-registry/index.d.ts +7 -0
  261. package/build/modules/app-registry/index.js +22 -0
  262. package/build/modules/status-bar.d.ts +17 -0
  263. package/build/modules/status-bar.js +121 -0
  264. package/build/primitives/index.d.ts +32 -0
  265. package/build/primitives/index.js +386 -0
  266. package/build/primitives/shared.d.ts +84 -0
  267. package/build/primitives/shared.js +231 -0
  268. package/build/render.d.ts +11 -0
  269. package/build/render.js +160 -0
  270. package/build/renderer.d.ts +38 -0
  271. package/build/renderer.js +359 -0
  272. package/build/services/color-scheme.service.d.ts +13 -0
  273. package/build/services/color-scheme.service.js +72 -0
  274. package/build/services/index.d.ts +2 -0
  275. package/build/services/index.js +2 -0
  276. package/build/services/window-dimensions.service.d.ts +13 -0
  277. package/build/services/window-dimensions.service.js +83 -0
  278. package/metro-css-parser.cjs +11 -0
  279. package/package.json +57 -0
  280. package/src/__tests__/responder-change-detection.test.ts +102 -0
  281. package/src/__tests__/responder-nested-cd.test.ts +113 -0
  282. package/src/angular-gaps.test.ts +142 -0
  283. package/src/angular-imports.test.ts +100 -0
  284. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +44 -0
  285. package/src/components/activity-indicator/activity-indicator.test.ts +131 -0
  286. package/src/components/activity-indicator/index.android.ts +16 -0
  287. package/src/components/activity-indicator/index.ios.ts +17 -0
  288. package/src/components/activity-indicator/index.ts +4 -0
  289. package/src/components/activity-indicator/shared.ts +206 -0
  290. package/src/components/button.ts +193 -0
  291. package/src/components/flat-list/column-wrapper-style-class.test.ts +116 -0
  292. package/src/components/flat-list/flat-list-array-style.test.ts +131 -0
  293. package/src/components/flat-list/flat-list-class-style.test.ts +74 -0
  294. package/src/components/flat-list/flat-list-scroll-containment.test.ts +107 -0
  295. package/src/components/flat-list/flat-list.test.ts +99 -0
  296. package/src/components/flat-list/index.ts +575 -0
  297. package/src/components/image/image-class-style.test.ts +46 -0
  298. package/src/components/image/index.android.ts +48 -0
  299. package/src/components/image/index.ios.ts +48 -0
  300. package/src/components/image/index.ts +3 -0
  301. package/src/components/image/shared.ts +478 -0
  302. package/src/components/image-background-class-style.test.ts +48 -0
  303. package/src/components/image-background-style-class.test.ts +79 -0
  304. package/src/components/image-background.ts +270 -0
  305. package/src/components/input-accessory-view.test.ts +43 -0
  306. package/src/components/input-accessory-view.ts +191 -0
  307. package/src/components/keyboard-avoiding-view.test.ts +54 -0
  308. package/src/components/keyboard-avoiding-view.ts +308 -0
  309. package/src/components/modal.test.ts +43 -0
  310. package/src/components/modal.ts +304 -0
  311. package/src/components/pressable/index.ts +371 -0
  312. package/src/components/pressable/pressable.test.ts +85 -0
  313. package/src/components/refresh-control.test.ts +41 -0
  314. package/src/components/refresh-control.ts +288 -0
  315. package/src/components/safe-area-view.test.ts +43 -0
  316. package/src/components/safe-area-view.ts +178 -0
  317. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +123 -0
  318. package/src/components/scroll-view/content-container-style-class.test.ts +75 -0
  319. package/src/components/scroll-view/index.android.ts +190 -0
  320. package/src/components/scroll-view/index.ios.ts +79 -0
  321. package/src/components/scroll-view/index.ts +7 -0
  322. package/src/components/scroll-view/projection.ts +384 -0
  323. package/src/components/scroll-view/scroll-view-class-style.test.ts +134 -0
  324. package/src/components/scroll-view/scroll-view-projection.test.ts +194 -0
  325. package/src/components/scroll-view/shared.ts +836 -0
  326. package/src/components/scroll-view/sticky-header.ts +230 -0
  327. package/src/components/section-list/index.ts +445 -0
  328. package/src/components/switch/android-switch-class.test.ts +42 -0
  329. package/src/components/switch/index.android.ts +61 -0
  330. package/src/components/switch/index.ios.ts +60 -0
  331. package/src/components/switch/index.ts +4 -0
  332. package/src/components/switch/shared.ts +197 -0
  333. package/src/components/switch/switch.test.ts +92 -0
  334. package/src/components/text-input-class-style.test.ts +54 -0
  335. package/src/components/text-input.ts +536 -0
  336. package/src/components/touchable-native-feedback.test.ts +49 -0
  337. package/src/components/touchable-native-feedback.ts +260 -0
  338. package/src/components/touchable.test.ts +102 -0
  339. package/src/components/touchable.ts +643 -0
  340. package/src/components/virtualized-list/directives.ts +169 -0
  341. package/src/components/virtualized-list/index.ts +1204 -0
  342. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +76 -0
  343. package/src/components/virtualized-section-list/directives.ts +91 -0
  344. package/src/components/virtualized-section-list/index.ts +598 -0
  345. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +60 -0
  346. package/src/components.ts +149 -0
  347. package/src/create-portal.test.ts +90 -0
  348. package/src/create-portal.ts +74 -0
  349. package/src/create-tunnel.test.ts +120 -0
  350. package/src/create-tunnel.ts +136 -0
  351. package/src/descriptor-to-angular/descriptor-outlet.test.ts +94 -0
  352. package/src/descriptor-to-angular/index.ts +190 -0
  353. package/src/host-instance/host-instance.test.ts +49 -0
  354. package/src/host-instance/index.ts +36 -0
  355. package/src/index.ts +257 -0
  356. package/src/modules/animated/animated-scroll-view.test.ts +85 -0
  357. package/src/modules/animated/create-animated-component.ts +435 -0
  358. package/src/modules/animated/index.ts +105 -0
  359. package/src/modules/app-registry/app-registry.test.ts +106 -0
  360. package/src/modules/app-registry/index.ts +60 -0
  361. package/src/modules/status-bar.test.ts +50 -0
  362. package/src/modules/status-bar.ts +57 -0
  363. package/src/primitives/index.ts +119 -0
  364. package/src/primitives/shared.ts +191 -0
  365. package/src/render.test.ts +147 -0
  366. package/src/render.ts +224 -0
  367. package/src/renderer.test.ts +179 -0
  368. package/src/renderer.ts +407 -0
  369. package/src/services/color-scheme.service.ts +27 -0
  370. package/src/services/index.ts +2 -0
  371. package/src/services/services.test.ts +163 -0
  372. package/src/services/window-dimensions.service.ts +41 -0
@@ -0,0 +1,206 @@
1
+ import { Directive, ElementRef, EventEmitter, inject, Input, Output } from '@angular/core';
2
+ import {
3
+ renderActivityIndicator,
4
+ resolveAccessibilityProps,
5
+ type IAccessibilityProps,
6
+ type IAccessibilityStateValue,
7
+ type IActivityIndicatorPlatform,
8
+ type IActivityIndicatorProps,
9
+ type IActivityIndicatorSize,
10
+ type IAriaProps,
11
+ } from '@symbiote-native/components';
12
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
13
+ import type { IDescriptor } from '@symbiote-native/components';
14
+ import { anchorHostStyle } from '../../primitives';
15
+
16
+ export type { IActivityIndicatorProps, IActivityIndicatorSize } from '@symbiote-native/components';
17
+
18
+ // Narrows anchorHostStyle's `unknown` (it reads an opaque engine prop bag) to the shape
19
+ // IActivityIndicatorProps['style'] actually declares, the same runtime guard image/shared.ts's
20
+ // asStyle() uses for the identical reason — a style value is structurally opaque at the type
21
+ // level, so this only rules out non-objects, never validates individual style keys.
22
+ function asStyle(value: unknown): IActivityIndicatorProps['style'] {
23
+ return typeof value === 'object' && value !== null ? value : undefined;
24
+ }
25
+
26
+ // What the component itself takes as plain @Input()s: the full shared, cross-adapter
27
+ // IActivityIndicatorProps surface minus onLayout and the accessibility callbacks, which it
28
+ // exposes as real @Output() EventEmitters instead (see the class below), mirroring Pressable's
29
+ // IAngularPressableInputs split.
30
+ export type IActivityIndicatorInputs = Omit<
31
+ IActivityIndicatorProps,
32
+ | 'onLayout'
33
+ | 'onAccessibilityAction'
34
+ | 'onAccessibilityTap'
35
+ | 'onMagicTap'
36
+ | 'onAccessibilityEscape'
37
+ >;
38
+
39
+ @Directive()
40
+ export abstract class ActivityIndicatorBase implements IActivityIndicatorInputs {
41
+ @Input() animating?: boolean;
42
+ @Input() color?: string;
43
+ @Input() hidesWhenStopped?: boolean;
44
+ @Input() nativeID?: string;
45
+ @Input() size?: IActivityIndicatorSize;
46
+ @Input() style?: IActivityIndicatorProps['style'];
47
+ @Input() testID?: string;
48
+ // Real @Output()s. This component renders through DescriptorOutlet (`symbiote-descriptor-
49
+ // outlet`), not a hand-written template: emitterHandler() below wraps each EventEmitter into a
50
+ // plain callback under the SAME onX key the shared, cross-adapter IActivityIndicatorProps
51
+ // expects, so the Descriptor's props bag still carries a function the engine's routeProp
52
+ // recognizes and routes to setEventListener — the outlet's renderer.setProperty call never
53
+ // sees an EventEmitter, only the wrapper.
54
+ @Output() readonly layout = new EventEmitter<ISymbioteEvent>();
55
+
56
+ @Input() accessible?: boolean;
57
+ @Input() accessibilityLabel?: string;
58
+ @Input() accessibilityHint?: string;
59
+ @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
60
+ @Input() accessibilityState?: IAccessibilityStateValue;
61
+ @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
62
+ @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
63
+ @Input() accessibilityLabelledBy?: string | string[];
64
+ @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
65
+ @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
66
+ @Input() screenReaderFocusable?: boolean;
67
+ @Input() accessibilityViewIsModal?: boolean;
68
+ @Input() accessibilityElementsHidden?: boolean;
69
+ @Input() accessibilityIgnoresInvertColors?: boolean;
70
+ @Input() accessibilityLanguage?: string;
71
+ @Input() accessibilityRespondsToUserInteraction?: boolean;
72
+ @Input() accessibilityShowsLargeContentViewer?: boolean;
73
+ @Input() accessibilityLargeContentTitle?: string;
74
+ @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
75
+ @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
76
+ @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
77
+ @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
78
+
79
+ @Input() role?: IAriaProps['role'];
80
+ @Input('aria-label') ariaLabel?: string;
81
+ @Input('aria-labelledby') ariaLabelledBy?: string;
82
+ @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
83
+ @Input('aria-hidden') ariaHidden?: boolean;
84
+ @Input('aria-busy') ariaBusy?: boolean;
85
+ @Input('aria-checked') ariaChecked?: boolean | 'mixed';
86
+ @Input('aria-disabled') ariaDisabled?: boolean;
87
+ @Input('aria-expanded') ariaExpanded?: boolean;
88
+ @Input('aria-selected') ariaSelected?: boolean;
89
+ @Input('aria-modal') ariaModal?: boolean;
90
+ @Input('aria-valuemax') ariaValueMax?: number;
91
+ @Input('aria-valuemin') ariaValueMin?: number;
92
+ @Input('aria-valuenow') ariaValueNow?: number;
93
+ @Input('aria-valuetext') ariaValueText?: string;
94
+
95
+ protected abstract readonly defaultColor: string | null;
96
+ protected readonly nativeExtras: Readonly<Record<string, unknown>> = {};
97
+
98
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
99
+ // onto (see anchorHostStyle's doc comment) — NOT the descriptor-outlet-rendered wrapper the
100
+ // `descriptor` getter below builds. Merged FIRST so an explicit `style` @Input still wins
101
+ // (flattenStyle's later-wins collapse), mirroring every other composed component's anchor merge.
102
+ private readonly elementRef = inject(ElementRef);
103
+
104
+ // Stable bound wrappers (created ONCE, not per `descriptor` access) fed to emitterHandler below.
105
+ // jsonEqual (core/engine/src/commit.ts) falls through to Object.is for a function leaf, so a
106
+ // fresh closure built inside the `descriptor` getter would never equal the previous frame's
107
+ // closure even when nothing changed — every CD pass would read as "props changed" and force a
108
+ // real Fabric re-clone cascading up every ancestor to the root (the same hazard
109
+ // AnimatedComponentBase.reconcile() and ScrollViewStickyHeader's onHostLayout guard against).
110
+ private readonly onLayoutHandler = (event: ISymbioteEvent): void => this.layout.emit(event);
111
+ private readonly onAccessibilityActionHandler = (event: ISymbioteEvent): void =>
112
+ this.accessibilityAction.emit(event);
113
+ private readonly onAccessibilityTapHandler = (event: ISymbioteEvent): void =>
114
+ this.accessibilityTap.emit(event);
115
+ private readonly onMagicTapHandler = (event: ISymbioteEvent): void => this.magicTap.emit(event);
116
+ private readonly onAccessibilityEscapeHandler = (event: ISymbioteEvent): void =>
117
+ this.accessibilityEscape.emit(event);
118
+
119
+ get descriptor(): IDescriptor {
120
+ const props = resolveAccessibilityProps<IActivityIndicatorProps>({
121
+ animating: this.animating,
122
+ color: this.color,
123
+ hidesWhenStopped: this.hidesWhenStopped,
124
+ nativeID: this.nativeID,
125
+ size: this.size,
126
+ style: [asStyle(anchorHostStyle(this.elementRef)), this.style],
127
+ testID: this.testID,
128
+ onLayout: this.emitterHandler(this.layout, this.onLayoutHandler),
129
+ accessible: this.accessible,
130
+ accessibilityLabel: this.accessibilityLabel,
131
+ accessibilityHint: this.accessibilityHint,
132
+ accessibilityRole: this.accessibilityRole,
133
+ accessibilityState: this.accessibilityState,
134
+ accessibilityValue: this.accessibilityValue,
135
+ accessibilityActions: this.accessibilityActions,
136
+ accessibilityLabelledBy: this.accessibilityLabelledBy,
137
+ importantForAccessibility: this.importantForAccessibility,
138
+ accessibilityLiveRegion: this.accessibilityLiveRegion,
139
+ screenReaderFocusable: this.screenReaderFocusable,
140
+ accessibilityViewIsModal: this.accessibilityViewIsModal,
141
+ accessibilityElementsHidden: this.accessibilityElementsHidden,
142
+ accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
143
+ accessibilityLanguage: this.accessibilityLanguage,
144
+ accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
145
+ accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
146
+ accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
147
+ onAccessibilityAction: this.emitterHandler(
148
+ this.accessibilityAction,
149
+ this.onAccessibilityActionHandler,
150
+ ),
151
+ onAccessibilityTap: this.emitterHandler(
152
+ this.accessibilityTap,
153
+ this.onAccessibilityTapHandler,
154
+ ),
155
+ onMagicTap: this.emitterHandler(this.magicTap, this.onMagicTapHandler),
156
+ onAccessibilityEscape: this.emitterHandler(
157
+ this.accessibilityEscape,
158
+ this.onAccessibilityEscapeHandler,
159
+ ),
160
+ role: this.role,
161
+ 'aria-label': this.ariaLabel,
162
+ 'aria-labelledby': this.ariaLabelledBy,
163
+ 'aria-live': this.ariaLive,
164
+ 'aria-hidden': this.ariaHidden,
165
+ 'aria-busy': this.ariaBusy,
166
+ 'aria-checked': this.ariaChecked,
167
+ 'aria-disabled': this.ariaDisabled,
168
+ 'aria-expanded': this.ariaExpanded,
169
+ 'aria-selected': this.ariaSelected,
170
+ 'aria-modal': this.ariaModal,
171
+ 'aria-valuemax': this.ariaValueMax,
172
+ 'aria-valuemin': this.ariaValueMin,
173
+ 'aria-valuenow': this.ariaValueNow,
174
+ 'aria-valuetext': this.ariaValueText,
175
+ });
176
+
177
+ const { animating, color, hidesWhenStopped, size, style, ...passthrough } = props;
178
+ return renderActivityIndicator(
179
+ {
180
+ animating: animating !== false,
181
+ color,
182
+ hidesWhenStopped: hidesWhenStopped !== false,
183
+ size: size ?? 'small',
184
+ style,
185
+ passthrough,
186
+ },
187
+ this.platform,
188
+ );
189
+ }
190
+
191
+ private get platform(): IActivityIndicatorPlatform {
192
+ return { defaultColor: this.defaultColor, nativeExtras: this.nativeExtras };
193
+ }
194
+
195
+ // Gates a STABLE bound handler behind `observed`, so an unbound event still resolves to
196
+ // `undefined` in the descriptor's props bag — the same "undefined means nobody cares" contract
197
+ // the old @Input() callback had, letting resolveAccessibilityProps drop the key entirely instead
198
+ // of handing the engine a no-op function. `handler` must be one of the readonly bound fields
199
+ // above, never a closure built here — see their doc comment for why.
200
+ private emitterHandler(
201
+ emitter: EventEmitter<ISymbioteEvent>,
202
+ handler: (event: ISymbioteEvent) => void,
203
+ ): ((event: ISymbioteEvent) => void) | undefined {
204
+ return emitter.observed ? handler : undefined;
205
+ }
206
+ }
@@ -0,0 +1,193 @@
1
+ // Button for Angular. The minimal cross-platform button in RN's iOS shape (Button.js): a
2
+ // TouchableOpacity wrapping a Text. The base text style, the role constant, and the color fold are
3
+ // shared in @symbiote-native/components/view; here Angular only composes its TouchableOpacity + a
4
+ // symbiote-text child and re-maps the few Button-owned props. The Angular twin of the React/Vue
5
+ // adapter's Button. No JS-side platform branch, so this stays a flat single file (ADR 0026).
6
+ //
7
+ // RN's Button fixes accessibilityRole="button", marks the root accessible, and propagates `disabled`
8
+ // into the accessibility state; those three win over any caller value. `title` becomes the Text
9
+ // child; disabled forwards straight; touchSoundDisabled maps to the pressable's android_disableSound.
10
+ // Every OTHER field of the shared IButtonProps (title, color, disabled, touchSoundDisabled, testID,
11
+ // TV-focus, accessibility state) is agnostic and stays RE-EXPORTED from @symbiote-native/components verbatim
12
+ // (<prop_types_split_agnostic_vs_per_adapter>). onPress and the four accessibility callbacks are the
13
+ // exceptions: each is a real @Output() here (`press`, `accessibilityAction`, `accessibilityTap`,
14
+ // `magicTap`, `accessibilityEscape`), mirroring the Vue adapter's Button, which forks the SAME onPress
15
+ // field the same way (`Omit<ICoreButtonProps, 'onPress'>` + a `press` emit) — not a new precedent, the
16
+ // existing one extended to accessibility.
17
+
18
+ import {
19
+ CUSTOM_ELEMENTS_SCHEMA,
20
+ ChangeDetectionStrategy,
21
+ Component,
22
+ ElementRef,
23
+ EventEmitter,
24
+ inject,
25
+ Input,
26
+ Output,
27
+ } from '@angular/core';
28
+ import {
29
+ BUTTON_ACCESSIBILITY_ROLE,
30
+ resolveButtonTextStyle,
31
+ type IAccessibilityProps,
32
+ type IAccessibilityStateValue,
33
+ type IAriaProps,
34
+ type IButtonProps as ICoreButtonProps,
35
+ } from '@symbiote-native/components';
36
+ import type { ISymbioteEvent, ITextStyle, IStyleProp, IViewStyle } from '@symbiote-native/engine';
37
+ import { anchorStyleProp, TextHost } from '../primitives';
38
+ import { TouchableOpacity } from './touchable';
39
+
40
+ export type IButtonProps = Omit<
41
+ ICoreButtonProps,
42
+ | 'onPress'
43
+ | 'onAccessibilityAction'
44
+ | 'onAccessibilityTap'
45
+ | 'onMagicTap'
46
+ | 'onAccessibilityEscape'
47
+ >;
48
+
49
+ @Component({
50
+ selector: 'Button',
51
+ standalone: true,
52
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
53
+ imports: [TouchableOpacity, TextHost],
54
+ changeDetection: ChangeDetectionStrategy.OnPush,
55
+ template: `
56
+ <TouchableOpacity
57
+ [style]="anchorStyle"
58
+ (press)="press.emit($event)"
59
+ [disabled]="disabled"
60
+ [android_disableSound]="touchSoundDisabled"
61
+ [accessibilityRole]="buttonRole"
62
+ [accessible]="true"
63
+ [accessibilityState]="{ disabled }"
64
+ [testID]="testID"
65
+ [nativeID]="nativeID"
66
+ [hasTVPreferredFocus]="hasTVPreferredFocus"
67
+ [nextFocusDown]="nextFocusDown"
68
+ [nextFocusForward]="nextFocusForward"
69
+ [nextFocusLeft]="nextFocusLeft"
70
+ [nextFocusRight]="nextFocusRight"
71
+ [nextFocusUp]="nextFocusUp"
72
+ [accessibilityLabel]="accessibilityLabel"
73
+ [accessibilityHint]="accessibilityHint"
74
+ [accessibilityValue]="accessibilityValue"
75
+ [accessibilityActions]="accessibilityActions"
76
+ [accessibilityLabelledBy]="accessibilityLabelledBy"
77
+ [importantForAccessibility]="importantForAccessibility"
78
+ [accessibilityLiveRegion]="accessibilityLiveRegion"
79
+ [screenReaderFocusable]="screenReaderFocusable"
80
+ [accessibilityViewIsModal]="accessibilityViewIsModal"
81
+ [accessibilityElementsHidden]="accessibilityElementsHidden"
82
+ [accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
83
+ [accessibilityLanguage]="accessibilityLanguage"
84
+ [accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"
85
+ [accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"
86
+ [accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
87
+ (accessibilityAction)="accessibilityAction.emit($event)"
88
+ (accessibilityTap)="accessibilityTap.emit($event)"
89
+ (magicTap)="magicTap.emit($event)"
90
+ (accessibilityEscape)="accessibilityEscape.emit($event)"
91
+ [ariaLabel]="ariaLabel"
92
+ [ariaBusy]="ariaBusy"
93
+ [ariaChecked]="ariaChecked"
94
+ [ariaDisabled]="ariaDisabled"
95
+ [ariaExpanded]="ariaExpanded"
96
+ [ariaHidden]="ariaHidden"
97
+ [ariaLabelledBy]="ariaLabelledBy"
98
+ [ariaLive]="ariaLive"
99
+ [ariaSelected]="ariaSelected"
100
+ [ariaModal]="ariaModal"
101
+ [ariaValueMax]="ariaValueMax"
102
+ [ariaValueMin]="ariaValueMin"
103
+ [ariaValueNow]="ariaValueNow"
104
+ [ariaValueText]="ariaValueText"
105
+ [role]="role"
106
+ >
107
+ <symbiote-text [style]="textStyle">{{ title }}</symbiote-text>
108
+ </TouchableOpacity>
109
+ `,
110
+ })
111
+ export class Button implements IButtonProps {
112
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
113
+ // onto (see anchorHostStyle's doc comment in primitives/shared.ts) — NOT the inner
114
+ // TouchableOpacity/Pressable one level down. RN's real Button takes no `style` prop, so this
115
+ // is the only style source forwarded.
116
+ private readonly elementRef = inject(ElementRef);
117
+
118
+ @Output() readonly press = new EventEmitter<ISymbioteEvent>();
119
+ @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
120
+ @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
121
+ @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
122
+ @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
123
+
124
+ // Button-owned props: title is the Text child, color/disabled fold into the label style, and
125
+ // touchSoundDisabled re-maps onto TouchableOpacity.
126
+ @Input() title = '';
127
+ @Input() color?: string;
128
+ @Input() disabled?: boolean;
129
+ @Input() touchSoundDisabled?: boolean;
130
+ @Input() testID?: string;
131
+
132
+ // TV-focus props (Button.js): inert on a phone host, forwarded through TouchableOpacity.
133
+ @Input() hasTVPreferredFocus?: boolean;
134
+ @Input() nextFocusDown?: number;
135
+ @Input() nextFocusForward?: number;
136
+ @Input() nextFocusLeft?: number;
137
+ @Input() nextFocusRight?: number;
138
+ @Input() nextFocusUp?: number;
139
+
140
+ // Accessibility surface (IAccessibilityProps). accessibilityRole, accessible, and
141
+ // accessibilityState are declared for surface completeness but Button forces its own
142
+ // (button / true / { disabled }); the caller value is ignored, mirroring RN's Button.js.
143
+ @Input() nativeID?: string;
144
+ @Input() accessible?: boolean;
145
+ @Input() accessibilityLabel?: string;
146
+ @Input() accessibilityHint?: string;
147
+ @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
148
+ @Input() accessibilityState?: IAccessibilityStateValue;
149
+ @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
150
+ @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
151
+ @Input() accessibilityLabelledBy?: string | string[];
152
+ @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
153
+ @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
154
+ @Input() screenReaderFocusable?: boolean;
155
+ @Input() accessibilityViewIsModal?: boolean;
156
+ @Input() accessibilityElementsHidden?: boolean;
157
+ @Input() accessibilityIgnoresInvertColors?: boolean;
158
+ @Input() accessibilityLanguage?: string;
159
+ @Input() accessibilityRespondsToUserInteraction?: boolean;
160
+ @Input() accessibilityShowsLargeContentViewer?: boolean;
161
+ @Input() accessibilityLargeContentTitle?: string;
162
+
163
+ // Web-alias a11y surface (IAriaProps). External dashed aliases are exposed as Angular inputs and
164
+ // forwarded to TouchableOpacity's camelCase aria inputs.
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
+ // RN's Button is always accessibilityRole="button"; the shared role constant, not a literal.
182
+ readonly buttonRole = BUTTON_ACCESSIBILITY_ROLE;
183
+
184
+ // The label style with the caller color tinted in and `disabled` greying it out (disabled wins),
185
+ // computed by the shared fold so every adapter paints the identical button.
186
+ get textStyle(): ITextStyle {
187
+ return resolveButtonTextStyle(this.color, this.disabled);
188
+ }
189
+
190
+ get anchorStyle(): IStyleProp<IViewStyle> | undefined {
191
+ return anchorStyleProp<IViewStyle>(this.elementRef);
192
+ }
193
+ }
@@ -0,0 +1,116 @@
1
+ // Regression guard: columnWrapperStyle previously accepted ONLY a JS style object/array. It now
2
+ // also resolves a class-name string through the shared style registry, merged onto the same
3
+ // flex-row wrapper as an object/array value (see index.ts's ngOnChanges). Mirrors the Vue twin
4
+ // (column-wrapper-style-class.test.ts) and flat-list.test.ts's row-packing shape.
5
+ import '@angular/compiler';
6
+ import { Component } from '@angular/core';
7
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
+ import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
9
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
10
+
11
+ import { mount, unmount } from '../../render';
12
+ import { FlatList } from './index';
13
+ import { VListItemDirective } from '../virtualized-list/directives';
14
+
15
+ const ROOT_TAG = 953;
16
+ const fabric = installFabric();
17
+ const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
18
+
19
+ interface IRow {
20
+ id: string;
21
+ label: string;
22
+ }
23
+
24
+ const rows: IRow[] = Array.from({ length: 4 }, (_unused, index) => ({
25
+ id: `r-${index}`,
26
+ label: `row-${index}`,
27
+ }));
28
+
29
+ function walk(nodes: IFakeNode[], visit: (node: IFakeNode) => void): void {
30
+ for (const node of nodes) {
31
+ visit(node);
32
+ walk(node.children, visit);
33
+ }
34
+ }
35
+
36
+ function rowsWithFlexDirection(): IFakeNode[] {
37
+ const found: IFakeNode[] = [];
38
+ walk(fabric.committed, node => {
39
+ if (node.viewName === 'RCTView' && node.props.flexDirection === 'row') found.push(node);
40
+ });
41
+ return found;
42
+ }
43
+
44
+ @Component({
45
+ selector: 'symbiote-flatlist-column-style-class-host',
46
+ standalone: true,
47
+ imports: [FlatList, VListItemDirective],
48
+ template: `
49
+ <FlatList
50
+ [data]="rows"
51
+ [numColumns]="2"
52
+ [keyExtractor]="keyExtractor"
53
+ [columnWrapperStyle]="'gap8'"
54
+ >
55
+ <ng-template vListItem let-item>
56
+ <symbiote-text [testID]="item.id">{{ item.label }}</symbiote-text>
57
+ </ng-template>
58
+ </FlatList>
59
+ `,
60
+ })
61
+ class FlatListColumnStyleClassHost {
62
+ rows = rows;
63
+ keyExtractor = (item: IRow): string => item.id;
64
+ }
65
+
66
+ @Component({
67
+ selector: 'symbiote-flatlist-column-style-object-host',
68
+ standalone: true,
69
+ imports: [FlatList, VListItemDirective],
70
+ template: `
71
+ <FlatList
72
+ [data]="rows"
73
+ [numColumns]="2"
74
+ [keyExtractor]="keyExtractor"
75
+ [columnWrapperStyle]="{ gap: 4 }"
76
+ >
77
+ <ng-template vListItem let-item>
78
+ <symbiote-text [testID]="item.id">{{ item.label }}</symbiote-text>
79
+ </ng-template>
80
+ </FlatList>
81
+ `,
82
+ })
83
+ class FlatListColumnStyleObjectHost {
84
+ rows = rows;
85
+ keyExtractor = (item: IRow): string => item.id;
86
+ }
87
+
88
+ beforeEach(() => {
89
+ fabric.reset();
90
+ registerStyles({ gap8: { gap: 8 } });
91
+ });
92
+ afterEach(() => {
93
+ unmount(ROOT_TAG);
94
+ clearGlobalStyles();
95
+ });
96
+
97
+ describe('FlatList columnWrapperStyle class-name support', () => {
98
+ it('resolves a class-name string onto the row wrapper alongside flexDirection', async () => {
99
+ mount(ROOT_TAG, FlatListColumnStyleClassHost);
100
+ await tick();
101
+ await tick();
102
+
103
+ const found = rowsWithFlexDirection();
104
+ expect(found.length, 'two flex-row rows for 4 items in 2 columns').toBe(2);
105
+ expect(found[0].props.gap).toBe(8);
106
+ });
107
+
108
+ it('still accepts an ordinary style object unchanged', async () => {
109
+ mount(ROOT_TAG, FlatListColumnStyleObjectHost);
110
+ await tick();
111
+ await tick();
112
+
113
+ const found = rowsWithFlexDirection();
114
+ expect(found[0].props.gap).toBe(4);
115
+ });
116
+ });
@@ -0,0 +1,131 @@
1
+ import '@angular/compiler';
2
+ import { Component } from '@angular/core';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+ import { installFabric } from '@symbiote-native/test-utils';
5
+ import { flattenStyle } from '@symbiote-native/engine';
6
+
7
+ import { mount, unmount } from '../../render';
8
+ import { FlatList } from './index';
9
+ import { VListItemDirective } from '../virtualized-list/directives';
10
+
11
+ const ROOT_TAG = 905;
12
+ const fabric = installFabric();
13
+
14
+ interface IChip {
15
+ id: string;
16
+ n: number;
17
+ }
18
+
19
+ const chips: IChip[] = Array.from({ length: 24 }, (_unused, index) => ({
20
+ id: `c-${index}`,
21
+ n: index,
22
+ }));
23
+
24
+ // Regression coverage for a real device bug: Angular compiles a literal `[style]="…"` binding to
25
+ // the built-in ɵɵstyleMap instruction (NOT a regular @Input() property write), regardless of the
26
+ // target's declared type — it only understands a flat style object, never an array. RN's own
27
+ // `style={[base, override]}` composition idiom (used by examples/angular/App.ts's "FlatList · 24
28
+ // chips" demo for the per-item computed backgroundColor) crashed deep inside Angular's own styling
29
+ // engine (`prop.indexOf is not a function`), and the crash landed inside a zoneless
30
+ // SymbioteChangeDetectionScheduler tick with no catch around it — so the retry kept re-firing
31
+ // forever (RAM climbing, constant re-render log spam on device) while the item never painted its
32
+ // style. Fixed by flattening any array-composed style with the engine's own `flattenStyle` before
33
+ // it reaches a `[style]=` binding — required of app authors (see App.ts) AND of every adapter
34
+ // component that forwards its own `style` @Input() onward (see the `resolvedStyle` getters/fields
35
+ // in virtualized-list, section-list, virtualized-section-list — flat-list's own `rowStyle` had the
36
+ // identical landmine, always assigning an array). See the angular-adapter skill for the full story.
37
+ @Component({
38
+ selector: 'symbiote-chip-list-host',
39
+ standalone: true,
40
+ imports: [FlatList, VListItemDirective],
41
+ template: `
42
+ <FlatList
43
+ testID="chips-list"
44
+ [data]="chips"
45
+ [horizontal]="true"
46
+ [keyExtractor]="keyExtractor"
47
+ [getItemLayout]="getItemLayout"
48
+ [style]="{ height: 84 }"
49
+ >
50
+ <ng-template vListItem let-item>
51
+ <symbiote-view
52
+ [testID]="'chip-' + item.n"
53
+ [style]="flattenStyle([{ height: 72, width: 64 }, { backgroundColor: chipColor(item) }])"
54
+ >
55
+ <symbiote-text>{{ item.n }}</symbiote-text>
56
+ </symbiote-view>
57
+ </ng-template>
58
+ </FlatList>
59
+ `,
60
+ })
61
+ class ChipListHost {
62
+ chips = chips;
63
+ flattenStyle = flattenStyle;
64
+ keyExtractor = (item: IChip): string => item.id;
65
+ getItemLayout = (
66
+ _data: unknown,
67
+ index: number,
68
+ ): { length: number; offset: number; index: number } => ({
69
+ length: 64,
70
+ offset: 64 * index,
71
+ index,
72
+ });
73
+ chipColor(item: IChip): string {
74
+ return `hsl(${(item.n * 47) % 360}, 70%, 50%)`;
75
+ }
76
+ }
77
+
78
+ beforeEach(() => fabric.reset());
79
+ afterEach(() => unmount(ROOT_TAG));
80
+
81
+ function findCommitted(
82
+ nodes: typeof fabric.committed,
83
+ predicate: (n: (typeof nodes)[number]) => boolean,
84
+ ): (typeof nodes)[number] | undefined {
85
+ for (const node of nodes) {
86
+ if (predicate(node)) return node;
87
+ const found = findCommitted(node.children, predicate);
88
+ if (found !== undefined) return found;
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ describe('FlatList array-composed item style', () => {
94
+ it('flattens to a plain object instead of crashing ɵɵstyleMap, and commits it', async () => {
95
+ mount(ROOT_TAG, ChipListHost);
96
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
97
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
98
+
99
+ const chip0 = findCommitted(fabric.committed, node => node.props.testID === 'chip-0');
100
+ expect(chip0?.props).toMatchObject({
101
+ height: 72,
102
+ width: 64,
103
+ backgroundColor: 'hsl(0, 70%, 50%)',
104
+ });
105
+ });
106
+
107
+ it('does not free-run change detection once the window settles', async () => {
108
+ mount(ROOT_TAG, ChipListHost);
109
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
110
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
111
+
112
+ const scroll = fabric.find(node => node.viewName === 'RCTScrollView');
113
+ if (scroll !== undefined) {
114
+ fabric.fireEvent(scroll.instanceHandle, 'topLayout', {
115
+ layout: { x: 0, y: 0, width: 360, height: 84 },
116
+ });
117
+ }
118
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
119
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
120
+
121
+ let previous = fabric.counts.completeRoot;
122
+ let stillGrowing = false;
123
+ for (let tick = 0; tick < 10; tick += 1) {
124
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
125
+ const current = fabric.counts.completeRoot;
126
+ if (current > previous) stillGrowing = true;
127
+ previous = current;
128
+ }
129
+ expect(stillGrowing).toBe(false);
130
+ });
131
+ });