@symbiote-native/angular 1.0.0 → 2.0.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 (248) hide show
  1. package/README.md +13 -10
  2. package/babel-register-composed.cjs +41 -19
  3. package/bin/symbiote-angular-dev.cjs +16 -4
  4. package/build/angular/anchor-host-registry.js +1 -17
  5. package/build/angular/anchor-host-registry.js.map +1 -1
  6. package/build/angular/change-detection-flush.d.ts +31 -0
  7. package/build/angular/change-detection-flush.js +110 -0
  8. package/build/angular/change-detection-flush.js.map +1 -0
  9. package/build/angular/components/activity-indicator-props.d.ts +2 -0
  10. package/build/angular/components/activity-indicator-props.js +2 -0
  11. package/build/angular/components/activity-indicator-props.js.map +1 -0
  12. package/build/angular/components/button-props.d.ts +2 -0
  13. package/build/angular/components/button-props.js +2 -0
  14. package/build/angular/components/button-props.js.map +1 -0
  15. package/build/angular/components/flat-list/index.d.ts +2 -1
  16. package/build/angular/components/flat-list/index.js +22 -10
  17. package/build/angular/components/flat-list/index.js.map +1 -1
  18. package/build/angular/components/image-background-props.d.ts +6 -0
  19. package/build/angular/components/image-background-props.js +2 -0
  20. package/build/angular/components/image-background-props.js.map +1 -0
  21. package/build/angular/components/{image/shared.d.ts → image-shared.d.ts} +1 -1
  22. package/build/angular/components/{image/shared.js → image-shared.js} +7 -2
  23. package/build/angular/components/image-shared.js.map +1 -0
  24. package/build/angular/components/input-accessory-view-props.d.ts +7 -0
  25. package/build/angular/components/input-accessory-view-props.js +9 -0
  26. package/build/angular/components/input-accessory-view-props.js.map +1 -0
  27. package/build/angular/components/keyboard-avoiding-view/index.js +11 -17
  28. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  29. package/build/angular/components/modal/index.d.ts +1 -0
  30. package/build/angular/components/modal/index.js +19 -12
  31. package/build/angular/components/modal/index.js.map +1 -1
  32. package/build/angular/components/pressable-props.d.ts +32 -0
  33. package/build/angular/components/pressable-props.js +11 -0
  34. package/build/angular/components/pressable-props.js.map +1 -0
  35. package/build/angular/components/refresh-control-props.d.ts +16 -0
  36. package/build/angular/components/refresh-control-props.js +14 -0
  37. package/build/angular/components/refresh-control-props.js.map +1 -0
  38. package/build/angular/components/safe-area-view-props.d.ts +7 -0
  39. package/build/angular/components/safe-area-view-props.js +11 -0
  40. package/build/angular/components/safe-area-view-props.js.map +1 -0
  41. package/build/angular/components/scroll-view-props.d.ts +76 -0
  42. package/build/angular/components/scroll-view-props.js +18 -0
  43. package/build/angular/components/scroll-view-props.js.map +1 -0
  44. package/build/angular/components/text-input-props.d.ts +45 -0
  45. package/build/angular/components/text-input-props.js +18 -0
  46. package/build/angular/components/text-input-props.js.map +1 -0
  47. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +6 -0
  48. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js +2 -0
  49. package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js.map +1 -0
  50. package/build/angular/components/touchable-props.d.ts +14 -0
  51. package/build/angular/components/touchable-props.js +13 -0
  52. package/build/angular/components/touchable-props.js.map +1 -0
  53. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -0
  54. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js +2 -0
  55. package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js.map +1 -0
  56. package/build/angular/components/virtualized-list/index.d.ts +5 -1
  57. package/build/angular/components/virtualized-list/index.js +366 -280
  58. package/build/angular/components/virtualized-list/index.js.map +1 -1
  59. package/build/angular/components.d.ts +20 -26
  60. package/build/angular/components.js +12 -14
  61. package/build/angular/components.js.map +1 -1
  62. package/build/angular/create-portal/index.d.ts +2 -2
  63. package/build/angular/create-portal/index.js +5 -5
  64. package/build/angular/create-tunnel/index.js +1 -1
  65. package/build/angular/element-props.d.ts +53 -0
  66. package/build/angular/element-props.js +2 -0
  67. package/build/angular/element-props.js.map +1 -0
  68. package/build/angular/elements.d.ts +528 -0
  69. package/build/angular/elements.js +1444 -0
  70. package/build/angular/elements.js.map +1 -0
  71. package/build/angular/host-instance/index.js +1 -1
  72. package/build/angular/index.d.ts +4 -2
  73. package/build/angular/index.js +15 -1
  74. package/build/angular/index.js.map +1 -1
  75. package/build/angular/modules/animated/create-animated-component.d.ts +5 -6
  76. package/build/angular/modules/animated/create-animated-component.js +60 -73
  77. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  78. package/build/angular/modules/animated/index.js +1 -1
  79. package/build/angular/modules/animated/index.js.map +1 -1
  80. package/build/angular/modules/image/index.d.ts +3 -0
  81. package/build/angular/modules/image/index.js +16 -0
  82. package/build/angular/modules/image/index.js.map +1 -0
  83. package/build/angular/primitives/index.d.ts +18 -18
  84. package/build/angular/primitives/index.js +40 -36
  85. package/build/angular/primitives/index.js.map +1 -1
  86. package/build/angular/primitives/shared.d.ts +2 -3
  87. package/build/angular/primitives/shared.js +8 -53
  88. package/build/angular/primitives/shared.js.map +1 -1
  89. package/build/angular/register.d.ts +1 -1
  90. package/build/angular/register.js +12 -31
  91. package/build/angular/register.js.map +1 -1
  92. package/build/angular/render/index.js +3 -3
  93. package/build/angular/render/index.js.map +1 -1
  94. package/build/angular/renderer/index.js +113 -67
  95. package/build/angular/renderer/index.js.map +1 -1
  96. package/build/angular/renderer/value-change.d.ts +2 -0
  97. package/build/angular/renderer/value-change.js +9 -0
  98. package/build/angular/renderer/value-change.js.map +1 -0
  99. package/metro-config.cjs +23 -5
  100. package/metro-css-parser.cjs +2 -1
  101. package/package.json +5 -14
  102. package/src/anchor-host-registry.ts +1 -17
  103. package/src/change-detection-flush.ts +120 -0
  104. package/src/components/activity-indicator-props.ts +21 -0
  105. package/src/components/button-props.ts +26 -0
  106. package/src/components/flat-list/index.ts +12 -4
  107. package/src/components/image-background-props.ts +24 -0
  108. package/src/components/{image/shared.ts → image-shared.ts} +6 -1
  109. package/src/components/input-accessory-view-props.ts +21 -0
  110. package/src/components/keyboard-avoiding-view/index.ts +6 -9
  111. package/src/components/modal/index.ts +15 -7
  112. package/src/components/pressable-props.ts +73 -0
  113. package/src/components/refresh-control-props.ts +43 -0
  114. package/src/components/safe-area-view-props.ts +28 -0
  115. package/src/components/scroll-view-props.ts +109 -0
  116. package/src/components/text-input-props.ts +72 -0
  117. package/src/components/touchable-native-feedback/touchable-native-feedback-props.ts +23 -0
  118. package/src/components/touchable-props.ts +30 -0
  119. package/src/components/touchable-without-feedback/touchable-without-feedback-props.ts +21 -0
  120. package/src/components/virtualized-list/index.ts +239 -156
  121. package/src/components.ts +84 -35
  122. package/src/create-portal/index.ts +5 -5
  123. package/src/create-tunnel/index.ts +1 -1
  124. package/src/diagnostics.ts +1 -1
  125. package/src/element-props.ts +90 -0
  126. package/src/elements.ts +919 -0
  127. package/src/host-instance/index.ts +1 -1
  128. package/src/index.ts +52 -15
  129. package/src/modules/animated/create-animated-component.ts +50 -55
  130. package/src/modules/animated/index.ts +1 -1
  131. package/src/modules/image/index.ts +18 -0
  132. package/src/primitives/index.ts +22 -18
  133. package/src/primitives/shared.ts +11 -44
  134. package/src/register.ts +12 -40
  135. package/src/render/index.ts +3 -3
  136. package/src/renderer/index.ts +122 -73
  137. package/src/renderer/value-change.ts +8 -0
  138. package/babel-lower-host-primitives.cjs +0 -636
  139. package/build/angular/components/activity-indicator/index.android.d.ts +0 -13
  140. package/build/angular/components/activity-indicator/index.android.js +0 -29
  141. package/build/angular/components/activity-indicator/index.android.js.map +0 -1
  142. package/build/angular/components/activity-indicator/index.d.ts +0 -1
  143. package/build/angular/components/activity-indicator/index.ios.d.ts +0 -9
  144. package/build/angular/components/activity-indicator/index.ios.js +0 -26
  145. package/build/angular/components/activity-indicator/index.ios.js.map +0 -1
  146. package/build/angular/components/activity-indicator/index.js +0 -4
  147. package/build/angular/components/activity-indicator/index.js.map +0 -1
  148. package/build/angular/components/activity-indicator/shared.d.ts +0 -67
  149. package/build/angular/components/activity-indicator/shared.js +0 -260
  150. package/build/angular/components/activity-indicator/shared.js.map +0 -1
  151. package/build/angular/components/button.d.ts +0 -68
  152. package/build/angular/components/button.js +0 -360
  153. package/build/angular/components/button.js.map +0 -1
  154. package/build/angular/components/image/index.android.d.ts +0 -15
  155. package/build/angular/components/image/index.android.js +0 -83
  156. package/build/angular/components/image/index.android.js.map +0 -1
  157. package/build/angular/components/image/index.d.ts +0 -1
  158. package/build/angular/components/image/index.ios.d.ts +0 -15
  159. package/build/angular/components/image/index.ios.js +0 -83
  160. package/build/angular/components/image/index.ios.js.map +0 -1
  161. package/build/angular/components/image/index.js +0 -3
  162. package/build/angular/components/image/index.js.map +0 -1
  163. package/build/angular/components/image/shared.js.map +0 -1
  164. package/build/angular/components/image-background.d.ts +0 -85
  165. package/build/angular/components/image-background.js +0 -443
  166. package/build/angular/components/image-background.js.map +0 -1
  167. package/build/angular/components/input-accessory-view/index.d.ts +0 -66
  168. package/build/angular/components/input-accessory-view/index.js +0 -287
  169. package/build/angular/components/input-accessory-view/index.js.map +0 -1
  170. package/build/angular/components/pressable/index.d.ts +0 -134
  171. package/build/angular/components/pressable/index.js +0 -514
  172. package/build/angular/components/pressable/index.js.map +0 -1
  173. package/build/angular/components/refresh-control/index.d.ts +0 -91
  174. package/build/angular/components/refresh-control/index.js +0 -382
  175. package/build/angular/components/refresh-control/index.js.map +0 -1
  176. package/build/angular/components/safe-area-view/index.d.ts +0 -64
  177. package/build/angular/components/safe-area-view/index.js +0 -287
  178. package/build/angular/components/safe-area-view/index.js.map +0 -1
  179. package/build/angular/components/scroll-view/index.android.d.ts +0 -20
  180. package/build/angular/components/scroll-view/index.android.js +0 -262
  181. package/build/angular/components/scroll-view/index.android.js.map +0 -1
  182. package/build/angular/components/scroll-view/index.d.ts +0 -3
  183. package/build/angular/components/scroll-view/index.ios.d.ts +0 -8
  184. package/build/angular/components/scroll-view/index.ios.js +0 -92
  185. package/build/angular/components/scroll-view/index.ios.js.map +0 -1
  186. package/build/angular/components/scroll-view/index.js +0 -6
  187. package/build/angular/components/scroll-view/index.js.map +0 -1
  188. package/build/angular/components/scroll-view/projection.d.ts +0 -67
  189. package/build/angular/components/scroll-view/projection.js +0 -560
  190. package/build/angular/components/scroll-view/projection.js.map +0 -1
  191. package/build/angular/components/scroll-view/shared.d.ts +0 -241
  192. package/build/angular/components/scroll-view/shared.js +0 -848
  193. package/build/angular/components/scroll-view/shared.js.map +0 -1
  194. package/build/angular/components/scroll-view/sticky-header.d.ts +0 -40
  195. package/build/angular/components/scroll-view/sticky-header.js +0 -224
  196. package/build/angular/components/scroll-view/sticky-header.js.map +0 -1
  197. package/build/angular/components/switch/index.android.d.ts +0 -24
  198. package/build/angular/components/switch/index.android.js +0 -112
  199. package/build/angular/components/switch/index.android.js.map +0 -1
  200. package/build/angular/components/switch/index.d.ts +0 -1
  201. package/build/angular/components/switch/index.ios.d.ts +0 -23
  202. package/build/angular/components/switch/index.ios.js +0 -111
  203. package/build/angular/components/switch/index.ios.js.map +0 -1
  204. package/build/angular/components/switch/index.js +0 -4
  205. package/build/angular/components/switch/index.js.map +0 -1
  206. package/build/angular/components/switch/shared.d.ts +0 -49
  207. package/build/angular/components/switch/shared.js +0 -191
  208. package/build/angular/components/switch/shared.js.map +0 -1
  209. package/build/angular/components/text-input.d.ts +0 -167
  210. package/build/angular/components/text-input.js +0 -684
  211. package/build/angular/components/text-input.js.map +0 -1
  212. package/build/angular/components/touchable/index.d.ts +0 -283
  213. package/build/angular/components/touchable/index.js +0 -1453
  214. package/build/angular/components/touchable/index.js.map +0 -1
  215. package/build/angular/components/touchable-native-feedback/index.d.ts +0 -87
  216. package/build/angular/components/touchable-native-feedback/index.js +0 -409
  217. package/build/angular/components/touchable-native-feedback/index.js.map +0 -1
  218. package/build/angular/state-style.d.ts +0 -9
  219. package/build/angular/state-style.js +0 -39
  220. package/build/angular/state-style.js.map +0 -1
  221. package/metro-transformer.cjs +0 -69
  222. package/src/components/activity-indicator/index.android.ts +0 -23
  223. package/src/components/activity-indicator/index.ios.ts +0 -21
  224. package/src/components/activity-indicator/index.ts +0 -4
  225. package/src/components/activity-indicator/shared.ts +0 -225
  226. package/src/components/button.ts +0 -237
  227. package/src/components/image/index.android.ts +0 -95
  228. package/src/components/image/index.ios.ts +0 -95
  229. package/src/components/image/index.ts +0 -3
  230. package/src/components/image-background.ts +0 -308
  231. package/src/components/input-accessory-view/index.ts +0 -246
  232. package/src/components/pressable/index.ts +0 -522
  233. package/src/components/refresh-control/index.ts +0 -349
  234. package/src/components/safe-area-view/index.ts +0 -247
  235. package/src/components/scroll-view/index.android.ts +0 -205
  236. package/src/components/scroll-view/index.ios.ts +0 -81
  237. package/src/components/scroll-view/index.ts +0 -7
  238. package/src/components/scroll-view/projection.ts +0 -713
  239. package/src/components/scroll-view/shared.ts +0 -1093
  240. package/src/components/scroll-view/sticky-header.ts +0 -267
  241. package/src/components/switch/index.android.ts +0 -113
  242. package/src/components/switch/index.ios.ts +0 -112
  243. package/src/components/switch/index.ts +0 -4
  244. package/src/components/switch/shared.ts +0 -284
  245. package/src/components/text-input.ts +0 -669
  246. package/src/components/touchable/index.ts +0 -956
  247. package/src/components/touchable-native-feedback/index.ts +0 -327
  248. package/src/state-style.ts +0 -39
@@ -54,7 +54,7 @@ function asAngularHost(hostNode: SymbioteSurface | ISymbioteNode): Element {
54
54
  return hostNode as unknown as Element;
55
55
  }
56
56
 
57
- // A bare `symbiote-view` node, created directly through the engine (bypassing Angular's own
57
+ // A bare `view` node, created directly through the engine (bypassing Angular's own
58
58
  // createElement, which only resolves KNOWN symbiote primitives, never an arbitrary component's
59
59
  // selector — see SymbioteRenderer.createElement). Used as the AppRegistry root's host when a
60
60
  // wrapperComponentProvider is set: Angular has no hostElement-less bootstrap for our renderer
@@ -62,9 +62,9 @@ function asAngularHost(hostNode: SymbioteSurface | ISymbioteNode): Element {
62
62
  // that isn't one of our primitives), so the root needs an explicit host too, one we then hand
63
63
  // to the wrapper as projectable content.
64
64
  function createDetachedViewHost(): ISymbioteNode {
65
- // 'symbiote-view', not 'View': the public name is not a Fabric view name, and until
65
+ // 'view', not 'View': the public name is not a Fabric view name, and until
66
66
  // makeDescriptorFor learned to reject one it fell through to a view literally named `View`.
67
- const descriptor = descriptorFor('symbiote-view');
67
+ const descriptor = descriptorFor('view');
68
68
  return toPublicInstance(
69
69
  createEngineElement(descriptor.component, descriptor.isText),
70
70
  );
@@ -14,6 +14,7 @@ import {
14
14
  getExplicitStyle,
15
15
  insertBefore,
16
16
  isDebug,
17
+ isSymbioteEvent,
17
18
  isSymbioteNode,
18
19
  removeChild,
19
20
  routeProp,
@@ -25,7 +26,10 @@ import {
25
26
  SymbioteSurface,
26
27
  type ISymbioteNode,
27
28
  } from '@symbiote-native/engine';
28
- import { descriptorFor } from '@symbiote-native/components';
29
+ import {
30
+ COMPONENT_DESCRIPTORS,
31
+ descriptorFor,
32
+ } from '@symbiote-native/components';
29
33
  import { foldHostBag } from '@symbiote-native/components/fold-host-bag';
30
34
  import type { Renderer2, RendererFactory2, RendererType2 } from '@angular/core';
31
35
  import { isAnchorHostComponent } from '../anchor-host-registry';
@@ -34,12 +38,6 @@ import {
34
38
  noteAngularCreate,
35
39
  noteAngularWrite,
36
40
  } from '../diagnostics';
37
- import {
38
- flushScrollViewProjections,
39
- getScrollViewProjection,
40
- removeScrollViewProjectedChild,
41
- } from '../components/scroll-view/projection';
42
-
43
41
  // Angular host nodes are all SymbioteNode (elements, raw text, anchors). The mount
44
42
  // container is the surface, so a parent can be either a node or the surface root.
45
43
  type IHostNode = ISymbioteNode;
@@ -62,8 +60,8 @@ function isRecord(value: unknown): value is Record<string, unknown> {
62
60
  // resolveTextProps, which the composed `Text` @Component already calls). That component's own
63
61
  // host paints directly (Text is not anchor-hosted — see the top-level "View/Text's own component
64
62
  // doesn't have this split" reasoning elsewhere in this file), so createElement runs for its INNER
65
- // symbiote-text node too; seeding here therefore covers both the composed Text and any bare
66
- // `symbiote-text` a future lowering emits, uniformly. Found missing 2026-08-31 (a cross-adapter
63
+ // text node too; seeding here therefore covers both the composed Text and any bare
64
+ // `text` a future lowering emits, uniformly. Found missing 2026-08-31 (a cross-adapter
67
65
  // key-count diff against Vue's real BenchmarkRow.vue) — without this a lowered Text's
68
66
  // `numberOfLines` clips with no ellipsis, silently, on device only. Vue's renderer already does
69
67
  // this (`adapters/vue/src/renderer/index.ts`'s `seedTextDefaults`); Angular's simply never did.
@@ -72,11 +70,11 @@ function isRecord(value: unknown): value is Record<string, unknown> {
72
70
  // `HOST_PRIMITIVES.Text.defaults`) rather than a second hardcoded copy — React and Svelte call the
73
71
  // same function directly; this used to be a THIRD, independent restatement of the same two
74
72
  // defaults, with nothing to catch it drifting from the spec if a default's value ever changed.
75
- // `foldHostBag('symbiote-text', {})` on an EMPTY bag folds every default with no authored value to
73
+ // `foldHostBag('text', {})` on an EMPTY bag folds every default with no authored value to
76
74
  // override it (the alias loop has nothing to fold — `id` is only rewritten when present), which is
77
75
  // exactly the seed this function needs.
78
76
  function seedTextDefaults(node: ISymbioteNode): void {
79
- const seeded = foldHostBag('symbiote-text', {});
77
+ const seeded = foldHostBag('text', {});
80
78
  for (const [key, value] of Object.entries(seeded)) setProp(node, key, value);
81
79
  }
82
80
 
@@ -87,12 +85,12 @@ function seedTextDefaults(node: ISymbioteNode): void {
87
85
  // contract is "fold a whole bag" — the extra key computed alongside `key` is simply unread here.
88
86
  function textDefaultFor(el: IHostElement, key: string): unknown {
89
87
  if (isSurface(el) || !el.isText) return undefined;
90
- return foldHostBag('symbiote-text', { [key]: undefined })[key];
88
+ return foldHostBag('text', { [key]: undefined })[key];
91
89
  }
92
90
 
93
91
  // RN's `id` is the modern W3C-named alias for `nativeID` (core/components/host-primitives.cjs's
94
92
  // `ID_ALIAS`) — View.js/Text.js copy it over unconditionally, so the two name ONE native prop.
95
- // React/Solid/Svelte fold it in a wrapper or transform; Angular had it nowhere, so `<View
93
+ // React/Solid/Svelte fold it in a wrapper or transform; Angular had it nowhere, so `<view
96
94
  // id="x">`/`[id]="x"` reached Fabric with an unknown `id` key and no `nativeID` — silently, on
97
95
  // device only. Lives in the renderer (mirroring Vue's `PROP_ALIASES`) so it covers every path
98
96
  // that can set a prop — `setAttribute`, `setProperty`, and (should a future lowering emit one) a
@@ -102,9 +100,10 @@ function textDefaultFor(el: IHostElement, key: string): unknown {
102
100
  // throws inside change detection). Under any other name it is an ordinary property binding and
103
101
  // arrives here.
104
102
  // Angular's two-way sugar `[(value)]` compiles to a `(valueChange)` binding; the engine knows the
105
- // same fold as the function prop `onValueChange`. See `listen()`.
106
- const VALUE_CHANGE_EVENT = 'valueChange';
107
- const VALUE_CHANGE_PROP = 'onValueChange';
103
+ // same fold as the function prop `onValueChange`. See `listen()`. The two names live in a leaf
104
+ // module so `elements.ts`'s ControlValueAccessor can name them without importing this cyclic file.
105
+ import { VALUE_CHANGE_EVENT, VALUE_CHANGE_PROP } from './value-change';
106
+ import { flushViewFor } from '../change-detection-flush';
108
107
 
109
108
  const PROP_ALIASES: ReadonlyMap<string, string> = new Map([
110
109
  ['id', 'nativeID'],
@@ -115,6 +114,30 @@ function aliasedPropName(name: string): string {
115
114
  return PROP_ALIASES.get(name) ?? name;
116
115
  }
117
116
 
117
+ // The app callbacks an engine behavior READS BACK inside the same microtask turn, as an Angular
118
+ // `(event)` binding — `valueChange` is handled in `listen` on its own, since it also needs the field
119
+ // unwrapped. Zoneless change detection is a macrotask, so without a flush the behavior reads the
120
+ // PRE-event value; `../change-detection-flush` holds the whole mechanism, the three behaviors, and
121
+ // why this is scoped rather than wired to the engine's event-dispatch seam.
122
+ //
123
+ // The other spelling — the `[onRefresh]` flat-bag PROP — is not here: an `on*` function prop is
124
+ // wrapped by `SymbioteElement` itself, which reaches every one of them rather than a named three,
125
+ // and adds the `markForCheck` a prop callback needs and an event binding gets from Angular.
126
+ const READ_BACK_EVENTS: ReadonlySet<string> = new Set(['refresh']);
127
+
128
+ type IReadBackListener = (event: unknown) => unknown;
129
+
130
+ function withChangeDetection(
131
+ node: IHostElement,
132
+ listener: IReadBackListener,
133
+ ): IReadBackListener {
134
+ return (event: unknown): unknown => {
135
+ const result = listener(event);
136
+ flushViewFor(node);
137
+ return result;
138
+ };
139
+ }
140
+
118
141
  // Diagnostic-only: tags each anchor with a sequential id so log lines can tell distinct
119
142
  // anchors apart (they all otherwise print the same generic '#anchor' component name).
120
143
  // Gated behind isDebug() so a production build pays zero cost (no WeakMap writes).
@@ -139,27 +162,51 @@ function describeHost(node: IHostElement | null | undefined): string {
139
162
  : node.component;
140
163
  }
141
164
 
165
+ // A hand-written tag ngtsc accepts must contain a HYPHEN, so the six dashless intrinsics
166
+ // (`view`/`text`/`pressable`/`image`/`switch`/`modal`) need a second spelling that has one.
167
+ // `hasElement`/`hasProperty` gate the CUSTOM_ELEMENTS_SCHEMA branch behind
168
+ // `normalizedTag.includes('-')` (compiler/src/schema/dom_element_schema_registry.ts), so a dashless
169
+ // tag is NG8001 under every schema but NO_ERRORS_SCHEMA — which switches off element checking
170
+ // entirely. Dropping the `symbiote-` prefix from the tag alphabet is what took the dash away.
171
+ //
172
+ // A per-tag directive is the other repair, and it is the one an app should reach for now that
173
+ // `SYMBIOTE_ELEMENTS` exists (`../elements.ts`). Matching a directive does turn every bound prop
174
+ // into an input lookup — the sentence that used to end this paragraph — but that is a repair
175
+ // rather than a dead end: declare the input and the lookup succeeds, with a real TYPE, which no
176
+ // schema gives. This alias map stays for the `symbiote-*` spelling and for an app that has not
177
+ // imported the directives.
178
+ //
179
+ // Derived, so a future dashless intrinsic joins by existing; when the alphabet regains its dashes
180
+ // upstream every entry becomes identity and the map can go. See `bare-intrinsic-tag-aot.test.ts`.
181
+ const HYPHEN_PREFIX = 'symbiote-';
182
+
183
+ function hyphenatedIntrinsicAliases(): Record<string, string> {
184
+ const aliases: Record<string, string> = {};
185
+ for (const tag of Object.keys(COMPONENT_DESCRIPTORS)) {
186
+ if (tag.includes('-')) continue;
187
+ aliases[`${HYPHEN_PREFIX}${tag}`] = tag;
188
+ }
189
+ return aliases;
190
+ }
191
+
142
192
  const PRIMITIVE_SELECTOR_ALIAS: Record<string, string> = {
143
193
  // Public ergonomic selectors map directly to the engine primitive descriptors.
144
- View: 'symbiote-view',
145
- Text: 'symbiote-text',
194
+ View: 'view',
195
+ Text: 'text',
196
+ ...hyphenatedIntrinsicAliases(),
146
197
  };
147
198
 
148
199
  // The ANCHOR_HOST_COMPONENTS Set and registerComposedComponent now live in the dependency-free
149
200
  // leaf module ../anchor-host-registry (imported above as isAnchorHostComponent) — see its header
150
201
  // for why the registry must NOT sit in this require-cyclic renderer module.
151
202
 
152
- // Inserting a bare raw-text node anywhere but inside a <Text> is invalid in Fabric (a
153
- // stray RCTRawText would paint). Angular's ɵɵtext only ever lands text inside a <Text>,
203
+ // Inserting a bare raw-text node anywhere but inside a <text> is invalid in Fabric (a
204
+ // stray RCTRawText would paint). Angular's ɵɵtext only ever lands text inside a <text>,
154
205
  // but guard anyway for parity with the Vue adapter and to fail loudly on a bad template.
155
- // `removeScrollViewProjectedChild` takes a remove callback and uses it only when the child turns
156
- // out to be projected, which almost none are — so the engine's own `removeChild` is passed by
157
- // reference rather than wrapped in an arrow. The wrapper was allocated on EVERY removed node:
158
- // 10 000 closures on a Clear whose engine window is 0.1 ms.
159
206
  function assertTextPlacement(child: ISymbioteNode, parent: IHostElement): void {
160
207
  if (isRawText(child) && (isSurface(parent) || !parent.isText)) {
161
208
  throw new Error(
162
- `Text string "${String(child.props.text)}" must be rendered inside a <Text>`,
209
+ `Text string "${String(child.props.text)}" must be rendered inside a <text>`,
163
210
  );
164
211
  }
165
212
  }
@@ -178,8 +225,8 @@ export class SymbioteRenderer implements Renderer2 {
178
225
  destroy(): void {}
179
226
 
180
227
  createElement(name: string): IHostNode {
181
- // `name` is the component's host tag — a symbiote intrinsic (`symbiote-view`,
182
- // `symbiote-text`, …), a public ergonomic alias (`View`, `Text`), or a raw Fabric view
228
+ // `name` is the component's host tag — a symbiote intrinsic (`view`,
229
+ // `text`, …), a public ergonomic alias (`View`, `Text`), or a raw Fabric view
183
230
  // name for a native leaf. Public aliases are normalized to their engine primitive name
184
231
  // before descriptor lookup. descriptorFor resolves it; an unknown `symbiote-*` is a typo,
185
232
  // any other string flows through as a raw Fabric name (events/processors derived from its
@@ -189,7 +236,20 @@ export class SymbioteRenderer implements Renderer2 {
189
236
  noteAngularCreate(name);
190
237
  const engineName = PRIMITIVE_SELECTOR_ALIAS[name] ?? name;
191
238
 
192
- if (isAnchorHostComponent(engineName)) {
239
+ // An INTRINSIC is never an anchor, and the check has to come first now that the tags carry no
240
+ // prefix. The anchor registry is keyed on lowercased selectors — Angular lowercases a
241
+ // dynamically-mounted component's selector at runtime, so it must be — and the composed
242
+ // wrappers are named after the primitives they render: `Text`.toLowerCase() IS the tag `text`.
243
+ // While both layers exist, every `<text>` would anchor instead of painting.
244
+ //
245
+ // Derived from the descriptor table rather than an exclusion list, so it cannot go stale; and
246
+ // it disappears on its own when the wrappers do, which is what this migration is for. The
247
+ // collision is exactly the single-word names — `TextInput` lowercases to `textinput`, which is
248
+ // no tag of ours.
249
+ if (
250
+ COMPONENT_DESCRIPTORS[engineName] === undefined &&
251
+ isAnchorHostComponent(engineName)
252
+ ) {
193
253
  const anchor = tagAnchorForDebug(createAnchor());
194
254
  if (isDebug()) {
195
255
  dlog(
@@ -249,19 +309,15 @@ export class SymbioteRenderer implements Renderer2 {
249
309
  }
250
310
  parent.appendChild(newChild);
251
311
  } else {
252
- const projection = getScrollViewProjection(parent);
312
+ // The engine's own `appendChild` already redirects through `parent.childHost` when the
313
+ // primitive's behavior builds one (ScrollView's content node, e.g.) — the adapter-side
314
+ // projection bridge this used to route through is gone with the ScrollView component.
253
315
  if (isDebug()) {
254
316
  dlog(
255
- `Angular renderer appendChild parent=${describeHost(parent)} child=${describeHost(newChild)} projection=${projection !== undefined}`,
256
- );
257
- }
258
- if (projection !== undefined) {
259
- projection.appendProjectedChild(parent, newChild, (target, child) =>
260
- appendChild(target, child),
317
+ `Angular renderer appendChild parent=${describeHost(parent)} child=${describeHost(newChild)}`,
261
318
  );
262
- } else {
263
- appendChild(parent, newChild);
264
319
  }
320
+ appendChild(parent, newChild);
265
321
  }
266
322
  this.surface.requestCommit();
267
323
  }
@@ -283,27 +339,13 @@ export class SymbioteRenderer implements Renderer2 {
283
339
  if (refChild) parent.insertBefore(newChild, refChild);
284
340
  else parent.appendChild(newChild);
285
341
  } else {
286
- const projection = getScrollViewProjection(parent);
287
342
  if (isDebug()) {
288
343
  dlog(
289
- `Angular renderer insertBefore parent=${describeHost(parent)} child=${describeHost(newChild)} ref=${refChild ? describeHost(refChild) : 'null'} projection=${projection !== undefined}`,
290
- );
291
- }
292
- if (projection !== undefined) {
293
- projection.insertProjectedChild(
294
- parent,
295
- newChild,
296
- refChild,
297
- (target, child, before) => {
298
- if (before === undefined) appendChild(target, child);
299
- else insertBefore(target, child, before);
300
- },
344
+ `Angular renderer insertBefore parent=${describeHost(parent)} child=${describeHost(newChild)} ref=${refChild ? describeHost(refChild) : 'null'}`,
301
345
  );
302
- } else if (refChild) {
303
- insertBefore(parent, newChild, refChild);
304
- } else {
305
- appendChild(parent, newChild);
306
346
  }
347
+ if (refChild) insertBefore(parent, newChild, refChild);
348
+ else appendChild(parent, newChild);
307
349
  }
308
350
  this.surface.requestCommit();
309
351
  }
@@ -313,20 +355,17 @@ export class SymbioteRenderer implements Renderer2 {
313
355
  // surface.children with no parent). Angular's `parent` arg is ignored in favor of the
314
356
  // authoritative link, mirroring the Vue adapter's remove.
315
357
  countAngular('nodesRemoved');
316
- const wasProjected = removeScrollViewProjectedChild(oldChild, removeChild);
317
358
  if (isDebug()) {
318
359
  const angularParent = _parent !== null ? describeHost(_parent) : 'null';
319
360
  const retainedParent =
320
361
  oldChild.parent !== undefined ? describeHost(oldChild.parent) : 'none';
321
362
  dlog(
322
- `Angular renderer removeChild angularParent=${angularParent} retainedParent=${retainedParent} child=${describeHost(oldChild)} viaProjection=${wasProjected}`,
363
+ `Angular renderer removeChild angularParent=${angularParent} retainedParent=${retainedParent} child=${describeHost(oldChild)}`,
323
364
  );
324
365
  }
325
- if (!wasProjected) {
326
- const parent = oldChild.parent;
327
- if (parent !== undefined) removeChild(parent, oldChild);
328
- else this.surface.removeChild(oldChild);
329
- }
366
+ const parent = oldChild.parent;
367
+ if (parent !== undefined) removeChild(parent, oldChild);
368
+ else this.surface.removeChild(oldChild);
330
369
  this.surface.requestCommit();
331
370
  }
332
371
 
@@ -479,15 +518,28 @@ export class SymbioteRenderer implements Renderer2 {
479
518
  // `valueChange` as an engine event would wait forever for a Fabric event of that name.
480
519
  //
481
520
  // The lowered path already carries the same fold under RN's own spelling: both behaviors call
482
- // `node.props.onValueChange(value, event)` — a plain function PROP, not an event
483
- // (`behaviors/switch.ts`, `behaviors/text-input.ts`). So this is a rename, not a mechanism:
484
- // route the binding to that prop and `[(value)]` behaves identically on both paths. Without it
485
- // the transform had to refuse to lower the two primitives whose idiomatic spelling this is.
521
+ // `node.props.onValueChange(event)` — a plain function PROP, not an event
522
+ // (`behaviors/switch.ts`, `behaviors/text-input.ts`), with `text`/`value` carried as a FIELD on
523
+ // the event object rather than a second argument (Svelte forces every individual `on*` prop
524
+ // through a native listener that calls with exactly one argument, always a real object). So
525
+ // Angular's own `[(value)]` sugar needs one extra step its React/Vue/Solid counterparts do not:
526
+ // unwrap that field back to a bare value before handing it to Angular's callback, or `text =
527
+ // $event` would assign the whole event object instead of the typed string/boolean.
486
528
  if (eventName === VALUE_CHANGE_EVENT) {
487
- routeProp(target, VALUE_CHANGE_PROP, callback);
529
+ const forwardValue = withChangeDetection(target, (event: unknown) => {
530
+ if (isSymbioteEvent(event)) {
531
+ if ('text' in event) return callback(event.text);
532
+ if ('value' in event) return callback(event.value);
533
+ }
534
+ return callback(event);
535
+ });
536
+ routeProp(target, VALUE_CHANGE_PROP, forwardValue);
488
537
  return () => routeProp(target, VALUE_CHANGE_PROP, undefined);
489
538
  }
490
- setEventListener(target, eventName, callback);
539
+ const listener = READ_BACK_EVENTS.has(eventName)
540
+ ? withChangeDetection(target, callback)
541
+ : callback;
542
+ setEventListener(target, eventName, listener);
491
543
  return () => setEventListener(target, eventName, undefined);
492
544
  }
493
545
  }
@@ -507,13 +559,10 @@ export class SymbioteRendererFactory implements RendererFactory2 {
507
559
  return (this.renderer ??= new SymbioteRenderer(this.surface));
508
560
  }
509
561
 
510
- // Not commit coalescing (requestCommit owns that) — this is the one moment where a ScrollView's
511
- // two input channels are known to be consistent: Angular has finished writing every `@Input` AND
512
- // the renderer has finished every projected insert/remove for this pass, while the surface's
513
- // commit is still only queued. Sticky projection reconciles are batched to here for both reasons;
514
- // see flushScrollViewProjections.
562
+ // Not commit coalescing (requestCommit owns that) — a per-CD-pass counter only, now that the
563
+ // ScrollView projection bridge this used to also flush is gone (`../register.ts`'s
564
+ // `registerScrollViewBehavior` owns the content node and the sticky seam for every adapter).
515
565
  end(): void {
516
566
  countAngular('cdPasses');
517
- flushScrollViewProjections();
518
567
  }
519
568
  }
@@ -0,0 +1,8 @@
1
+ // The two names Angular's `[(value)]` sugar rides on, in a dependency-free LEAF so `elements.ts`
2
+ // can name the event without importing the require-cyclic `renderer/index.ts` (same reasoning as
3
+ // `anchor-host-registry.ts`).
4
+ //
5
+ // `[(value)]` desugars to a `(valueChange)` binding, which is no Fabric event — the renderer
6
+ // translates it to the `onValueChange` PROP both behaviors already call.
7
+ export const VALUE_CHANGE_EVENT = 'valueChange';
8
+ export const VALUE_CHANGE_PROP = 'onValueChange';