@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,288 @@
1
+ // RefreshControl, the Angular lifecycle half. On iOS this is the PullToRefreshView Fabric node that
2
+ // lives INSIDE a ScrollView (a childless sibling before the content container); on Android it is
3
+ // AndroidSwipeRefreshLayout and WRAPS the scroll view (ADR 0020), receiving it through <ng-content>.
4
+ // The Angular twin of the React/Vue RefreshControl. There is no JS-side platform renaming and no
5
+ // shared render fn — every prop forwards straight to the native node, which reads the ones it
6
+ // understands and ignores the rest, so the Android-only and iOS-only families ride down harmlessly
7
+ // on both. So this folds aria/role through the shared resolveAccessibilityProps and maps the native
8
+ // props + a11y + onRefresh straight onto the symbiote-refresh-control host, children via <ng-content>.
9
+ // No platform branch (one composed component both platforms), so this stays a flat single file (ADR 0026).
10
+ //
11
+ // `refreshing` is a controlled prop: the parent owns it and pushes it down each commit; native
12
+ // reports the gesture via the direct `topRefresh` event, which the engine routes to the host's
13
+ // `refresh` listener (Angular blocks [onX] property bindings; events flow through (event) only).
14
+
15
+ import {
16
+ CUSTOM_ELEMENTS_SCHEMA,
17
+ ChangeDetectionStrategy,
18
+ Component,
19
+ ElementRef,
20
+ EventEmitter,
21
+ inject,
22
+ Input,
23
+ Output,
24
+ ViewChild,
25
+ type AfterViewInit,
26
+ type OnChanges,
27
+ type OnInit,
28
+ type SimpleChanges,
29
+ } from '@angular/core';
30
+ import {
31
+ resolveAccessibilityProps,
32
+ type IAccessibilityProps,
33
+ type IAccessibilityStateValue,
34
+ type IAriaProps,
35
+ } from '@symbiote-native/components';
36
+ import {
37
+ dispatchViewCommand,
38
+ dlog,
39
+ isSymbioteNode,
40
+ type IStyleProp,
41
+ type ISymbioteEvent,
42
+ type IViewStyle,
43
+ } from '@symbiote-native/engine';
44
+ import { anchorHostStyle, RefreshControlHost, SymbioteHostPropsDirective } from '../primitives';
45
+
46
+ // Record<string, unknown> already tolerates `style` holding the [anchorHostStyle, this.style]
47
+ // array anchorHostStyle's merge produces (see hostProps below) — no widening needed.
48
+ type IHostProps = Record<string, unknown>;
49
+
50
+ // Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
51
+ // so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
52
+ function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
53
+ return typeof value === 'object' && value !== null && 'nativeEvent' in value;
54
+ }
55
+
56
+ // Mirrors React's IRefreshControlProps minus children (Angular takes the Android-wrapped scroll view
57
+ // via <ng-content>), declared per-adapter over the shared a11y base per
58
+ // <prop_types_split_agnostic_vs_per_adapter>. Read the React reference for the native prop names.
59
+ export interface IAngularRefreshControlProps extends IAccessibilityProps, IAriaProps {
60
+ refreshing: boolean;
61
+ // RN's onRefresh is `() => void | Promise<void>`, the handler may be async; the promise is
62
+ // fire-and-forget (native already starts refreshing on the gesture).
63
+ onRefresh?: () => void | Promise<void>;
64
+ tintColor?: string;
65
+ title?: string;
66
+ titleColor?: string;
67
+ progressViewOffset?: number;
68
+ // Android-only spinner styling (RN RefreshControlPropsAndroid): `colors` are the indicator's
69
+ // animated stroke colors, `progressBackgroundColor` the disc behind it, `size` the diameter preset.
70
+ // AndroidSwipeRefreshLayout reads them; PullToRefreshView on iOS ignores unknown props.
71
+ colors?: readonly string[];
72
+ progressBackgroundColor?: string;
73
+ size?: 'default' | 'large';
74
+ // Android-only native prop forwarded to AndroidSwipeRefreshLayout; iOS native never reads it.
75
+ enabled?: boolean;
76
+ // The Android scroll-view wrap injects the layout half of the style onto this host; iOS leaves it
77
+ // unset (the RefreshControl is a childless sibling). Harmless to forward on both.
78
+ style?: IStyleProp<IViewStyle>;
79
+ }
80
+
81
+ // What the RefreshControl component itself takes as plain @Input()s: the full surface minus the
82
+ // refresh/accessibility events, which it exposes as real @Output() EventEmitters instead (mirrors
83
+ // Pressable's IAngularPressableInputs in pressable/index.ts).
84
+ export type IAngularRefreshControlInputs = Omit<
85
+ IAngularRefreshControlProps,
86
+ | 'onRefresh'
87
+ | 'onAccessibilityAction'
88
+ | 'onAccessibilityTap'
89
+ | 'onMagicTap'
90
+ | 'onAccessibilityEscape'
91
+ >;
92
+
93
+ @Component({
94
+ selector: 'RefreshControl',
95
+ standalone: true,
96
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
97
+ imports: [RefreshControlHost, SymbioteHostPropsDirective],
98
+ changeDetection: ChangeDetectionStrategy.OnPush,
99
+ template: `
100
+ <symbiote-refresh-control
101
+ #host
102
+ [symbioteHostProps]="hostProps"
103
+ (refresh)="handleRefresh()"
104
+ (accessibilityAction)="emit(accessibilityAction, $event)"
105
+ (accessibilityTap)="emit(accessibilityTap, $event)"
106
+ (magicTap)="emit(magicTap, $event)"
107
+ (accessibilityEscape)="emit(accessibilityEscape, $event)"
108
+ >
109
+ <ng-content></ng-content>
110
+ </symbiote-refresh-control>
111
+ `,
112
+ })
113
+ export class RefreshControl
114
+ implements IAngularRefreshControlInputs, OnInit, OnChanges, AfterViewInit
115
+ {
116
+ // Controlled prop the parent owns; required to match the React reference surface.
117
+ @Input({ required: true }) refreshing!: boolean;
118
+ // RN's onRefresh is `() => void | Promise<void>`, so the callback shape allows an async handler;
119
+ // the @Output() itself only signals the gesture (fire-and-forget, native already starts refreshing).
120
+ @Output() readonly refresh = new EventEmitter<void>();
121
+ @Input() tintColor?: string;
122
+ @Input() title?: string;
123
+ @Input() titleColor?: string;
124
+ @Input() progressViewOffset?: number;
125
+ @Input() colors?: readonly string[];
126
+ @Input() progressBackgroundColor?: string;
127
+ @Input() size?: 'default' | 'large';
128
+ @Input() enabled?: boolean;
129
+ @Input() style?: IStyleProp<IViewStyle>;
130
+ @Input() testID?: string;
131
+ @Input() nativeID?: string;
132
+ @Input() accessible?: boolean;
133
+ @Input() accessibilityLabel?: string;
134
+ @Input() accessibilityHint?: string;
135
+ @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
136
+ @Input() accessibilityState?: IAccessibilityStateValue;
137
+ @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
138
+ @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
139
+ @Input() accessibilityLabelledBy?: string | string[];
140
+ @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
141
+ @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
142
+ @Input() screenReaderFocusable?: boolean;
143
+ @Input() accessibilityViewIsModal?: boolean;
144
+ @Input() accessibilityElementsHidden?: boolean;
145
+ @Input() accessibilityIgnoresInvertColors?: boolean;
146
+ @Input() accessibilityLanguage?: string;
147
+ @Input() accessibilityRespondsToUserInteraction?: boolean;
148
+ @Input() accessibilityShowsLargeContentViewer?: boolean;
149
+ @Input() accessibilityLargeContentTitle?: string;
150
+ @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
151
+ @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
152
+ @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
153
+ @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
154
+ @Input() role?: IAriaProps['role'];
155
+ @Input('aria-label') ariaLabel?: string;
156
+ @Input('aria-labelledby') ariaLabelledBy?: string;
157
+ @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
158
+ @Input('aria-hidden') ariaHidden?: boolean;
159
+ @Input('aria-busy') ariaBusy?: boolean;
160
+ @Input('aria-checked') ariaChecked?: boolean | 'mixed';
161
+ @Input('aria-disabled') ariaDisabled?: boolean;
162
+ @Input('aria-expanded') ariaExpanded?: boolean;
163
+ @Input('aria-selected') ariaSelected?: boolean;
164
+ @Input('aria-modal') ariaModal?: boolean;
165
+ @Input('aria-valuemax') ariaValueMax?: number;
166
+ @Input('aria-valuemin') ariaValueMin?: number;
167
+ @Input('aria-valuenow') ariaValueNow?: number;
168
+ @Input('aria-valuetext') ariaValueText?: string;
169
+
170
+ // The inner symbiote-refresh-control primitive — NOT this component's own anchor host. Used for
171
+ // the imperative dispatchViewCommand calls below.
172
+ @ViewChild('host') private host?: RefreshControlHost;
173
+
174
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
175
+ // onto (see anchorHostStyle's doc comment) — distinct from `host` above, which targets the real
176
+ // inner `symbiote-refresh-control` primitive one level down.
177
+ private readonly elementRef = inject(ElementRef);
178
+
179
+ private lastNativeRefreshing = false;
180
+ private refreshNativeNode: unknown;
181
+
182
+ ngOnInit(): void {
183
+ dlog('RefreshControl -> PullToRefreshView');
184
+ dlog(`RefreshControl refreshing=${String(this.refreshing)}`);
185
+ if (this.enabled !== undefined)
186
+ dlog(`RefreshControl enabled=${String(this.enabled)} (Android-only)`);
187
+ if (this.refresh.observed) dlog('RefreshControl refresh listener wired');
188
+ }
189
+
190
+ ngAfterViewInit(): void {
191
+ this.lastNativeRefreshing = this.refreshing;
192
+ }
193
+
194
+ ngOnChanges(changes: SimpleChanges): void {
195
+ const refreshing = changes.refreshing;
196
+ if (refreshing === undefined) return;
197
+ if (refreshing.firstChange) {
198
+ this.lastNativeRefreshing = this.refreshing;
199
+ return;
200
+ }
201
+ this.syncNativeRefreshing(this.refreshNativeNode ?? this.host?.nativeElement);
202
+ }
203
+
204
+ // Native starts the spinner before JS runs. Mirror RN's RefreshControl controlled-component
205
+ // handshake: remember that native is refreshing, call the user's callback, then after Angular has
206
+ // had a chance to propagate `[refreshing]`, force native back to the JS value if it stayed false.
207
+ handleRefresh(nativeNode: unknown = this.host?.nativeElement): void {
208
+ this.refreshNativeNode = nativeNode;
209
+ this.lastNativeRefreshing = true;
210
+ this.refresh.emit();
211
+ queueMicrotask(() => this.syncNativeRefreshing(nativeNode));
212
+ }
213
+
214
+ private syncNativeRefreshing(nativeNode: unknown): void {
215
+ if (this.refreshing === this.lastNativeRefreshing) return;
216
+ if (!isSymbioteNode(nativeNode)) return;
217
+ dispatchViewCommand(nativeNode, 'setNativeRefreshing', [this.refreshing]);
218
+ this.lastNativeRefreshing = this.refreshing;
219
+ }
220
+
221
+ // Forward an engine event to the matching @Output(), narrowing the template's untyped $event
222
+ // first. The accessibility* events arrive on the engine's structural event channel.
223
+ emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
224
+ if (isSymbioteEvent(event)) emitter.emit(event);
225
+ }
226
+
227
+ // The full native + a11y prop bag applied onto the host in one shot via
228
+ // [symbioteHostProps], instead of enumerating each key as its own template binding. The
229
+ // anchor's class-derived style goes FIRST, this component's own explicit `style` @Input SECOND
230
+ // — flattenStyle's later-wins collapse keeps an explicit [style] winning over its ambient class.
231
+ get hostProps(): IHostProps {
232
+ return {
233
+ refreshing: this.refreshing,
234
+ tintColor: this.tintColor,
235
+ title: this.title,
236
+ titleColor: this.titleColor,
237
+ progressViewOffset: this.progressViewOffset,
238
+ colors: this.colors,
239
+ progressBackgroundColor: this.progressBackgroundColor,
240
+ size: this.size,
241
+ enabled: this.enabled,
242
+ style: [anchorHostStyle(this.elementRef), this.style],
243
+ testID: this.testID,
244
+ nativeID: this.nativeID,
245
+ accessible: this.accessible,
246
+ ...this.folded,
247
+ };
248
+ }
249
+
250
+ // Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
251
+ // host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
252
+ get folded(): Partial<IAngularRefreshControlProps> {
253
+ return resolveAccessibilityProps({
254
+ accessibilityLabel: this.accessibilityLabel,
255
+ accessibilityHint: this.accessibilityHint,
256
+ accessibilityRole: this.accessibilityRole,
257
+ accessibilityState: this.accessibilityState,
258
+ accessibilityValue: this.accessibilityValue,
259
+ accessibilityActions: this.accessibilityActions,
260
+ accessibilityLabelledBy: this.accessibilityLabelledBy,
261
+ importantForAccessibility: this.importantForAccessibility,
262
+ accessibilityLiveRegion: this.accessibilityLiveRegion,
263
+ screenReaderFocusable: this.screenReaderFocusable,
264
+ accessibilityViewIsModal: this.accessibilityViewIsModal,
265
+ accessibilityElementsHidden: this.accessibilityElementsHidden,
266
+ accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
267
+ accessibilityLanguage: this.accessibilityLanguage,
268
+ accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
269
+ accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
270
+ accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
271
+ role: this.role,
272
+ 'aria-label': this.ariaLabel,
273
+ 'aria-labelledby': this.ariaLabelledBy,
274
+ 'aria-live': this.ariaLive,
275
+ 'aria-hidden': this.ariaHidden,
276
+ 'aria-busy': this.ariaBusy,
277
+ 'aria-checked': this.ariaChecked,
278
+ 'aria-disabled': this.ariaDisabled,
279
+ 'aria-expanded': this.ariaExpanded,
280
+ 'aria-selected': this.ariaSelected,
281
+ 'aria-modal': this.ariaModal,
282
+ 'aria-valuemax': this.ariaValueMax,
283
+ 'aria-valuemin': this.ariaValueMin,
284
+ 'aria-valuenow': this.ariaValueNow,
285
+ 'aria-valuetext': this.ariaValueText,
286
+ });
287
+ }
288
+ }
@@ -0,0 +1,43 @@
1
+ import '@angular/compiler';
2
+ import { Component } from '@angular/core';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+ import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
5
+ import { installFabric } from '@symbiote-native/test-utils';
6
+
7
+ import { mount, unmount } from '../render';
8
+ import { SafeAreaView } from './safe-area-view';
9
+
10
+ const ROOT_TAG = 910;
11
+ const fabric = installFabric();
12
+
13
+ @Component({
14
+ selector: 'symbiote-safe-area-view-host',
15
+ standalone: true,
16
+ imports: [SafeAreaView],
17
+ template: `
18
+ <SafeAreaView [testID]="'safe-area'" class="screen">
19
+ <symbiote-text>Hello</symbiote-text>
20
+ </SafeAreaView>
21
+ `,
22
+ })
23
+ class SafeAreaViewHostFixture {}
24
+
25
+ beforeEach(() => {
26
+ fabric.reset();
27
+ });
28
+ afterEach(() => {
29
+ unmount(ROOT_TAG);
30
+ clearGlobalStyles();
31
+ });
32
+
33
+ describe('SafeAreaView', () => {
34
+ it('resolves a class= on the SafeAreaView use site onto the real committed view, not the anchor', async () => {
35
+ registerStyles({ screen: { backgroundColor: 'navy' } });
36
+
37
+ mount(ROOT_TAG, SafeAreaViewHostFixture);
38
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
39
+
40
+ const node = fabric.find(n => n.props.testID === 'safe-area');
41
+ expect(node?.props.backgroundColor).toBe('navy');
42
+ });
43
+ });
@@ -0,0 +1,178 @@
1
+ // SafeAreaView, the Angular lifecycle half. A plain view whose native side insets its
2
+ // children to the safe area (notch, rounded corners, system bars). There is no JS-side
3
+ // translation — RN renders the native RCTSafeAreaView and lets the host do the inset math —
4
+ // so this folds aria/role through the shared resolveAccessibilityProps and maps style +
5
+ // a11y + onLayout straight onto the symbiote-safe-area-view host, children via <ng-content>.
6
+ // The Angular twin of the React/Vue SafeAreaView. No platform branch (one Fabric name both
7
+ // platforms), so this stays a flat single file (ADR 0026).
8
+
9
+ import {
10
+ CUSTOM_ELEMENTS_SCHEMA,
11
+ ChangeDetectionStrategy,
12
+ Component,
13
+ ElementRef,
14
+ EventEmitter,
15
+ inject,
16
+ Input,
17
+ Output,
18
+ } from '@angular/core';
19
+ import {
20
+ resolveAccessibilityProps,
21
+ type IAccessibilityProps,
22
+ type IAccessibilityStateValue,
23
+ type IAriaProps,
24
+ } from '@symbiote-native/components';
25
+ import type { IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
26
+ import { anchorHostStyle, SafeAreaViewHost, SymbioteHostPropsDirective } from '../primitives';
27
+
28
+ // Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
29
+ // so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
30
+ function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
31
+ return typeof value === 'object' && value !== null && 'nativeEvent' in value;
32
+ }
33
+
34
+ // Mirrors React's ISafeAreaViewProps minus children (Angular takes children via <ng-content>),
35
+ // declared per-adapter over the shared a11y base per <prop_types_split_agnostic_vs_per_adapter>.
36
+ export interface IAngularSafeAreaViewProps extends IAccessibilityProps, IAriaProps {
37
+ style?: IStyleProp<IViewStyle>;
38
+ onLayout?: (event: ISymbioteEvent) => void;
39
+ }
40
+
41
+ // What the component itself takes as plain @Input()s: the full surface minus onLayout and the
42
+ // accessibility callbacks, which it exposes as real @Output() EventEmitters instead (see the
43
+ // class below), mirroring Pressable's IAngularPressableInputs split.
44
+ export type IAngularSafeAreaViewInputs = Omit<
45
+ IAngularSafeAreaViewProps,
46
+ | 'onLayout'
47
+ | 'onAccessibilityAction'
48
+ | 'onAccessibilityTap'
49
+ | 'onMagicTap'
50
+ | 'onAccessibilityEscape'
51
+ >;
52
+
53
+ @Component({
54
+ selector: 'SafeAreaView',
55
+ standalone: true,
56
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
57
+ imports: [SafeAreaViewHost, SymbioteHostPropsDirective],
58
+ changeDetection: ChangeDetectionStrategy.OnPush,
59
+ template: `
60
+ <symbiote-safe-area-view
61
+ [symbioteHostProps]="hostProps"
62
+ (accessibilityAction)="emit(accessibilityAction, $event)"
63
+ (accessibilityTap)="emit(accessibilityTap, $event)"
64
+ (magicTap)="emit(magicTap, $event)"
65
+ (accessibilityEscape)="emit(accessibilityEscape, $event)"
66
+ (layout)="emit(layout, $event)"
67
+ >
68
+ <ng-content></ng-content>
69
+ </symbiote-safe-area-view>
70
+ `,
71
+ })
72
+ export class SafeAreaView implements IAngularSafeAreaViewInputs {
73
+ @Input() style?: IStyleProp<IViewStyle>;
74
+ // Real @Output()s, not `[onX]="…"` callbacks. Safe to name `layout` the same as the native
75
+ // `layout` event fired inside this component's own template — the engine's bubble() treats
76
+ // ANCHOR_HOST_COMPONENTS as transparent to listener lookup, so there is no double-fire
77
+ // (angular-adapter skill §7).
78
+ @Output() readonly layout = new EventEmitter<ISymbioteEvent>();
79
+ @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
80
+ @Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
81
+ @Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
82
+ @Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
83
+ @Input() testID?: string;
84
+ @Input() nativeID?: string;
85
+ @Input() accessible?: boolean;
86
+ @Input() accessibilityLabel?: string;
87
+ @Input() accessibilityHint?: string;
88
+ @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
89
+ @Input() accessibilityState?: IAccessibilityStateValue;
90
+ @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
91
+ @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
92
+ @Input() accessibilityLabelledBy?: string | string[];
93
+ @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
94
+ @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
95
+ @Input() screenReaderFocusable?: boolean;
96
+ @Input() accessibilityViewIsModal?: boolean;
97
+ @Input() accessibilityElementsHidden?: boolean;
98
+ @Input() accessibilityIgnoresInvertColors?: boolean;
99
+ @Input() accessibilityLanguage?: string;
100
+ @Input() accessibilityRespondsToUserInteraction?: boolean;
101
+ @Input() accessibilityShowsLargeContentViewer?: boolean;
102
+ @Input() accessibilityLargeContentTitle?: string;
103
+ @Input() role?: IAriaProps['role'];
104
+ @Input('aria-label') ariaLabel?: string;
105
+ @Input('aria-labelledby') ariaLabelledBy?: string;
106
+ @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
107
+ @Input('aria-hidden') ariaHidden?: boolean;
108
+ @Input('aria-busy') ariaBusy?: boolean;
109
+ @Input('aria-checked') ariaChecked?: boolean | 'mixed';
110
+ @Input('aria-disabled') ariaDisabled?: boolean;
111
+ @Input('aria-expanded') ariaExpanded?: boolean;
112
+ @Input('aria-selected') ariaSelected?: boolean;
113
+ @Input('aria-modal') ariaModal?: boolean;
114
+ @Input('aria-valuemax') ariaValueMax?: number;
115
+ @Input('aria-valuemin') ariaValueMin?: number;
116
+ @Input('aria-valuenow') ariaValueNow?: number;
117
+ @Input('aria-valuetext') ariaValueText?: string;
118
+
119
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
120
+ // onto (see anchorHostStyle's doc comment). SafeAreaView has no inner ViewChild, so this is
121
+ // the only ElementRef in the class.
122
+ private readonly elementRef = inject(ElementRef);
123
+
124
+ // Forward an engine event to the matching @Output(), narrowing the template's untyped
125
+ // $event first. layout / accessibility* arrive on the engine's structural event channel
126
+ // (Angular blocks [onX] property bindings; events flow through (event) only).
127
+ emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
128
+ if (isSymbioteEvent(event)) emitter.emit(event);
129
+ }
130
+
131
+ // Folds style/testID/nativeID/accessible alongside the accessibility*/aria-*/role surface into
132
+ // one bag for [symbioteHostProps] — resolveAccessibilityProps takes the whole props object (not
133
+ // just the accessibility slice) and returns it with aria-*/role folded into accessibility*, so
134
+ // the host node never sees an aria-* key (native ignores them), same transform React's
135
+ // SafeAreaView runs over its whole `rawProps`. The anchor's class-derived style goes FIRST, this
136
+ // component's own explicit `style` @Input SECOND — flattenStyle's later-wins collapse keeps an
137
+ // explicit [style] winning over its ambient class (angular-adapter skill's anchorHostStyle doc).
138
+ get hostProps(): Record<string, unknown> {
139
+ return resolveAccessibilityProps({
140
+ style: [anchorHostStyle(this.elementRef), this.style],
141
+ testID: this.testID,
142
+ nativeID: this.nativeID,
143
+ accessible: this.accessible,
144
+ accessibilityLabel: this.accessibilityLabel,
145
+ accessibilityHint: this.accessibilityHint,
146
+ accessibilityRole: this.accessibilityRole,
147
+ accessibilityState: this.accessibilityState,
148
+ accessibilityValue: this.accessibilityValue,
149
+ accessibilityActions: this.accessibilityActions,
150
+ accessibilityLabelledBy: this.accessibilityLabelledBy,
151
+ importantForAccessibility: this.importantForAccessibility,
152
+ accessibilityLiveRegion: this.accessibilityLiveRegion,
153
+ screenReaderFocusable: this.screenReaderFocusable,
154
+ accessibilityViewIsModal: this.accessibilityViewIsModal,
155
+ accessibilityElementsHidden: this.accessibilityElementsHidden,
156
+ accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
157
+ accessibilityLanguage: this.accessibilityLanguage,
158
+ accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
159
+ accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
160
+ accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
161
+ role: this.role,
162
+ 'aria-label': this.ariaLabel,
163
+ 'aria-labelledby': this.ariaLabelledBy,
164
+ 'aria-live': this.ariaLive,
165
+ 'aria-hidden': this.ariaHidden,
166
+ 'aria-busy': this.ariaBusy,
167
+ 'aria-checked': this.ariaChecked,
168
+ 'aria-disabled': this.ariaDisabled,
169
+ 'aria-expanded': this.ariaExpanded,
170
+ 'aria-selected': this.ariaSelected,
171
+ 'aria-modal': this.ariaModal,
172
+ 'aria-valuemax': this.ariaValueMax,
173
+ 'aria-valuemin': this.ariaValueMin,
174
+ 'aria-valuenow': this.ariaValueNow,
175
+ 'aria-valuetext': this.ariaValueText,
176
+ });
177
+ }
178
+ }
@@ -0,0 +1,123 @@
1
+ // Regression test for the Android content-projection bug documented in angular-adapter skill §18:
2
+ // ScrollView on Android picks a different native content view per axis (RCTView vs
3
+ // AndroidHorizontalScrollContentView), which used to require a separate <ng-content> per
4
+ // axis x refresh-control combination — and Angular only reliably projects into a component's
5
+ // LAST-declared <ng-content> when 2+ distinct declarations exist. Fixed by routing all four
6
+ // branches through a single shared <ng-template>/<ng-content> via a local template-outlet
7
+ // directive (see index.android.ts). Covers all four static combinations plus a runtime axis
8
+ // flip, which the projection.ts records/reconcile path must survive without dropping children.
9
+ import '@angular/compiler';
10
+ import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+ import { installFabric } from '@symbiote-native/test-utils';
13
+ import { mount, unmount } from '../../render';
14
+ import { RefreshControl } from '../refresh-control';
15
+ import { ScrollView } from './index.android';
16
+
17
+ const ROOT_TAG = 919;
18
+ const fabric = installFabric();
19
+ const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
20
+
21
+ function expectCellsProjected(): void {
22
+ expect(fabric.find(node => node.props.testID === 'a')).toBeDefined();
23
+ expect(fabric.find(node => node.props.testID === 'b')).toBeDefined();
24
+ }
25
+
26
+ class HorizontalNoRefreshApp {}
27
+ Component({
28
+ selector: 'axis-projection-horizontal-no-refresh',
29
+ standalone: true,
30
+ imports: [ScrollView],
31
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
32
+ template: `
33
+ <ScrollView [horizontal]="true">
34
+ <symbiote-view testID="a"></symbiote-view>
35
+ <symbiote-view testID="b"></symbiote-view>
36
+ </ScrollView>
37
+ `,
38
+ })(HorizontalNoRefreshApp);
39
+
40
+ class HorizontalRefreshApp {
41
+ refresh = (): void => {};
42
+ }
43
+ Component({
44
+ selector: 'axis-projection-horizontal-refresh',
45
+ standalone: true,
46
+ imports: [ScrollView, RefreshControl],
47
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
48
+ template: `
49
+ <ScrollView [horizontal]="true">
50
+ <RefreshControl [refreshing]="true" (refresh)="refresh()" />
51
+ <symbiote-view testID="a"></symbiote-view>
52
+ <symbiote-view testID="b"></symbiote-view>
53
+ </ScrollView>
54
+ `,
55
+ })(HorizontalRefreshApp);
56
+
57
+ class VerticalNoRefreshApp {}
58
+ Component({
59
+ selector: 'axis-projection-vertical-no-refresh',
60
+ standalone: true,
61
+ imports: [ScrollView],
62
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
63
+ template: `
64
+ <ScrollView>
65
+ <symbiote-view testID="a"></symbiote-view>
66
+ <symbiote-view testID="b"></symbiote-view>
67
+ </ScrollView>
68
+ `,
69
+ })(VerticalNoRefreshApp);
70
+
71
+ class AxisSwitchApp {
72
+ static readonly horizontal = signal(false);
73
+ get isHorizontal(): boolean {
74
+ return AxisSwitchApp.horizontal();
75
+ }
76
+ }
77
+ Component({
78
+ selector: 'axis-projection-axis-switch',
79
+ standalone: true,
80
+ imports: [ScrollView],
81
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
82
+ template: `
83
+ <ScrollView [horizontal]="isHorizontal">
84
+ <symbiote-view testID="a"></symbiote-view>
85
+ <symbiote-view testID="b"></symbiote-view>
86
+ </ScrollView>
87
+ `,
88
+ })(AxisSwitchApp);
89
+
90
+ beforeEach(() => fabric.reset());
91
+ afterEach(() => unmount(ROOT_TAG));
92
+
93
+ describe('Android ScrollView projects content across every axis x refresh combination', () => {
94
+ it('horizontal, no refresh control', async () => {
95
+ mount(ROOT_TAG, HorizontalNoRefreshApp);
96
+ await tick();
97
+ expectCellsProjected();
98
+ });
99
+
100
+ it('horizontal, with a projected refresh control', async () => {
101
+ mount(ROOT_TAG, HorizontalRefreshApp);
102
+ await tick();
103
+ expectCellsProjected();
104
+ });
105
+
106
+ it('vertical, no refresh control', async () => {
107
+ mount(ROOT_TAG, VerticalNoRefreshApp);
108
+ await tick();
109
+ expectCellsProjected();
110
+ });
111
+
112
+ it('survives a runtime vertical -> horizontal axis switch', async () => {
113
+ AxisSwitchApp.horizontal.set(false);
114
+ mount(ROOT_TAG, AxisSwitchApp);
115
+ await tick();
116
+ expectCellsProjected();
117
+
118
+ AxisSwitchApp.horizontal.set(true);
119
+ await tick();
120
+ await tick();
121
+ expectCellsProjected();
122
+ });
123
+ });