@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,575 @@
1
+ // FlatList, the Angular convenience surface over VirtualizedList. It takes a plain `data` array
2
+ // and derives getItem/getItemCount; numColumns packs items into rows (each row a flex-row of N
3
+ // columns), so the virtualized stream is rows, not items (RN's FlatList). All windowing /
4
+ // viewability / batching / imperative scrolling are inherited from VirtualizedList; the data
5
+ // shaping (chunkIntoRows / rowKeyExtractor / expandRowViewability / row-separator unwrap) is shared
6
+ // verbatim from @symbiote-native/components, exactly as the React and Vue FlatLists reuse it. This is the
7
+ // Angular twin of adapters/vue/src/components/flat-list/index.ts.
8
+ //
9
+ // TEMPLATE FORWARDING — both paths RE-STAMP; neither uses a bare `<ng-content>` passthrough.
10
+ // Angular's @ContentChild does NOT resolve a directive across a SECOND `<ng-content>` re-projection
11
+ // hop (it only sees what was projected directly onto the querying component's own tag) — a bare
12
+ // `<VirtualizedList ...><ng-content></ng-content></VirtualizedList>` here left VirtualizedList's own
13
+ // itemDir/headerDir/etc. undefined, so every cell rendered empty (a real, confirmed device bug — see
14
+ // flat-list.test.ts). Fixed for both column modes the same way:
15
+ // * Single column (numColumns <= 1): FlatList captures the app's `<ng-template vListItem>` (and
16
+ // vListHeader/vListFooter/vListEmpty/vListSeparator) with its OWN @ContentChild — a single,
17
+ // direct projection hop, which always resolves — then re-authors equivalent `<ng-template>`s on
18
+ // `<VirtualizedList>`, each forwarding the captured templateRef + context through
19
+ // VListOutletDirective. Item/index/separators pass through 1:1 (no row wrapping).
20
+ // * Multi column (numColumns > 1): the same re-stamp, but the app's vListItem is typed for ItemT
21
+ // while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work even if
22
+ // the projection issue didn't exist. The row vListItem lays out the N columns side by side (each
23
+ // cell stamps the app's item template via VListOutletDirective with the per-item context {item,
24
+ // index: row.startIndex + column, separators}), the row vListSeparator unwraps the flanking rows
25
+ // to their last/first item, and onViewableItemsChanged is wrapped with expandRowViewability so the
26
+ // caller still sees per-item visibility. This mirrors exactly how Vue's FlatList intercepts
27
+ // renderItem with renderRow.
28
+ //
29
+ // The imperative handle (scrollToIndex / scrollToOffset / scrollToEnd / scrollToItem /
30
+ // recordInteraction / flashScrollIndicators / getScroll* / getNativeScrollRef) is RN's FlatList
31
+ // surface; FlatList re-exposes it by delegating to the inner VirtualizedList (@ViewChild). The
32
+ // element-returning props (renderItem / ItemSeparatorComponent / List{Header,Footer,Empty}Component)
33
+ // are templates in Angular, so they are absent from IFlatListProps, per the per-adapter
34
+ // children/render split of <prop_types_split_agnostic_vs_per_adapter>; everything agnostic mirrors
35
+ // IVirtualizedListProps.
36
+
37
+ import {
38
+ CUSTOM_ELEMENTS_SCHEMA,
39
+ ChangeDetectionStrategy,
40
+ Component,
41
+ ContentChild,
42
+ ElementRef,
43
+ EventEmitter,
44
+ Input,
45
+ Output,
46
+ ViewChild,
47
+ inject,
48
+ type OnChanges,
49
+ type SimpleChanges,
50
+ } from '@angular/core';
51
+ import {
52
+ SINGLE_COLUMN,
53
+ chunkIntoRows,
54
+ expandRowViewability,
55
+ firstItemOfRow,
56
+ lastItemOfRow,
57
+ rowKeyExtractor,
58
+ type IRow,
59
+ type IScrollViewHandle,
60
+ type ISeparators,
61
+ type IViewabilityConfigCallbackPair,
62
+ type IViewableItemsChangedInfo,
63
+ type IVirtualizedListHandle,
64
+ } from '@symbiote-native/components';
65
+ import {
66
+ dlog,
67
+ flattenStyle,
68
+ resolveClassName,
69
+ type IStyleProp,
70
+ type ISymbioteNode,
71
+ type IViewStyle,
72
+ } from '@symbiote-native/engine';
73
+ import {
74
+ VirtualizedList,
75
+ VListEmptyDirective,
76
+ VListFooterDirective,
77
+ VListHeaderDirective,
78
+ VListItemDirective,
79
+ VListSeparatorDirective,
80
+ type IVirtualizedListProps,
81
+ type IVListItemContext,
82
+ type IVListSeparatorContext,
83
+ } from '../virtualized-list';
84
+ import { VListOutletDirective } from '../virtualized-list/directives';
85
+ import { stableAnchorStyle, ViewHost } from '../../primitives';
86
+
87
+ function isRecord(value: unknown): value is Record<string, unknown> {
88
+ return typeof value === 'object' && value !== null;
89
+ }
90
+ function isRow<ItemT>(value: unknown): value is IRow<ItemT> {
91
+ return (
92
+ isRecord(value) && Array.isArray(value['items']) && typeof value['startIndex'] === 'number'
93
+ );
94
+ }
95
+ function isSeparators(value: unknown): value is ISeparators {
96
+ return (
97
+ isRecord(value) &&
98
+ typeof value['highlight'] === 'function' &&
99
+ typeof value['unhighlight'] === 'function'
100
+ );
101
+ }
102
+ // Used only as a type-safe fallback if a row's separators handle is ever absent; VirtualizedList
103
+ // always supplies a real one, so in practice the live handle flows through.
104
+ const NOOP_SEPARATORS: ISeparators = {
105
+ highlight: (): void => undefined,
106
+ unhighlight: (): void => undefined,
107
+ updateProps: (): void => undefined,
108
+ };
109
+
110
+ // Angular cannot preserve VListSeparatorDirective<ItemT>'s type parameter across this re-stamp
111
+ // reuse (the directive is matched structurally in FlatList's own template with no explicit type
112
+ // argument to pin ItemT to, so `let-leadingItem`/`let-trailingItem` arrive typed `unknown`) — even
113
+ // though VirtualizedList's own buildSeparatorContext always supplies a real ItemT value. The
114
+ // narrowest possible I/O-boundary cast for that gap; not a general-purpose unknown-to-T narrowing.
115
+ function asItem<ItemT>(value: unknown): ItemT | undefined {
116
+ return value as ItemT | undefined;
117
+ }
118
+
119
+ // Re-export the shared list types + the authoring directives so the app can import the cell/slot
120
+ // directives alongside FlatList (mirrors how virtualized-list/index re-exports them).
121
+ export type {
122
+ ISeparators,
123
+ IViewableItemsChangedInfo,
124
+ IViewabilityConfigCallbackPair,
125
+ IVirtualizedListHandle,
126
+ } from '@symbiote-native/components';
127
+ export {
128
+ VListEmptyDirective,
129
+ VListFooterDirective,
130
+ VListHeaderDirective,
131
+ VListItemDirective,
132
+ VListSeparatorDirective,
133
+ } from '../virtualized-list';
134
+ export type { IVListItemContext, IVListSeparatorContext } from '../virtualized-list';
135
+
136
+ // FlatList's imperative handle is exactly VirtualizedList's.
137
+ export type IFlatListHandle = IVirtualizedListHandle;
138
+
139
+ // The Angular FlatList prop surface. Mirrors React/Vue's IFlatListProps: every agnostic
140
+ // VirtualizedList prop EXCEPT data/getItem/getItemCount (FlatList derives those from a plain `data`
141
+ // array), PLUS numColumns + columnWrapperStyle. The element-returning props are templates in
142
+ // Angular, so they are absent here (they were already absent from IVirtualizedListProps).
143
+ export type IFlatListProps<ItemT> = Omit<
144
+ IVirtualizedListProps<ItemT>,
145
+ 'data' | 'getItem' | 'getItemCount'
146
+ > & {
147
+ data: readonly ItemT[];
148
+ numColumns?: number;
149
+ // Style for the auto-generated row View when numColumns > 1 (RN's columnWrapperStyle). A bare
150
+ // string resolves through the shared style registry.
151
+ columnWrapperStyle?: IStyleProp<IViewStyle> | string;
152
+ };
153
+
154
+ // What the FlatList component itself takes as plain @Input()s: the full surface minus the events
155
+ // it exposes as real @Output() EventEmitters instead (see the class below), mirroring how
156
+ // pressable/index.ts derives IAngularPressableInputs from IAngularPressableProps.
157
+ export type IFlatListInputs<ItemT> = Omit<
158
+ IFlatListProps<ItemT>,
159
+ | 'onEndReached'
160
+ | 'onStartReached'
161
+ | 'onRefresh'
162
+ | 'onViewableItemsChanged'
163
+ | 'onScrollToIndexFailed'
164
+ >;
165
+
166
+ @Component({
167
+ selector: 'FlatList',
168
+ standalone: true,
169
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
170
+ imports: [
171
+ VirtualizedList,
172
+ VListItemDirective,
173
+ VListHeaderDirective,
174
+ VListFooterDirective,
175
+ VListEmptyDirective,
176
+ VListSeparatorDirective,
177
+ VListOutletDirective,
178
+ ViewHost,
179
+ ],
180
+ changeDetection: ChangeDetectionStrategy.OnPush,
181
+ template: `
182
+ @if (isMultiColumn) {
183
+ <VirtualizedList
184
+ [data]="rows"
185
+ [getItem]="getRow"
186
+ [getItemCount]="getRowCount"
187
+ [keyExtractor]="rowKey"
188
+ [getItemLayout]="getItemLayout"
189
+ [horizontal]="horizontal"
190
+ [inverted]="inverted"
191
+ [extraData]="extraData"
192
+ (endReached)="endReached.emit($event)"
193
+ [onEndReachedThreshold]="onEndReachedThreshold"
194
+ (startReached)="startReached.emit($event)"
195
+ [onStartReachedThreshold]="onStartReachedThreshold"
196
+ (refresh)="refresh.emit()"
197
+ [refreshRequested]="refresh.observed"
198
+ [refreshing]="refreshing"
199
+ [progressViewOffset]="progressViewOffset"
200
+ (viewableItemsChanged)="rowViewableItemsChanged($event)"
201
+ [viewabilityConfig]="viewabilityConfig"
202
+ [viewabilityConfigCallbackPairs]="rowViewabilityPairs"
203
+ (scrollToIndexFailed)="scrollToIndexFailed.emit($event)"
204
+ [initialNumToRender]="initialNumToRender"
205
+ [initialScrollIndex]="initialScrollIndex"
206
+ [maxToRenderPerBatch]="maxToRenderPerBatch"
207
+ [updateCellsBatchingPeriod]="updateCellsBatchingPeriod"
208
+ [windowSize]="windowSize"
209
+ [stickyHeaderIndices]="stickyHeaderIndices"
210
+ [maintainVisibleContentPosition]="maintainVisibleContentPosition"
211
+ [onScroll]="onScroll"
212
+ [onScrollBeginDrag]="onScrollBeginDrag"
213
+ [onScrollEndDrag]="onScrollEndDrag"
214
+ [onMomentumScrollBegin]="onMomentumScrollBegin"
215
+ [onMomentumScrollEnd]="onMomentumScrollEnd"
216
+ [scrollEventThrottle]="scrollEventThrottle"
217
+ [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
218
+ [keyboardDismissMode]="keyboardDismissMode"
219
+ [style]="resolvedStyle"
220
+ [contentContainerStyle]="contentContainerStyle"
221
+ >
222
+ <ng-template vListItem let-row let-separators="separators">
223
+ <symbiote-view [style]="rowStyle">
224
+ @for (cell of rowCells(row, separators); track cell.key) {
225
+ <symbiote-view [style]="columnCellStyle">
226
+ <ng-container
227
+ [vListOutlet]="itemDir?.templateRef"
228
+ [vListOutletContext]="cell.context"
229
+ ></ng-container>
230
+ </symbiote-view>
231
+ }
232
+ </symbiote-view>
233
+ </ng-template>
234
+ @if (headerDir !== undefined) {
235
+ <ng-template vListHeader>
236
+ <ng-container [vListOutlet]="headerDir.templateRef"></ng-container>
237
+ </ng-template>
238
+ }
239
+ @if (footerDir !== undefined) {
240
+ <ng-template vListFooter>
241
+ <ng-container [vListOutlet]="footerDir.templateRef"></ng-container>
242
+ </ng-template>
243
+ }
244
+ @if (emptyDir !== undefined) {
245
+ <ng-template vListEmpty>
246
+ <ng-container [vListOutlet]="emptyDir.templateRef"></ng-container>
247
+ </ng-template>
248
+ }
249
+ @if (separatorDir !== undefined) {
250
+ <ng-template
251
+ vListSeparator
252
+ let-highlighted="highlighted"
253
+ let-leadingItem="leadingItem"
254
+ let-trailingItem="trailingItem"
255
+ >
256
+ <ng-container
257
+ [vListOutlet]="separatorDir.templateRef"
258
+ [vListOutletContext]="rowSeparatorContext(highlighted, leadingItem, trailingItem)"
259
+ ></ng-container>
260
+ </ng-template>
261
+ }
262
+ </VirtualizedList>
263
+ } @else {
264
+ <VirtualizedList
265
+ [data]="data"
266
+ [getItem]="getFlatItem"
267
+ [getItemCount]="getFlatCount"
268
+ [keyExtractor]="keyExtractor"
269
+ [getItemLayout]="getItemLayout"
270
+ [horizontal]="horizontal"
271
+ [inverted]="inverted"
272
+ [extraData]="extraData"
273
+ (endReached)="endReached.emit($event)"
274
+ [onEndReachedThreshold]="onEndReachedThreshold"
275
+ (startReached)="startReached.emit($event)"
276
+ [onStartReachedThreshold]="onStartReachedThreshold"
277
+ (refresh)="refresh.emit()"
278
+ [refreshRequested]="refresh.observed"
279
+ [refreshing]="refreshing"
280
+ [progressViewOffset]="progressViewOffset"
281
+ (viewableItemsChanged)="viewableItemsChanged.emit($event)"
282
+ [viewabilityConfig]="viewabilityConfig"
283
+ [viewabilityConfigCallbackPairs]="viewabilityConfigCallbackPairs"
284
+ (scrollToIndexFailed)="scrollToIndexFailed.emit($event)"
285
+ [initialNumToRender]="initialNumToRender"
286
+ [initialScrollIndex]="initialScrollIndex"
287
+ [maxToRenderPerBatch]="maxToRenderPerBatch"
288
+ [updateCellsBatchingPeriod]="updateCellsBatchingPeriod"
289
+ [windowSize]="windowSize"
290
+ [stickyHeaderIndices]="stickyHeaderIndices"
291
+ [maintainVisibleContentPosition]="maintainVisibleContentPosition"
292
+ [onScroll]="onScroll"
293
+ [onScrollBeginDrag]="onScrollBeginDrag"
294
+ [onScrollEndDrag]="onScrollEndDrag"
295
+ [onMomentumScrollBegin]="onMomentumScrollBegin"
296
+ [onMomentumScrollEnd]="onMomentumScrollEnd"
297
+ [scrollEventThrottle]="scrollEventThrottle"
298
+ [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
299
+ [keyboardDismissMode]="keyboardDismissMode"
300
+ [style]="resolvedStyle"
301
+ [contentContainerStyle]="contentContainerStyle"
302
+ >
303
+ <ng-template vListItem let-item let-index="index" let-separators="separators">
304
+ <ng-container
305
+ [vListOutlet]="itemDir?.templateRef"
306
+ [vListOutletContext]="{ $implicit: item, index, separators }"
307
+ ></ng-container>
308
+ </ng-template>
309
+ @if (headerDir !== undefined) {
310
+ <ng-template vListHeader>
311
+ <ng-container [vListOutlet]="headerDir.templateRef"></ng-container>
312
+ </ng-template>
313
+ }
314
+ @if (footerDir !== undefined) {
315
+ <ng-template vListFooter>
316
+ <ng-container [vListOutlet]="footerDir.templateRef"></ng-container>
317
+ </ng-template>
318
+ }
319
+ @if (emptyDir !== undefined) {
320
+ <ng-template vListEmpty>
321
+ <ng-container [vListOutlet]="emptyDir.templateRef"></ng-container>
322
+ </ng-template>
323
+ }
324
+ @if (separatorDir !== undefined) {
325
+ <ng-template
326
+ vListSeparator
327
+ let-highlighted="highlighted"
328
+ let-leadingItem="leadingItem"
329
+ let-trailingItem="trailingItem"
330
+ >
331
+ <ng-container
332
+ [vListOutlet]="separatorDir.templateRef"
333
+ [vListOutletContext]="itemSeparatorContext(highlighted, leadingItem, trailingItem)"
334
+ ></ng-container>
335
+ </ng-template>
336
+ }
337
+ </VirtualizedList>
338
+ }
339
+ `,
340
+ })
341
+ export class FlatList<ItemT = unknown>
342
+ implements IFlatListInputs<ItemT>, IVirtualizedListHandle, OnChanges
343
+ {
344
+ // The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
345
+ // `[onEndReached]="…"` — re-emitted straight from the inner VirtualizedList's own @Output()s (see
346
+ // the template's `(endReached)="endReached.emit($event)"` style forwarding above).
347
+ @Output() readonly endReached = new EventEmitter<{ distanceFromEnd: number }>();
348
+ @Output() readonly startReached = new EventEmitter<{ distanceFromStart: number }>();
349
+ @Output() readonly refresh = new EventEmitter<void>();
350
+ @Output() readonly viewableItemsChanged = new EventEmitter<IViewableItemsChangedInfo<ItemT>>();
351
+ @Output() readonly scrollToIndexFailed = new EventEmitter<{
352
+ index: number;
353
+ highestMeasuredFrameIndex: number;
354
+ averageItemLength: number;
355
+ }>();
356
+ @Input({ required: true }) data!: readonly ItemT[];
357
+ @Input() numColumns?: number;
358
+ @Input() columnWrapperStyle?: IStyleProp<IViewStyle> | string;
359
+ @Input() keyExtractor?: (item: ItemT, index: number) => string;
360
+ @Input() getItemLayout?: (
361
+ data: unknown,
362
+ index: number,
363
+ ) => { length: number; offset: number; index: number };
364
+ @Input() horizontal?: boolean;
365
+ @Input() inverted?: boolean;
366
+ @Input() extraData?: unknown;
367
+ @Input() onEndReachedThreshold?: number;
368
+ @Input() onStartReachedThreshold?: number;
369
+ @Input() refreshing?: boolean | null;
370
+ @Input() progressViewOffset?: number;
371
+ @Input() viewabilityConfig?: IVirtualizedListProps<ItemT>['viewabilityConfig'];
372
+ @Input() viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
373
+ @Input() initialNumToRender?: number;
374
+ @Input() initialScrollIndex?: number;
375
+ @Input() maxToRenderPerBatch?: number;
376
+ @Input() updateCellsBatchingPeriod?: number;
377
+ @Input() windowSize?: number;
378
+ @Input() stickyHeaderIndices?: number[];
379
+ @Input() maintainVisibleContentPosition?: {
380
+ minIndexForVisible: number;
381
+ autoscrollToTopThreshold?: number;
382
+ };
383
+ @Input() onScroll?: IVirtualizedListProps<ItemT>['onScroll'];
384
+ @Input() onScrollBeginDrag?: IVirtualizedListProps<ItemT>['onScrollBeginDrag'];
385
+ @Input() onScrollEndDrag?: IVirtualizedListProps<ItemT>['onScrollEndDrag'];
386
+ @Input() onMomentumScrollBegin?: IVirtualizedListProps<ItemT>['onMomentumScrollBegin'];
387
+ @Input() onMomentumScrollEnd?: IVirtualizedListProps<ItemT>['onMomentumScrollEnd'];
388
+ @Input() scrollEventThrottle?: number;
389
+ @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
390
+ @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
391
+ @Input() style?: IStyleProp<IViewStyle>;
392
+ @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
393
+
394
+ // The app's cell + slot templates, captured for the multi-column re-stamp path. In the
395
+ // single-column path they are ALSO projected through <ng-content> to the inner list and these
396
+ // captures stay unused (the inner list's own @ContentChild resolves them across the projection).
397
+ @ContentChild(VListItemDirective) itemDir?: VListItemDirective<ItemT>;
398
+ @ContentChild(VListHeaderDirective) headerDir?: VListHeaderDirective;
399
+ @ContentChild(VListFooterDirective) footerDir?: VListFooterDirective;
400
+ @ContentChild(VListEmptyDirective) emptyDir?: VListEmptyDirective;
401
+ @ContentChild(VListSeparatorDirective) separatorDir?: VListSeparatorDirective<ItemT>;
402
+
403
+ // The composed inner list (whichever numColumns branch rendered). Its instance IS the
404
+ // IVirtualizedListHandle, so FlatList's handle delegates straight to it.
405
+ @ViewChild(VirtualizedList) private listRef?: VirtualizedList;
406
+
407
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
408
+ // onto (see anchorHostStyle's doc comment) — NOT `listRef` above, which targets the real inner
409
+ // `<VirtualizedList>` one level down (itself its own separate anchor host).
410
+ private readonly elementRef = inject(ElementRef);
411
+
412
+ // --- derived view state (recomputed in ngOnChanges, stable identity across CD) ---
413
+ rows: IRow<ItemT>[] = [];
414
+ // Angular's [style]="…" binding compiles to the built-in ɵɵstyleMap instruction (NOT a
415
+ // regular @Input() property write), which only understands a flat object — an array (RN's
416
+ // own `style={[a, b]}` composition idiom) crashes deep inside Angular's styling engine. So
417
+ // every style value this component stamps onto a template `[style]=` binding is flattened
418
+ // via the engine's own flattenStyle first, here at the source rather than at each call site.
419
+ rowStyle: IViewStyle = flattenStyle([{ flexDirection: 'row' }]);
420
+ rowViewabilityPairs?: IViewabilityConfigCallbackPair<IRow<ItemT>>[];
421
+ readonly columnCellStyle: IViewStyle = { flex: 1 };
422
+ resolvedStyle: IViewStyle | undefined = undefined;
423
+
424
+ get columns(): number {
425
+ return this.numColumns ?? SINGLE_COLUMN;
426
+ }
427
+ get isMultiColumn(): boolean {
428
+ return this.columns > SINGLE_COLUMN;
429
+ }
430
+
431
+ ngOnChanges(changes: SimpleChanges): void {
432
+ // Recomputed on EVERY ngOnChanges call, not gated on `changes['style']`: a class= on the
433
+ // FlatList use site (no matching [style] binding at all) never appears in `changes`, so a
434
+ // guard here would permanently skip picking up anchorHostStyle for that (the actual bug-report
435
+ // shape) — ngOnChanges still fires at least once at mount via the required `data` @Input(), and
436
+ // by then the anchor's class-derived style is already resolved synchronously (routeProp runs
437
+ // inline from Renderer2.addClass, before this component's own lifecycle hooks). stableAnchorStyle
438
+ // (not a bare flattenStyle) keeps `resolvedStyle`'s REFERENCE stable across ticks where nothing
439
+ // actually changed — `[style]="resolvedStyle"` binds onto VirtualizedList's own `@Input() style`,
440
+ // which feeds its `ngDoCheck` dedup gate; a fresh object every tick would defeat that gate and
441
+ // free-run change detection forever (see stableAnchorStyle's doc comment).
442
+ this.resolvedStyle = stableAnchorStyle(this.elementRef, this.style, this.resolvedStyle);
443
+ if (changes['columnWrapperStyle'] !== undefined) {
444
+ // A class-name string resolves through the shared registry before flattenStyle, which
445
+ // only understands style objects/arrays.
446
+ const resolvedColumnWrapperStyle =
447
+ typeof this.columnWrapperStyle === 'string'
448
+ ? resolveClassName(this.columnWrapperStyle)
449
+ : this.columnWrapperStyle;
450
+ this.rowStyle = flattenStyle([{ flexDirection: 'row' }, resolvedColumnWrapperStyle]);
451
+ }
452
+ if (changes['data'] !== undefined || changes['numColumns'] !== undefined) {
453
+ this.rows = this.isMultiColumn ? chunkIntoRows(this.data, this.columns) : [];
454
+ dlog(`Angular FlatList over ${this.data?.length ?? 0} items, ${this.columns} column(s)`);
455
+ }
456
+ if (
457
+ changes['viewabilityConfigCallbackPairs'] !== undefined ||
458
+ changes['numColumns'] !== undefined
459
+ ) {
460
+ this.rowViewabilityPairs = this.buildRowViewabilityPairs();
461
+ }
462
+ }
463
+
464
+ // ---- single-column data adaptation (plain array -> getItem/getItemCount) ----
465
+ getFlatItem = (_data: unknown, index: number): ItemT => this.data[index];
466
+ getFlatCount = (_data: unknown): number => this.data.length;
467
+
468
+ // ---- multi-column data adaptation (the virtualized stream is rows) ----
469
+ getRow = (_data: unknown, index: number): IRow<ItemT> => this.rows[index];
470
+ getRowCount = (_data: unknown): number => this.rows.length;
471
+ rowKey = (row: IRow<ItemT>, _index: number): string => rowKeyExtractor(row);
472
+
473
+ // Viewability over rows expands back to per-item tokens, so the caller sees item-level
474
+ // visibility, not row-level (shared expandRowViewability), matching Vue.
475
+ rowViewableItemsChanged = (info: IViewableItemsChangedInfo<IRow<ItemT>>): void => {
476
+ this.viewableItemsChanged.emit(expandRowViewability(info, this.keyExtractor));
477
+ };
478
+
479
+ private buildRowViewabilityPairs(): IViewabilityConfigCallbackPair<IRow<ItemT>>[] | undefined {
480
+ return this.viewabilityConfigCallbackPairs?.map(pair => ({
481
+ viewabilityConfig: pair.viewabilityConfig,
482
+ onViewableItemsChanged: (rowInfo: IViewableItemsChangedInfo<IRow<ItemT>>): void => {
483
+ pair.onViewableItemsChanged?.(expandRowViewability(rowInfo, this.keyExtractor));
484
+ },
485
+ }));
486
+ }
487
+
488
+ // Lay out one row's N columns: every item shares the row's separators handle (the divider sits
489
+ // between rows, not columns) and carries its absolute index (row.startIndex + column), like RN's
490
+ // multi-column FlatList. Args arrive `unknown` from the template's `let` bindings (Angular cannot
491
+ // type a generic structural directive's context), narrowed here — no `as`.
492
+ rowCells(
493
+ row: unknown,
494
+ separators: unknown,
495
+ ): { key: string; context: IVListItemContext<ItemT> }[] {
496
+ if (!isRow<ItemT>(row)) return [];
497
+ const handle = isSeparators(separators) ? separators : NOOP_SEPARATORS;
498
+ return row.items.map((item, column) => {
499
+ const index = row.startIndex + column;
500
+ const key = this.keyExtractor ? this.keyExtractor(item, index) : String(index);
501
+ return { key, context: { $implicit: item, index, separators: handle } };
502
+ });
503
+ }
504
+
505
+ // The divider between rows shows real items (last of the row above, first of the row below), so the
506
+ // app's separator template, typed on ItemT, sees items rather than the IRow wrapper. Args arrive
507
+ // `unknown` from the template's `let` bindings, narrowed here.
508
+ rowSeparatorContext(
509
+ highlighted: unknown,
510
+ leadingRow: unknown,
511
+ trailingRow: unknown,
512
+ ): IVListSeparatorContext<ItemT> {
513
+ const isHighlighted = highlighted === true;
514
+ return {
515
+ $implicit: isHighlighted,
516
+ highlighted: isHighlighted,
517
+ leadingItem: isRow<ItemT>(leadingRow) ? lastItemOfRow(leadingRow) : undefined,
518
+ trailingItem: isRow<ItemT>(trailingRow) ? firstItemOfRow(trailingRow) : undefined,
519
+ };
520
+ }
521
+
522
+ // Single-column separator context: leadingItem/trailingItem are already real items (no row
523
+ // wrapper to narrow through — see the asItem boundary comment above).
524
+ itemSeparatorContext(
525
+ highlighted: unknown,
526
+ leadingItem: unknown,
527
+ trailingItem: unknown,
528
+ ): IVListSeparatorContext<ItemT> {
529
+ const isHighlighted = highlighted === true;
530
+ return {
531
+ $implicit: isHighlighted,
532
+ highlighted: isHighlighted,
533
+ leadingItem: asItem<ItemT>(leadingItem),
534
+ trailingItem: asItem<ItemT>(trailingItem),
535
+ };
536
+ }
537
+
538
+ // ---- imperative handle (RN FlatList surface) — delegates to the inner VirtualizedList ----
539
+
540
+ scrollToOffset(params: { offset: number; animated?: boolean }): void {
541
+ this.listRef?.scrollToOffset(params);
542
+ }
543
+ scrollToIndex(params: {
544
+ index: number;
545
+ animated?: boolean;
546
+ viewOffset?: number;
547
+ viewPosition?: number;
548
+ }): void {
549
+ this.listRef?.scrollToIndex(params);
550
+ }
551
+ scrollToItem(params: { item: unknown; animated?: boolean; viewPosition?: number }): void {
552
+ this.listRef?.scrollToItem(params);
553
+ }
554
+ scrollToEnd(params?: { animated?: boolean }): void {
555
+ this.listRef?.scrollToEnd(params);
556
+ }
557
+ flashScrollIndicators(): void {
558
+ this.listRef?.flashScrollIndicators();
559
+ }
560
+ getNativeScrollRef(): IScrollViewHandle | null {
561
+ return this.listRef?.getNativeScrollRef() ?? null;
562
+ }
563
+ getScrollableNode(): IScrollViewHandle | null {
564
+ return this.listRef?.getScrollableNode() ?? null;
565
+ }
566
+ getScrollResponder(): IScrollViewHandle | null {
567
+ return this.listRef?.getScrollResponder() ?? null;
568
+ }
569
+ getScrollNode(): ISymbioteNode | null {
570
+ return this.listRef?.getScrollNode() ?? null;
571
+ }
572
+ recordInteraction(): void {
573
+ this.listRef?.recordInteraction();
574
+ }
575
+ }
@@ -0,0 +1,46 @@
1
+ // Regression test for the anchor/class bug (angular-adapter skill): Image is its own
2
+ // ANCHOR_HOST_COMPONENTS entry — a class= on <Image> resolves onto Image's OWN anchor and needs
3
+ // its OWN anchorHostStyle merge (see index.ios.ts/index.android.ts's imageProps override), not
4
+ // transitively fixed by anything else. Mirrors pressable.test.ts's "resolves a class=" case.
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 } from '@symbiote-native/test-utils';
10
+
11
+ import { mount, unmount } from '../../render';
12
+ import { Image } from './index';
13
+
14
+ const ROOT_TAG = 915;
15
+ const fabric = installFabric();
16
+
17
+ @Component({
18
+ selector: 'symbiote-image-class-host',
19
+ standalone: true,
20
+ imports: [Image],
21
+ template: `<Image
22
+ [testID]="'photo'"
23
+ class="card"
24
+ [source]="{ uri: 'https://example.com/a.png' }"
25
+ />`,
26
+ })
27
+ class ImageClassHost {}
28
+
29
+ beforeEach(() => {
30
+ fabric.reset();
31
+ registerStyles({ card: { backgroundColor: 'red' } });
32
+ });
33
+ afterEach(() => {
34
+ unmount(ROOT_TAG);
35
+ clearGlobalStyles();
36
+ });
37
+
38
+ describe('Image anchor class= resolution', () => {
39
+ it('resolves a class= on the Image use site onto the real committed view, not the anchor', async () => {
40
+ mount(ROOT_TAG, ImageClassHost);
41
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
42
+
43
+ const node = fabric.find(n => n.props.testID === 'photo');
44
+ expect(node?.props.backgroundColor).toBe('red');
45
+ });
46
+ });
@@ -0,0 +1,48 @@
1
+ import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, inject } from '@angular/core';
2
+ import { anchorHostStyle, ImageHost, SymbioteHostPropsDirective } from '../../primitives';
3
+ import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps } from './shared';
4
+ export { setImageSourceResolver } from './shared';
5
+ export type {
6
+ IImageProps,
7
+ IImageSource,
8
+ IImageSourceProp,
9
+ IResizeMode,
10
+ IImageSize,
11
+ IImageCacheStatus,
12
+ } from './shared';
13
+
14
+ @Component({
15
+ selector: 'Image',
16
+ standalone: true,
17
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
18
+ imports: [ImageHost, SymbioteHostPropsDirective],
19
+ inputs: IMAGE_INPUTS,
20
+ outputs: IMAGE_OUTPUTS,
21
+ template: `
22
+ <symbiote-image
23
+ [symbioteHostProps]="imageProps"
24
+ (accessibilityAction)="handleAccessibilityAction($event)"
25
+ (accessibilityTap)="handleAccessibilityTap($event)"
26
+ (magicTap)="handleMagicTap($event)"
27
+ (accessibilityEscape)="handleAccessibilityEscape($event)"
28
+ (loadStart)="handleLoadStart($event)"
29
+ (load)="handleLoad($event)"
30
+ (loadEnd)="handleLoadEnd($event)"
31
+ (error)="handleError($event)"
32
+ (progress)="handleProgress($event)"
33
+ (partialLoad)="handlePartialLoad($event)"
34
+ />
35
+ `,
36
+ })
37
+ export class Image extends ImageBase {
38
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
39
+ // onto (see anchorHostStyle's doc comment) — NOT the inner `symbiote-image` one level down.
40
+ private readonly elementRef = inject(ElementRef);
41
+
42
+ override get imageProps(): Record<string, unknown> {
43
+ return resolveImageProps({
44
+ ...this.imageInputProps,
45
+ style: [anchorHostStyle(this.elementRef), this.style],
46
+ });
47
+ }
48
+ }