@symbiote-native/angular 0.0.0-canary-20260724081123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/babel-linker.cjs +7 -0
  4. package/babel-register-composed.cjs +127 -0
  5. package/bin/symbiote-angular-dev.cjs +93 -0
  6. package/build/angular/anchor-host-registry.d.ts +2 -0
  7. package/build/angular/anchor-host-registry.js +84 -0
  8. package/build/angular/anchor-host-registry.js.map +1 -0
  9. package/build/angular/bootstrap.d.ts +7 -0
  10. package/build/angular/bootstrap.js +15 -0
  11. package/build/angular/bootstrap.js.map +1 -0
  12. package/build/angular/components/activity-indicator/index.android.d.ts +12 -0
  13. package/build/angular/components/activity-indicator/index.android.js +21 -0
  14. package/build/angular/components/activity-indicator/index.android.js.map +1 -0
  15. package/build/angular/components/activity-indicator/index.d.ts +1 -0
  16. package/build/angular/components/activity-indicator/index.ios.d.ts +8 -0
  17. package/build/angular/components/activity-indicator/index.ios.js +21 -0
  18. package/build/angular/components/activity-indicator/index.ios.js.map +1 -0
  19. package/build/angular/components/activity-indicator/index.js +4 -0
  20. package/build/angular/components/activity-indicator/index.js.map +1 -0
  21. package/build/angular/components/activity-indicator/shared.d.ts +67 -0
  22. package/build/angular/components/activity-indicator/shared.js +260 -0
  23. package/build/angular/components/activity-indicator/shared.js.map +1 -0
  24. package/build/angular/components/button.d.ts +63 -0
  25. package/build/angular/components/button.js +332 -0
  26. package/build/angular/components/button.js.map +1 -0
  27. package/build/angular/components/flat-list/index.d.ts +122 -0
  28. package/build/angular/components/flat-list/index.js +688 -0
  29. package/build/angular/components/flat-list/index.js.map +1 -0
  30. package/build/angular/components/image/index.android.d.ts +10 -0
  31. package/build/angular/components/image/index.android.js +59 -0
  32. package/build/angular/components/image/index.android.js.map +1 -0
  33. package/build/angular/components/image/index.d.ts +1 -0
  34. package/build/angular/components/image/index.ios.d.ts +10 -0
  35. package/build/angular/components/image/index.ios.js +59 -0
  36. package/build/angular/components/image/index.ios.js.map +1 -0
  37. package/build/angular/components/image/index.js +3 -0
  38. package/build/angular/components/image/index.js.map +1 -0
  39. package/build/angular/components/image/shared.d.ts +101 -0
  40. package/build/angular/components/image/shared.js +406 -0
  41. package/build/angular/components/image/shared.js.map +1 -0
  42. package/build/angular/components/image-background.d.ts +81 -0
  43. package/build/angular/components/image-background.js +418 -0
  44. package/build/angular/components/image-background.js.map +1 -0
  45. package/build/angular/components/input-accessory-view/index.d.ts +60 -0
  46. package/build/angular/components/input-accessory-view/index.js +251 -0
  47. package/build/angular/components/input-accessory-view/index.js.map +1 -0
  48. package/build/angular/components/keyboard-avoiding-view/index.d.ts +82 -0
  49. package/build/angular/components/keyboard-avoiding-view/index.js +358 -0
  50. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -0
  51. package/build/angular/components/modal/index.d.ts +92 -0
  52. package/build/angular/components/modal/index.js +360 -0
  53. package/build/angular/components/modal/index.js.map +1 -0
  54. package/build/angular/components/pressable/index.d.ts +123 -0
  55. package/build/angular/components/pressable/index.js +420 -0
  56. package/build/angular/components/pressable/index.js.map +1 -0
  57. package/build/angular/components/refresh-control/index.d.ts +86 -0
  58. package/build/angular/components/refresh-control/index.js +342 -0
  59. package/build/angular/components/refresh-control/index.js.map +1 -0
  60. package/build/angular/components/safe-area-view/index.d.ts +57 -0
  61. package/build/angular/components/safe-area-view/index.js +248 -0
  62. package/build/angular/components/safe-area-view/index.js.map +1 -0
  63. package/build/angular/components/scroll-view/index.android.d.ts +21 -0
  64. package/build/angular/components/scroll-view/index.android.js +235 -0
  65. package/build/angular/components/scroll-view/index.android.js.map +1 -0
  66. package/build/angular/components/scroll-view/index.d.ts +3 -0
  67. package/build/angular/components/scroll-view/index.ios.d.ts +9 -0
  68. package/build/angular/components/scroll-view/index.ios.js +86 -0
  69. package/build/angular/components/scroll-view/index.ios.js.map +1 -0
  70. package/build/angular/components/scroll-view/index.js +6 -0
  71. package/build/angular/components/scroll-view/index.js.map +1 -0
  72. package/build/angular/components/scroll-view/projection.d.ts +32 -0
  73. package/build/angular/components/scroll-view/projection.js +334 -0
  74. package/build/angular/components/scroll-view/projection.js.map +1 -0
  75. package/build/angular/components/scroll-view/shared.d.ts +230 -0
  76. package/build/angular/components/scroll-view/shared.js +682 -0
  77. package/build/angular/components/scroll-view/shared.js.map +1 -0
  78. package/build/angular/components/scroll-view/sticky-header.d.ts +40 -0
  79. package/build/angular/components/scroll-view/sticky-header.js +225 -0
  80. package/build/angular/components/scroll-view/sticky-header.js.map +1 -0
  81. package/build/angular/components/section-list/index.d.ts +116 -0
  82. package/build/angular/components/section-list/index.js +641 -0
  83. package/build/angular/components/section-list/index.js.map +1 -0
  84. package/build/angular/components/switch/index.android.d.ts +20 -0
  85. package/build/angular/components/switch/index.android.js +70 -0
  86. package/build/angular/components/switch/index.android.js.map +1 -0
  87. package/build/angular/components/switch/index.d.ts +1 -0
  88. package/build/angular/components/switch/index.ios.d.ts +19 -0
  89. package/build/angular/components/switch/index.ios.js +69 -0
  90. package/build/angular/components/switch/index.ios.js.map +1 -0
  91. package/build/angular/components/switch/index.js +4 -0
  92. package/build/angular/components/switch/index.js.map +1 -0
  93. package/build/angular/components/switch/shared.d.ts +40 -0
  94. package/build/angular/components/switch/shared.js +127 -0
  95. package/build/angular/components/switch/shared.js.map +1 -0
  96. package/build/angular/components/text-input.d.ts +153 -0
  97. package/build/angular/components/text-input.js +599 -0
  98. package/build/angular/components/text-input.js.map +1 -0
  99. package/build/angular/components/touchable/index.d.ts +245 -0
  100. package/build/angular/components/touchable/index.js +1186 -0
  101. package/build/angular/components/touchable/index.js.map +1 -0
  102. package/build/angular/components/touchable-native-feedback/index.d.ts +83 -0
  103. package/build/angular/components/touchable-native-feedback/index.js +371 -0
  104. package/build/angular/components/touchable-native-feedback/index.js.map +1 -0
  105. package/build/angular/components/virtualized-list/directives.d.ts +54 -0
  106. package/build/angular/components/virtualized-list/directives.js +153 -0
  107. package/build/angular/components/virtualized-list/directives.js.map +1 -0
  108. package/build/angular/components/virtualized-list/index.d.ts +253 -0
  109. package/build/angular/components/virtualized-list/index.js +1031 -0
  110. package/build/angular/components/virtualized-list/index.js.map +1 -0
  111. package/build/angular/components/virtualized-section-list/directives.d.ts +37 -0
  112. package/build/angular/components/virtualized-section-list/directives.js +76 -0
  113. package/build/angular/components/virtualized-section-list/directives.js.map +1 -0
  114. package/build/angular/components/virtualized-section-list/index.d.ts +178 -0
  115. package/build/angular/components/virtualized-section-list/index.js +720 -0
  116. package/build/angular/components/virtualized-section-list/index.js.map +1 -0
  117. package/build/angular/components.d.ts +39 -0
  118. package/build/angular/components.js +38 -0
  119. package/build/angular/components.js.map +1 -0
  120. package/build/angular/create-portal/index.d.ts +22 -0
  121. package/build/angular/create-portal/index.js +74 -0
  122. package/build/angular/create-portal/index.js.map +1 -0
  123. package/build/angular/create-tunnel/index.d.ts +34 -0
  124. package/build/angular/create-tunnel/index.js +126 -0
  125. package/build/angular/create-tunnel/index.js.map +1 -0
  126. package/build/angular/descriptor-to-angular/index.d.ts +22 -0
  127. package/build/angular/descriptor-to-angular/index.js +142 -0
  128. package/build/angular/descriptor-to-angular/index.js.map +1 -0
  129. package/build/angular/host-instance/index.d.ts +2 -0
  130. package/build/angular/host-instance/index.js +37 -0
  131. package/build/angular/host-instance/index.js.map +1 -0
  132. package/build/angular/index.d.ts +22 -0
  133. package/build/angular/index.js +46 -0
  134. package/build/angular/index.js.map +1 -0
  135. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +14 -0
  136. package/build/angular/modules/animated/animated-leaf-binder/index.js +91 -0
  137. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +1 -0
  138. package/build/angular/modules/animated/animated-leaf-binder.d.ts +14 -0
  139. package/build/angular/modules/animated/animated-leaf-binder.js +91 -0
  140. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -0
  141. package/build/angular/modules/animated/create-animated-component.d.ts +59 -0
  142. package/build/angular/modules/animated/create-animated-component.js +364 -0
  143. package/build/angular/modules/animated/create-animated-component.js.map +1 -0
  144. package/build/angular/modules/animated/index.d.ts +51 -0
  145. package/build/angular/modules/animated/index.js +59 -0
  146. package/build/angular/modules/animated/index.js.map +1 -0
  147. package/build/angular/modules/app-registry/index.d.ts +7 -0
  148. package/build/angular/modules/app-registry/index.js +23 -0
  149. package/build/angular/modules/app-registry/index.js.map +1 -0
  150. package/build/angular/modules/status-bar/index.d.ts +20 -0
  151. package/build/angular/modules/status-bar/index.js +59 -0
  152. package/build/angular/modules/status-bar/index.js.map +1 -0
  153. package/build/angular/primitives/index.d.ts +63 -0
  154. package/build/angular/primitives/index.js +189 -0
  155. package/build/angular/primitives/index.js.map +1 -0
  156. package/build/angular/primitives/shared.d.ts +88 -0
  157. package/build/angular/primitives/shared.js +171 -0
  158. package/build/angular/primitives/shared.js.map +1 -0
  159. package/build/angular/render/index.d.ts +11 -0
  160. package/build/angular/render/index.js +171 -0
  161. package/build/angular/render/index.js.map +1 -0
  162. package/build/angular/renderer/index.d.ts +37 -0
  163. package/build/angular/renderer/index.js +301 -0
  164. package/build/angular/renderer/index.js.map +1 -0
  165. package/build/angular/services/color-scheme.service.d.ts +16 -0
  166. package/build/angular/services/color-scheme.service.js +31 -0
  167. package/build/angular/services/color-scheme.service.js.map +1 -0
  168. package/build/angular/services/index.d.ts +2 -0
  169. package/build/angular/services/index.js +3 -0
  170. package/build/angular/services/index.js.map +1 -0
  171. package/build/angular/services/window-dimensions.service.d.ts +16 -0
  172. package/build/angular/services/window-dimensions.service.js +42 -0
  173. package/build/angular/services/window-dimensions.service.js.map +1 -0
  174. package/metro-config.cjs +48 -0
  175. package/metro-css-parser.cjs +13 -0
  176. package/package.json +80 -0
  177. package/src/__tests__/responder-change-detection.test.ts +102 -0
  178. package/src/__tests__/responder-nested-cd.test.ts +113 -0
  179. package/src/anchor-host-registry.ts +88 -0
  180. package/src/angular-gaps.test.ts +142 -0
  181. package/src/angular-imports.test.ts +100 -0
  182. package/src/bootstrap.ts +26 -0
  183. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +44 -0
  184. package/src/components/activity-indicator/activity-indicator.test.ts +131 -0
  185. package/src/components/activity-indicator/index.android.ts +16 -0
  186. package/src/components/activity-indicator/index.ios.ts +17 -0
  187. package/src/components/activity-indicator/index.ts +4 -0
  188. package/src/components/activity-indicator/shared.ts +206 -0
  189. package/src/components/button.ts +193 -0
  190. package/src/components/flat-list/column-wrapper-style-class.test.ts +116 -0
  191. package/src/components/flat-list/flat-list-array-style.test.ts +131 -0
  192. package/src/components/flat-list/flat-list-class-style.test.ts +74 -0
  193. package/src/components/flat-list/flat-list-scroll-containment.test.ts +107 -0
  194. package/src/components/flat-list/flat-list.test.ts +99 -0
  195. package/src/components/flat-list/index.ts +574 -0
  196. package/src/components/image/image-class-style.test.ts +46 -0
  197. package/src/components/image/index.android.ts +48 -0
  198. package/src/components/image/index.ios.ts +48 -0
  199. package/src/components/image/index.ts +3 -0
  200. package/src/components/image/shared.ts +479 -0
  201. package/src/components/image-background-class-style.test.ts +48 -0
  202. package/src/components/image-background-style-class.test.ts +79 -0
  203. package/src/components/image-background.ts +270 -0
  204. package/src/components/input-accessory-view/index.ts +185 -0
  205. package/src/components/input-accessory-view/input-accessory-view.test.ts +43 -0
  206. package/src/components/keyboard-avoiding-view/index.ts +303 -0
  207. package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +54 -0
  208. package/src/components/modal/index.ts +299 -0
  209. package/src/components/modal/modal.test.ts +43 -0
  210. package/src/components/pressable/index.ts +373 -0
  211. package/src/components/pressable/pressable.test.ts +85 -0
  212. package/src/components/refresh-control/index.ts +284 -0
  213. package/src/components/refresh-control/refresh-control.test.ts +41 -0
  214. package/src/components/safe-area-view/index.ts +177 -0
  215. package/src/components/safe-area-view/safe-area-view.test.ts +43 -0
  216. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +123 -0
  217. package/src/components/scroll-view/content-container-style-class.test.ts +75 -0
  218. package/src/components/scroll-view/index.android.ts +190 -0
  219. package/src/components/scroll-view/index.ios.ts +79 -0
  220. package/src/components/scroll-view/index.ts +7 -0
  221. package/src/components/scroll-view/projection.ts +414 -0
  222. package/src/components/scroll-view/scroll-view-class-style.test.ts +134 -0
  223. package/src/components/scroll-view/scroll-view-projection.test.ts +194 -0
  224. package/src/components/scroll-view/shared.ts +844 -0
  225. package/src/components/scroll-view/sticky-header.ts +255 -0
  226. package/src/components/section-list/index.ts +445 -0
  227. package/src/components/switch/android-switch-class.test.ts +42 -0
  228. package/src/components/switch/index.android.ts +61 -0
  229. package/src/components/switch/index.ios.ts +60 -0
  230. package/src/components/switch/index.ts +4 -0
  231. package/src/components/switch/shared.ts +197 -0
  232. package/src/components/switch/switch.test.ts +92 -0
  233. package/src/components/text-input-class-style.test.ts +54 -0
  234. package/src/components/text-input.ts +531 -0
  235. package/src/components/touchable/index.ts +629 -0
  236. package/src/components/touchable/touchable.test.ts +102 -0
  237. package/src/components/touchable-native-feedback/index.ts +254 -0
  238. package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +49 -0
  239. package/src/components/virtualized-list/directives.ts +168 -0
  240. package/src/components/virtualized-list/index.ts +992 -0
  241. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +76 -0
  242. package/src/components/virtualized-section-list/directives.ts +90 -0
  243. package/src/components/virtualized-section-list/index.ts +601 -0
  244. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +60 -0
  245. package/src/components.ts +149 -0
  246. package/src/create-portal/create-portal.test.ts +90 -0
  247. package/src/create-portal/index.ts +74 -0
  248. package/src/create-tunnel/create-tunnel.test.ts +120 -0
  249. package/src/create-tunnel/index.ts +136 -0
  250. package/src/descriptor-to-angular/descriptor-outlet.test.ts +94 -0
  251. package/src/descriptor-to-angular/index.ts +190 -0
  252. package/src/host-instance/host-instance.test.ts +49 -0
  253. package/src/host-instance/index.ts +36 -0
  254. package/src/index.ts +261 -0
  255. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +116 -0
  256. package/src/modules/animated/animated-leaf-binder/index.ts +101 -0
  257. package/src/modules/animated/animated-leaf-binder.test.ts +116 -0
  258. package/src/modules/animated/animated-leaf-binder.ts +101 -0
  259. package/src/modules/animated/animated-scroll-view.test.ts +102 -0
  260. package/src/modules/animated/create-animated-component.ts +365 -0
  261. package/src/modules/animated/index.ts +105 -0
  262. package/src/modules/app-registry/app-registry.test.ts +106 -0
  263. package/src/modules/app-registry/index.ts +60 -0
  264. package/src/modules/status-bar/index.ts +57 -0
  265. package/src/modules/status-bar/status-bar.test.ts +50 -0
  266. package/src/primitives/index.ts +119 -0
  267. package/src/primitives/shared.ts +190 -0
  268. package/src/render/index.ts +235 -0
  269. package/src/render/render.test.ts +147 -0
  270. package/src/renderer/index.ts +347 -0
  271. package/src/renderer/renderer.test.ts +203 -0
  272. package/src/services/color-scheme.service.ts +27 -0
  273. package/src/services/index.ts +2 -0
  274. package/src/services/services.test.ts +163 -0
  275. package/src/services/window-dimensions.service.ts +41 -0
  276. package/tsconfig.angular.base.json +30 -0
@@ -0,0 +1,149 @@
1
+ // Angular component surface over Symbiote intrinsics. Public tags (`View`, `Text`) are
2
+ // ergonomic aliases of the primitive hosts in `../primitives`; engine intrinsics (`symbiote-*`)
3
+ // are the actual Angular primitive host components. Every primitive host declares `style` as a
4
+ // real Angular input so RN `StyleProp` arrays/objects are flattened and forwarded through the
5
+ // custom renderer, instead of being misinterpreted by Angular's CSS style engine.
6
+
7
+ export { ActivityIndicator } from './components/activity-indicator';
8
+ export type { IActivityIndicatorProps } from './components/activity-indicator';
9
+ export { Image, setImageSourceResolver } from './components/image';
10
+ export { InputAccessoryView } from './components/input-accessory-view';
11
+ export type { IAngularInputAccessoryViewProps } from './components/input-accessory-view';
12
+ export { Modal } from './components/modal';
13
+ export type {
14
+ IAngularModalProps,
15
+ IModalAnimationType,
16
+ IModalOrientation,
17
+ IModalOrientationChangeEvent,
18
+ IModalPresentationStyle,
19
+ } from './components/modal';
20
+ export type {
21
+ IImageProps,
22
+ IImageSource,
23
+ IImageSourceProp,
24
+ IResizeMode,
25
+ IImageSize,
26
+ IImageCacheStatus,
27
+ } from './components/image';
28
+ export { Pressable } from './components/pressable';
29
+ export type { IAngularPressableProps } from './components/pressable';
30
+ export { SafeAreaView } from './components/safe-area-view';
31
+ export type { IAngularSafeAreaViewProps } from './components/safe-area-view';
32
+ export { Switch } from './components/switch';
33
+ export type { ISwitchProps, ISwitchTrackColor } from './components/switch';
34
+ export { ImageBackground } from './components/image-background';
35
+ export type { IAngularImageBackgroundProps } from './components/image-background';
36
+ export { KeyboardAvoidingView } from './components/keyboard-avoiding-view';
37
+ export type {
38
+ IAngularKeyboardAvoidingViewProps,
39
+ IKeyboardAvoidingBehavior,
40
+ } from './components/keyboard-avoiding-view';
41
+ export { RefreshControl } from './components/refresh-control';
42
+ export type { IAngularRefreshControlProps } from './components/refresh-control';
43
+ export { TextInput } from './components/text-input';
44
+ export type {
45
+ IAngularTextInputProps,
46
+ IEnterKeyHint,
47
+ IInputMode,
48
+ ISubmitBehavior,
49
+ ITextInputHandle,
50
+ ITextInputSelection,
51
+ } from './components/text-input';
52
+ export { TouchableNativeFeedback } from './components/touchable-native-feedback';
53
+ export type {
54
+ IAngularTouchableNativeFeedbackProps,
55
+ INativeFeedbackBackground,
56
+ IRippleBackground,
57
+ IThemeAttrBackground,
58
+ } from './components/touchable-native-feedback';
59
+ export {
60
+ TouchableHighlight,
61
+ TouchableOpacity,
62
+ TouchableWithoutFeedback,
63
+ } from './components/touchable';
64
+ export type {
65
+ IAngularTouchableHighlightProps,
66
+ IAngularTouchableOpacityProps,
67
+ IAngularTouchableWithoutFeedbackProps,
68
+ } from './components/touchable';
69
+ export { ScrollView, ScrollViewStickyHeader } from './components/scroll-view';
70
+ export type {
71
+ IAngularScrollViewProps,
72
+ IScrollViewHandle,
73
+ IStickyHeaderComponentType,
74
+ } from './components/scroll-view';
75
+ export { Button } from './components/button';
76
+ export type { IButtonProps } from './components/button';
77
+ export {
78
+ VirtualizedList,
79
+ VListEmptyDirective,
80
+ VListFooterDirective,
81
+ VListHeaderDirective,
82
+ VListItemDirective,
83
+ VListSeparatorDirective,
84
+ } from './components/virtualized-list';
85
+ export type {
86
+ ICellLayout,
87
+ ISeparatorProps,
88
+ ISeparators,
89
+ IViewabilityConfig,
90
+ IViewabilityConfigCallbackPair,
91
+ IViewableItemsChangedInfo,
92
+ IViewToken,
93
+ IVirtualizedListHandle,
94
+ IVirtualizedListProps,
95
+ IVListItemContext,
96
+ IVListSeparatorContext,
97
+ } from './components/virtualized-list';
98
+ export { FlatList } from './components/flat-list';
99
+ export type { IFlatListHandle, IFlatListProps } from './components/flat-list';
100
+ export {
101
+ VirtualizedSectionList,
102
+ VSectionFooterDirective,
103
+ VSectionHeaderDirective,
104
+ VSectionItemDirective,
105
+ VSectionSeparatorDirective,
106
+ } from './components/virtualized-section-list';
107
+ export type {
108
+ ISection,
109
+ IVirtualizedSectionListHandle,
110
+ IVirtualizedSectionListProps,
111
+ IVSectionContext,
112
+ IVSectionItemContext,
113
+ } from './components/virtualized-section-list';
114
+ export { SectionList } from './components/section-list';
115
+ export type { ISectionListHandle, ISectionListProps } from './components/section-list';
116
+
117
+ // Re-export primitive hosts so composed components can import them from the public barrel too.
118
+ export {
119
+ ViewHost,
120
+ ViewHost as View,
121
+ TextHost,
122
+ TextHost as Text,
123
+ ImageHost,
124
+ ScrollViewHost,
125
+ ScrollContentView,
126
+ HorizontalScrollView,
127
+ HorizontalScrollContentView,
128
+ TextInputHost,
129
+ MultilineTextInputHost,
130
+ SwitchHost,
131
+ ActivityIndicatorHost,
132
+ SafeAreaViewHost,
133
+ ModalHost,
134
+ RefreshControlHost,
135
+ InputAccessoryViewHost,
136
+ SymbioteHostPropsDirective,
137
+ // Exported so an OUT-OF-PACKAGE composed component (e.g. @symbiote-native/slider's Angular wrapper,
138
+ // itself listed in ANCHOR_HOST_COMPONENTS) can merge its own anchor host's class-derived style
139
+ // the same way every in-package composed component does — see anchorHostStyle's doc comment.
140
+ anchorHostStyle,
141
+ // Same reasoning, for a component whose merged style is reassigned unconditionally every CD
142
+ // tick (rather than read once inside a props-object getter) — see stableAnchorStyle's doc
143
+ // comment for the free-running-CD-loop it prevents.
144
+ stableAnchorStyle,
145
+ } from './primitives/index';
146
+
147
+ // Public ergonomic aliases: <View> and <Text> resolve directly to the primitive hosts
148
+ // (no extra Angular bookkeeping anchor), while the internal symbiote-* selectors remain
149
+ // available for composed adapter templates.
@@ -0,0 +1,90 @@
1
+ // Proves createPortal (create-portal.ts) paints content into an already-mounted destination
2
+ // WITHIN THE SAME SURFACE — mirrors adapters/react/src/create-portal.test.tsx's "same surface,
3
+ // already-mounted target" case (Angular has no cross-surface equivalent of this primitive;
4
+ // createTunnel covers that, see create-tunnel.test.ts).
5
+
6
+ import '@angular/compiler';
7
+ import { Component, signal } from '@angular/core';
8
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
9
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
10
+
11
+ import { mount, unmount } from '../render';
12
+ import { ViewHost, TextHost } from '../primitives';
13
+ import { PortalDirective, PortalOutletDirective } from './index';
14
+
15
+ const ROOT_TAG = 930;
16
+
17
+ const fabric = installFabric();
18
+ const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
19
+ const settle = async (): Promise<void> => {
20
+ await tick();
21
+ await tick();
22
+ };
23
+
24
+ let capturedHost: HostApp | undefined;
25
+
26
+ @Component({
27
+ selector: 'symbiote-portal-host-app',
28
+ standalone: true,
29
+ imports: [ViewHost, TextHost, PortalDirective, PortalOutletDirective],
30
+ template: `
31
+ <View>
32
+ <View portalOutlet #overlayHost="portalOutlet" testID="overlay-host"></View>
33
+ @if (visible()) {
34
+ <View *portal="overlayHost"><Text>portaled content</Text></View>
35
+ }
36
+ </View>
37
+ `,
38
+ })
39
+ class HostApp {
40
+ readonly visible = signal(false);
41
+
42
+ constructor() {
43
+ // Captures the live component instance so the test can drive its signals after mount.
44
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
45
+ capturedHost = this;
46
+ }
47
+ }
48
+
49
+ beforeEach(() => {
50
+ capturedHost = undefined;
51
+ fabric.reset();
52
+ });
53
+ afterEach(() => unmount(ROOT_TAG));
54
+
55
+ function walk(nodes: IFakeNode[], visit: (node: IFakeNode) => void): void {
56
+ for (const node of nodes) {
57
+ visit(node);
58
+ walk(node.children, visit);
59
+ }
60
+ }
61
+
62
+ function findText(text: string): IFakeNode | undefined {
63
+ let found: IFakeNode | undefined;
64
+ walk(fabric.committed, node => {
65
+ if (node.viewName === 'RCTRawText' && node.props.text === text) found = node;
66
+ });
67
+ return found;
68
+ }
69
+
70
+ describe('createPortal (Angular) — same-surface delivery', () => {
71
+ it('renders nothing until the portal is toggled on', async () => {
72
+ mount(ROOT_TAG, HostApp);
73
+ await settle();
74
+ expect(findText('portaled content')).toBeUndefined();
75
+ });
76
+
77
+ it('paints content into the outlet once toggled on, and removes it once toggled off', async () => {
78
+ mount(ROOT_TAG, HostApp);
79
+ await settle();
80
+
81
+ if (!capturedHost) throw new Error('host was not captured');
82
+ capturedHost.visible.set(true);
83
+ await settle();
84
+ expect(findText('portaled content'), 'painted once the portal renders').toBeDefined();
85
+
86
+ capturedHost.visible.set(false);
87
+ await settle();
88
+ expect(findText('portaled content'), 'gone once the portal unmounts').toBeUndefined();
89
+ });
90
+ });
@@ -0,0 +1,74 @@
1
+ // createPortal for @symbiote-native/angular — the Angular twin of the React/Vue same-surface portal
2
+ // (create-portal.ts / Teleport in runtime-helpers.ts). Scope is identical: the target must be
3
+ // an already-mounted location WITHIN THE SAME SURFACE as the portal's call site — moving content
4
+ // across independently-mounted surfaces has no safe host-level primitive to hook into, so
5
+ // createTunnel (create-tunnel.ts) covers that cross-surface case instead.
6
+ //
7
+ // React/Vue implement this as a thin validating wrapper around a mechanism the FRAMEWORK
8
+ // ITSELF already has (react-reconciler's Fiber-level HostPortal, Vue's own <Teleport>) — all
9
+ // the actual node-moving is handled generically by the engine's insert/remove, which those
10
+ // frameworks already call. Angular has no such built-in: there is no `@angular/cdk` dependency
11
+ // here (core-only, see package.json), and physically moving an EmbeddedView's already-created
12
+ // root nodes via Renderer2 after the fact is NOT safe — Angular's own view-destroy path removes
13
+ // a view's nodes from wherever ITS OWN bookkeeping thinks they live (the container's insertion
14
+ // point), not from wherever a node was manually moved to afterwards, so a raw Renderer2 move
15
+ // would desync Angular's internals from the retained tree.
16
+ //
17
+ // PortalDirective is a STRUCTURAL directive (`*portal="overlayHost"`), not a component that
18
+ // takes a separate `<ng-template>` + `[content]` binding — that two-step reads as foreign to
19
+ // anyone used to `*ngIf`/`*ngFor`/`*ngTemplateOutlet`, where the directive sits directly on the
20
+ // content and its TemplateRef comes from injection, not a passed-in reference. `*portal="x"`
21
+ // desugars the same way `*ngIf` does: Angular wraps the host element in an `<ng-template>` and
22
+ // injects that template's own TemplateRef into the directive automatically.
23
+ //
24
+ // The safe, fully-public-API mechanism: create the embedded view DIRECTLY inside a
25
+ // ViewContainerRef anchored at the destination, so there is nothing to move at all. That
26
+ // ViewContainerRef has to come from somewhere in the destination's own template — hence
27
+ // `PortalOutletDirective`, a marker placed on the target host (`<View portalOutlet
28
+ // #overlayHost="portalOutlet">`) purely to expose its ViewContainerRef, the same
29
+ // export-as-template-variable idiom `#form="ngForm"` uses. This also replaces the
30
+ // `isSymbioteNode` runtime guard React/Vue need: there `to` is an arbitrary JS value at
31
+ // runtime (a wrong ref, a string, a plain object all type-check as `any`/`unknown` until
32
+ // validated), but here `to` is typed as `PortalOutletDirective` — the only way to construct
33
+ // one is Angular's own template compiler resolving a template reference variable, so
34
+ // `strictTemplates` rejects anything else at compile time and there is nothing left to guard
35
+ // against at runtime.
36
+
37
+ import {
38
+ Directive,
39
+ inject,
40
+ Input,
41
+ TemplateRef,
42
+ ViewContainerRef,
43
+ type EmbeddedViewRef,
44
+ type OnChanges,
45
+ type OnDestroy,
46
+ } from '@angular/core';
47
+
48
+ /** Marks the destination for `*portal` — place it on whichever already-mounted host should
49
+ * paint the portaled content (e.g. a persistent overlay-host View near the app root), then
50
+ * export it to a template variable and pass that variable as `*portal`'s target. */
51
+ @Directive({ selector: '[portalOutlet]', standalone: true, exportAs: 'portalOutlet' })
52
+ export class PortalOutletDirective {
53
+ readonly viewContainerRef = inject(ViewContainerRef);
54
+ }
55
+
56
+ /** `*portal="overlayHost"` — renders the host element into whichever `PortalOutletDirective`
57
+ * `overlayHost` refers to, same surface only (see the file header). Combine with `@if` for
58
+ * conditional visibility, exactly like `*ngIf`. */
59
+ @Directive({ selector: '[portal]', standalone: true })
60
+ export class PortalDirective implements OnChanges, OnDestroy {
61
+ @Input({ required: true }) portal!: PortalOutletDirective;
62
+
63
+ private readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
64
+ private viewRef: EmbeddedViewRef<unknown> | null = null;
65
+
66
+ ngOnChanges(): void {
67
+ this.viewRef?.destroy();
68
+ this.viewRef = this.portal.viewContainerRef.createEmbeddedView(this.templateRef);
69
+ }
70
+
71
+ ngOnDestroy(): void {
72
+ this.viewRef?.destroy();
73
+ }
74
+ }
@@ -0,0 +1,120 @@
1
+ // Proves createTunnel (create-tunnel.ts) actually delivers across two GENUINELY separate,
2
+ // independently-mounted SymbioteSurfaces — the same proof React's/Vue's create-tunnel tests
3
+ // give, via two real `mount()` calls on different rootTags. Mirrors
4
+ // adapters/react/src/create-tunnel.test.tsx and adapters/vue/src/create-tunnel.test.ts.
5
+
6
+ import '@angular/compiler';
7
+ import { Component } from '@angular/core';
8
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
9
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
10
+
11
+ import { mount, unmount } from '../render';
12
+ import { ViewHost, TextHost } from '../primitives';
13
+ import { createTunnel, TunnelInDirective, TunnelOut } from './index';
14
+
15
+ const SOURCE_TAG = 920;
16
+ const TARGET_TAG = 921;
17
+
18
+ const fabric = installFabric();
19
+ const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
20
+ const settle = async (): Promise<void> => {
21
+ await tick();
22
+ await tick();
23
+ };
24
+
25
+ beforeEach(() => fabric.reset());
26
+ afterEach(() => {
27
+ unmount(SOURCE_TAG);
28
+ unmount(TARGET_TAG);
29
+ });
30
+
31
+ function walk(nodes: IFakeNode[], visit: (node: IFakeNode) => void): void {
32
+ for (const node of nodes) {
33
+ visit(node);
34
+ walk(node.children, visit);
35
+ }
36
+ }
37
+
38
+ function findText(text: string): IFakeNode | undefined {
39
+ let found: IFakeNode | undefined;
40
+ walk(fabric.committed, node => {
41
+ if (node.viewName === 'RCTRawText' && node.props.text === text) found = node;
42
+ });
43
+ return found;
44
+ }
45
+
46
+ describe('createTunnel (Angular) — genuine cross-surface delivery', () => {
47
+ it('paints content registered by surface A on surface B, a DIFFERENT mounted surface', async () => {
48
+ const tunnel = createTunnel();
49
+
50
+ @Component({
51
+ selector: 'symbiote-tunnel-source-app',
52
+ standalone: true,
53
+ imports: [TextHost, TunnelInDirective],
54
+ template: '<Text *tunnelIn="tunnel">ported across surfaces</Text>',
55
+ })
56
+ class SourceApp {
57
+ readonly tunnel = tunnel;
58
+ }
59
+
60
+ @Component({
61
+ selector: 'symbiote-tunnel-target-app',
62
+ standalone: true,
63
+ imports: [ViewHost, TunnelOut],
64
+ template: '<View><tunnel-out [tunnel]="tunnel" /></View>',
65
+ })
66
+ class TargetApp {
67
+ readonly tunnel = tunnel;
68
+ }
69
+
70
+ // Surface A registers, fully independently, before surface B ever mounts.
71
+ mount(SOURCE_TAG, SourceApp);
72
+ // Surface B mounts SEPARATELY (its own rootTag, its own SymbioteSurface) and reads the
73
+ // tunnel via its own TunnelOut — no ref, no rootTag lookup, no shared Fabric node at all.
74
+ mount(TARGET_TAG, TargetApp);
75
+ await settle();
76
+
77
+ // fake-fabric's `committed` is last-write-wins across rootTags (core/test-utils
78
+ // limitation, not the engine's), so after mounting B second it reflects B's own tree.
79
+ expect(
80
+ findText('ported across surfaces'),
81
+ 'content is present in the LAST-committed tree (surface B)',
82
+ ).toBeDefined();
83
+ });
84
+
85
+ it('removes the content from the target once the source unmounts', async () => {
86
+ const tunnel = createTunnel();
87
+
88
+ @Component({
89
+ selector: 'symbiote-tunnel-source-app-2',
90
+ standalone: true,
91
+ imports: [TextHost, TunnelInDirective],
92
+ template: '<Text *tunnelIn="tunnel">still here</Text>',
93
+ })
94
+ class SourceApp {
95
+ readonly tunnel = tunnel;
96
+ }
97
+
98
+ @Component({
99
+ selector: 'symbiote-tunnel-target-app-2',
100
+ standalone: true,
101
+ imports: [ViewHost, TunnelOut],
102
+ template: '<View><tunnel-out [tunnel]="tunnel" /></View>',
103
+ })
104
+ class TargetApp {
105
+ readonly tunnel = tunnel;
106
+ }
107
+
108
+ mount(SOURCE_TAG, SourceApp);
109
+ mount(TARGET_TAG, TargetApp);
110
+ await settle();
111
+ expect(findText('still here'), 'present while the source is mounted').toBeDefined();
112
+
113
+ // Tearing down surface A destroys TunnelInDirective, whose ngOnDestroy unregisters from
114
+ // the shared store; surface B's TunnelOut effect reacts on its own next tick and destroys
115
+ // its view.
116
+ unmount(SOURCE_TAG);
117
+ await settle();
118
+ expect(findText('still here'), 'gone from surface B after the source unmounts').toBeUndefined();
119
+ });
120
+ });
@@ -0,0 +1,136 @@
1
+ // createTunnel for @symbiote-native/angular — the Angular twin of the React/Vue cross-surface tunnel
2
+ // (adapters/react/src/create-tunnel.tsx, adapters/vue/src/create-tunnel.ts — read their
3
+ // headers for the full "why not just extend the portal across surfaces" rationale; same prior
4
+ // art: pmndrs/tunnel-rat, facebook/react#17147). `TunnelOut` lives in whichever component should
5
+ // PAINT the content; its OWN read of the shared store drives that surface's own normal render
6
+ // cycle — no cross-surface reach-in, no rootTag lookup, works whether TunnelIn and TunnelOut
7
+ // share a surface or not.
8
+ //
9
+ // Angular cannot mirror React's/Vue's shape verbatim: both of those build a FRESH `In`/`Out`
10
+ // pair per `createTunnel()` call (a closure-scoped class in Vue, closure-scoped functions in
11
+ // React). Angular components can't be synthesized at runtime — there is no JIT compiler under
12
+ // Metro/Hermes (see modules/animated/create-animated-component.ts's header for the same
13
+ // constraint). So here `createTunnel()` returns only a plain reactive STORE (a signal);
14
+ // `TunnelInDirective` and `TunnelOut` are ONE static, pre-authored, AOT-compilable pair,
15
+ // parameterized by that store through an input — same relationship `VListOutletDirective` has
16
+ // to the per-cell template it stamps, generalized to an open-ended, changing SET of templates.
17
+ //
18
+ // TunnelInDirective is a STRUCTURAL directive (`*tunnelIn="overlayTunnel"`), not a component
19
+ // that takes a separate `<ng-template>` + `[content]` binding — matching `*portal` (see
20
+ // create-portal.ts's header for why that reads as native Angular and the two-step form
21
+ // doesn't). `TunnelOut` stays a plain component (`<tunnel-out>`) — it's a rendering SLOT, the
22
+ // same shape as Angular's own `<router-outlet>`, not content that needs a structural directive.
23
+ //
24
+ // App code stays fully declarative — no ViewContainerRef/imperative rendering — by putting
25
+ // `*tunnelIn` directly on the portable content, the same way `*ngIf` sits directly on an
26
+ // element instead of wrapping it in a named `<ng-template>`:
27
+ // @if (toastVisible) {
28
+ // <View *tunnelIn="overlayTunnel"><Text>…</Text></View>
29
+ // }
30
+ // …
31
+ // <tunnel-out [tunnel]="overlayTunnel" />
32
+ // No `context` param: unlike a `*ngFor` cell template, tunneled content needs no per-registration
33
+ // data — it already closes over whatever signals/fields its OWN declaring component exposes, and
34
+ // Angular's own change detection keeps that live once the embedded view exists, the same way any
35
+ // other template does.
36
+
37
+ import {
38
+ Component,
39
+ Directive,
40
+ effect,
41
+ inject,
42
+ Injector,
43
+ Input,
44
+ signal,
45
+ TemplateRef,
46
+ ViewContainerRef,
47
+ type EmbeddedViewRef,
48
+ type OnDestroy,
49
+ type OnInit,
50
+ type Signal,
51
+ } from '@angular/core';
52
+
53
+ export interface ITunnelStore {
54
+ readonly entries: Signal<ReadonlyMap<number, TemplateRef<unknown>>>;
55
+ register(id: number, templateRef: TemplateRef<unknown>): void;
56
+ unregister(id: number): void;
57
+ }
58
+
59
+ export function createTunnel(): ITunnelStore {
60
+ const store = signal<ReadonlyMap<number, TemplateRef<unknown>>>(new Map());
61
+
62
+ return {
63
+ entries: store.asReadonly(),
64
+ register(id, templateRef) {
65
+ store.update(map => new Map(map).set(id, templateRef));
66
+ },
67
+ unregister(id) {
68
+ store.update(map => {
69
+ if (!map.has(id)) return map;
70
+ const next = new Map(map);
71
+ next.delete(id);
72
+ return next;
73
+ });
74
+ },
75
+ };
76
+ }
77
+
78
+ let nextTunnelEntryId = 0;
79
+
80
+ /** `*tunnelIn="overlayTunnel"` — registers the host element under `overlayTunnel` from
81
+ * wherever it's mounted, any surface. Combine with `@if` for conditional visibility, exactly
82
+ * like `*ngIf`. */
83
+ @Directive({ selector: '[tunnelIn]', standalone: true })
84
+ export class TunnelInDirective implements OnInit, OnDestroy {
85
+ @Input({ required: true }) tunnelIn!: ITunnelStore;
86
+
87
+ private readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
88
+ private readonly id = nextTunnelEntryId++;
89
+
90
+ ngOnInit(): void {
91
+ this.tunnelIn.register(this.id, this.templateRef);
92
+ }
93
+
94
+ ngOnDestroy(): void {
95
+ this.tunnelIn.unregister(this.id);
96
+ }
97
+ }
98
+
99
+ /** Renders everything currently registered on `tunnel`, in registration order. Mount this in
100
+ * the component that should actually paint the content — the tunnel's own `<router-outlet>`. */
101
+ @Component({
102
+ selector: 'tunnel-out',
103
+ standalone: true,
104
+ template: '',
105
+ })
106
+ export class TunnelOut implements OnInit, OnDestroy {
107
+ @Input({ required: true }) tunnel!: ITunnelStore;
108
+
109
+ private readonly viewContainerRef = inject(ViewContainerRef);
110
+ private readonly injector = inject(Injector);
111
+ private readonly views = new Map<number, EmbeddedViewRef<unknown>>();
112
+ private stopEffect: (() => void) | undefined;
113
+
114
+ ngOnInit(): void {
115
+ const ref = effect(() => this.sync(this.tunnel.entries()), { injector: this.injector });
116
+ this.stopEffect = () => ref.destroy();
117
+ }
118
+
119
+ ngOnDestroy(): void {
120
+ this.stopEffect?.();
121
+ for (const view of this.views.values()) view.destroy();
122
+ this.views.clear();
123
+ }
124
+
125
+ private sync(entries: ReadonlyMap<number, TemplateRef<unknown>>): void {
126
+ for (const [id, view] of this.views) {
127
+ if (entries.has(id)) continue;
128
+ view.destroy();
129
+ this.views.delete(id);
130
+ }
131
+ for (const [id, templateRef] of entries) {
132
+ if (this.views.has(id)) continue;
133
+ this.views.set(id, this.viewContainerRef.createEmbeddedView(templateRef));
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,94 @@
1
+ import '@angular/compiler';
2
+ import { Component, signal } from '@angular/core';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+ import { el, txt, type IDescriptor } from '@symbiote-native/components';
5
+ import { installFabric } from '@symbiote-native/test-utils';
6
+
7
+ import { mount, unmount } from '../render';
8
+ import { DescriptorOutlet } from './index.ts';
9
+
10
+ const ROOT_TAG = 904;
11
+ const fabric = installFabric();
12
+
13
+ let capturedHost: DescriptorOutletHost | undefined;
14
+
15
+ async function flushAngular(): Promise<void> {
16
+ await Promise.resolve();
17
+ await new Promise<void>(resolve => setTimeout(resolve, 0));
18
+ }
19
+
20
+ function currentOutletChild() {
21
+ const child = fabric.appRoot().children[0];
22
+ if (!child) throw new Error('descriptor outlet rendered no root child');
23
+ return child;
24
+ }
25
+
26
+ @Component({
27
+ selector: 'symbiote-descriptor-outlet-host',
28
+ standalone: true,
29
+ imports: [DescriptorOutlet],
30
+ template: '<symbiote-descriptor-outlet [node]="node()" />',
31
+ })
32
+ class DescriptorOutletHost {
33
+ readonly node = signal<IDescriptor>(
34
+ el('symbiote-view', { testID: 'root', style: { width: 10 } }, [txt({}, ['hello'])]),
35
+ );
36
+
37
+ constructor() {
38
+ // Captures the live component instance so the test can drive its signals after mount.
39
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
40
+ capturedHost = this;
41
+ }
42
+ }
43
+
44
+ beforeEach(() => {
45
+ capturedHost = undefined;
46
+ fabric.reset();
47
+ });
48
+ afterEach(() => unmount(ROOT_TAG));
49
+
50
+ describe('DescriptorOutlet', () => {
51
+ it('renders a Descriptor tree through Renderer2', async () => {
52
+ mount(ROOT_TAG, DescriptorOutletHost);
53
+ await flushAngular();
54
+
55
+ expect(fabric.serialize(fabric.appRoot().children)).toBe(
56
+ 'RCTView(RCTText(RCTRawText "hello"))',
57
+ );
58
+ const root = currentOutletChild();
59
+ expect(root.props.testID).toBe('root');
60
+ expect(root.props.width).toBe(10);
61
+ });
62
+
63
+ it('does not recommit a structurally identical descriptor through anchor flattening', async () => {
64
+ mount(ROOT_TAG, DescriptorOutletHost);
65
+ await flushAngular();
66
+
67
+ const completeRootBefore = fabric.counts.completeRoot;
68
+
69
+ capturedHost?.node.set(
70
+ el('symbiote-view', { testID: 'root', style: { width: 10 } }, [txt({}, ['hello'])]),
71
+ );
72
+ await flushAngular();
73
+
74
+ expect(fabric.counts.completeRoot).toBe(completeRootBefore);
75
+ });
76
+
77
+ it('patches same type/key descriptors without recreating the Fabric node', async () => {
78
+ mount(ROOT_TAG, DescriptorOutletHost);
79
+ await flushAngular();
80
+
81
+ const before = currentOutletChild();
82
+ const createdBefore = fabric.counts.createNode;
83
+
84
+ capturedHost?.node.set(
85
+ el('symbiote-view', { testID: 'root', style: { width: 20 } }, [txt({}, ['hello'])]),
86
+ );
87
+ await flushAngular();
88
+
89
+ const after = currentOutletChild();
90
+ expect(fabric.counts.createNode).toBe(createdBefore);
91
+ expect(after.instanceHandle).toBe(before.instanceHandle);
92
+ expect(after.props.width).toBe(20);
93
+ });
94
+ });