@symbiote-native/angular 0.7.0 → 1.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 (188) hide show
  1. package/README.md +3 -3
  2. package/babel-lower-host-primitives.cjs +636 -0
  3. package/babel-register-composed.cjs +21 -3
  4. package/build/angular/anchor-host-registry.js +3 -1
  5. package/build/angular/anchor-host-registry.js.map +1 -1
  6. package/build/angular/bootstrap.js +1 -1
  7. package/build/angular/bootstrap.js.map +1 -1
  8. package/build/angular/components/activity-indicator/index.android.js +7 -2
  9. package/build/angular/components/activity-indicator/index.android.js.map +1 -1
  10. package/build/angular/components/activity-indicator/index.ios.js +3 -1
  11. package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
  12. package/build/angular/components/activity-indicator/shared.d.ts +1 -1
  13. package/build/angular/components/activity-indicator/shared.js +1 -1
  14. package/build/angular/components/activity-indicator/shared.js.map +1 -1
  15. package/build/angular/components/button.d.ts +3 -0
  16. package/build/angular/components/button.js +22 -5
  17. package/build/angular/components/button.js.map +1 -1
  18. package/build/angular/components/flat-list/index.d.ts +1 -1
  19. package/build/angular/components/flat-list/index.js +47 -14
  20. package/build/angular/components/flat-list/index.js.map +1 -1
  21. package/build/angular/components/image/index.android.js +4 -10
  22. package/build/angular/components/image/index.android.js.map +1 -1
  23. package/build/angular/components/image/index.ios.js +4 -10
  24. package/build/angular/components/image/index.ios.js.map +1 -1
  25. package/build/angular/components/image/shared.d.ts +4 -4
  26. package/build/angular/components/image/shared.js +51 -33
  27. package/build/angular/components/image/shared.js.map +1 -1
  28. package/build/angular/components/image-background.d.ts +3 -0
  29. package/build/angular/components/image-background.js +32 -9
  30. package/build/angular/components/image-background.js.map +1 -1
  31. package/build/angular/components/input-accessory-view/index.d.ts +2 -1
  32. package/build/angular/components/input-accessory-view/index.js +18 -16
  33. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  34. package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -1
  35. package/build/angular/components/keyboard-avoiding-view/index.js +56 -19
  36. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  37. package/build/angular/components/modal/index.d.ts +1 -0
  38. package/build/angular/components/modal/index.js +21 -10
  39. package/build/angular/components/modal/index.js.map +1 -1
  40. package/build/angular/components/pressable/index.d.ts +10 -5
  41. package/build/angular/components/pressable/index.js +61 -16
  42. package/build/angular/components/pressable/index.js.map +1 -1
  43. package/build/angular/components/refresh-control/index.d.ts +2 -1
  44. package/build/angular/components/refresh-control/index.js +13 -9
  45. package/build/angular/components/refresh-control/index.js.map +1 -1
  46. package/build/angular/components/safe-area-view/index.d.ts +3 -1
  47. package/build/angular/components/safe-area-view/index.js +22 -20
  48. package/build/angular/components/safe-area-view/index.js.map +1 -1
  49. package/build/angular/components/scroll-view/index.android.js +44 -14
  50. package/build/angular/components/scroll-view/index.android.js.map +1 -1
  51. package/build/angular/components/scroll-view/index.ios.js +13 -5
  52. package/build/angular/components/scroll-view/index.ios.js.map +1 -1
  53. package/build/angular/components/scroll-view/projection.d.ts +32 -0
  54. package/build/angular/components/scroll-view/projection.js +212 -47
  55. package/build/angular/components/scroll-view/projection.js.map +1 -1
  56. package/build/angular/components/scroll-view/shared.d.ts +2 -0
  57. package/build/angular/components/scroll-view/shared.js +48 -11
  58. package/build/angular/components/scroll-view/shared.js.map +1 -1
  59. package/build/angular/components/scroll-view/sticky-header.js +7 -3
  60. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  61. package/build/angular/components/section-list/index.d.ts +9 -1
  62. package/build/angular/components/section-list/index.js +72 -18
  63. package/build/angular/components/section-list/index.js.map +1 -1
  64. package/build/angular/components/switch/index.android.js +23 -9
  65. package/build/angular/components/switch/index.android.js.map +1 -1
  66. package/build/angular/components/switch/index.ios.js +23 -9
  67. package/build/angular/components/switch/index.ios.js.map +1 -1
  68. package/build/angular/components/switch/shared.d.ts +1 -1
  69. package/build/angular/components/switch/shared.js +5 -2
  70. package/build/angular/components/switch/shared.js.map +1 -1
  71. package/build/angular/components/text-input.d.ts +8 -2
  72. package/build/angular/components/text-input.js +79 -34
  73. package/build/angular/components/text-input.js.map +1 -1
  74. package/build/angular/components/touchable/index.d.ts +36 -7
  75. package/build/angular/components/touchable/index.js +280 -72
  76. package/build/angular/components/touchable/index.js.map +1 -1
  77. package/build/angular/components/touchable-native-feedback/index.d.ts +2 -1
  78. package/build/angular/components/touchable-native-feedback/index.js +19 -17
  79. package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
  80. package/build/angular/components/virtualized-list/directives.js +4 -0
  81. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  82. package/build/angular/components/virtualized-list/index.d.ts +8 -2
  83. package/build/angular/components/virtualized-list/index.js +207 -61
  84. package/build/angular/components/virtualized-list/index.js.map +1 -1
  85. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  86. package/build/angular/components/virtualized-section-list/index.d.ts +21 -1
  87. package/build/angular/components/virtualized-section-list/index.js +100 -22
  88. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  89. package/build/angular/components.d.ts +2 -2
  90. package/build/angular/components.js +1 -1
  91. package/build/angular/components.js.map +1 -1
  92. package/build/angular/create-portal/index.d.ts +11 -2
  93. package/build/angular/create-portal/index.js +42 -12
  94. package/build/angular/create-portal/index.js.map +1 -1
  95. package/build/angular/create-tunnel/index.js.map +1 -1
  96. package/build/angular/descriptor-to-angular/index.js +3 -1
  97. package/build/angular/descriptor-to-angular/index.js.map +1 -1
  98. package/build/angular/diagnostics.d.ts +33 -0
  99. package/build/angular/diagnostics.js +82 -0
  100. package/build/angular/diagnostics.js.map +1 -0
  101. package/build/angular/gate-demand.d.ts +37 -0
  102. package/build/angular/gate-demand.js +84 -0
  103. package/build/angular/gate-demand.js.map +1 -0
  104. package/build/angular/host-instance/index.js +1 -1
  105. package/build/angular/host-instance/index.js.map +1 -1
  106. package/build/angular/index.d.ts +6 -2
  107. package/build/angular/index.js +16 -1
  108. package/build/angular/index.js.map +1 -1
  109. package/build/angular/modules/animated/create-animated-component.d.ts +1 -0
  110. package/build/angular/modules/animated/create-animated-component.js +41 -18
  111. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  112. package/build/angular/modules/app-registry/index.js.map +1 -1
  113. package/build/angular/modules/status-bar/index.js +1 -1
  114. package/build/angular/modules/status-bar/index.js.map +1 -1
  115. package/build/angular/primitives/index.d.ts +31 -2
  116. package/build/angular/primitives/index.js +81 -3
  117. package/build/angular/primitives/index.js.map +1 -1
  118. package/build/angular/primitives/shared.d.ts +9 -1
  119. package/build/angular/primitives/shared.js +49 -3
  120. package/build/angular/primitives/shared.js.map +1 -1
  121. package/build/angular/register.d.ts +1 -0
  122. package/build/angular/register.js +35 -0
  123. package/build/angular/register.js.map +1 -0
  124. package/build/angular/render/index.js +51 -6
  125. package/build/angular/render/index.js.map +1 -1
  126. package/build/angular/renderer/index.d.ts +1 -0
  127. package/build/angular/renderer/index.js +157 -21
  128. package/build/angular/renderer/index.js.map +1 -1
  129. package/build/angular/services/color-scheme.service.js.map +1 -1
  130. package/build/angular/services/window-dimensions.service.js +1 -1
  131. package/build/angular/services/window-dimensions.service.js.map +1 -1
  132. package/build/angular/state-style.d.ts +9 -0
  133. package/build/angular/state-style.js +39 -0
  134. package/build/angular/state-style.js.map +1 -0
  135. package/metro-transformer.cjs +69 -0
  136. package/package.json +27 -8
  137. package/src/anchor-host-registry.ts +3 -1
  138. package/src/bootstrap.ts +4 -1
  139. package/src/components/activity-indicator/index.android.ts +7 -2
  140. package/src/components/activity-indicator/index.ios.ts +3 -1
  141. package/src/components/activity-indicator/shared.ts +32 -13
  142. package/src/components/button.ts +36 -6
  143. package/src/components/flat-list/index.ts +91 -29
  144. package/src/components/image/index.android.ts +9 -6
  145. package/src/components/image/index.ios.ts +9 -6
  146. package/src/components/image/shared.ts +110 -45
  147. package/src/components/image-background.ts +47 -10
  148. package/src/components/input-accessory-view/index.ts +41 -18
  149. package/src/components/keyboard-avoiding-view/index.ts +76 -22
  150. package/src/components/modal/index.ts +39 -12
  151. package/src/components/pressable/index.ts +121 -31
  152. package/src/components/refresh-control/index.ts +36 -13
  153. package/src/components/safe-area-view/index.ts +44 -19
  154. package/src/components/scroll-view/index.android.ts +31 -9
  155. package/src/components/scroll-view/index.ios.ts +17 -4
  156. package/src/components/scroll-view/projection.ts +290 -63
  157. package/src/components/scroll-view/shared.ts +124 -30
  158. package/src/components/scroll-view/sticky-header.ts +25 -8
  159. package/src/components/section-list/index.ts +79 -19
  160. package/src/components/switch/index.android.ts +18 -7
  161. package/src/components/switch/index.ios.ts +18 -7
  162. package/src/components/switch/shared.ts +15 -4
  163. package/src/components/text-input.ts +119 -31
  164. package/src/components/touchable/index.ts +339 -72
  165. package/src/components/touchable-native-feedback/index.ts +50 -18
  166. package/src/components/virtualized-list/directives.ts +19 -5
  167. package/src/components/virtualized-list/index.ts +292 -72
  168. package/src/components/virtualized-section-list/directives.ts +6 -3
  169. package/src/components/virtualized-section-list/index.ts +164 -34
  170. package/src/components.ts +6 -1
  171. package/src/create-portal/index.ts +47 -11
  172. package/src/create-tunnel/index.ts +3 -1
  173. package/src/descriptor-to-angular/index.ts +29 -8
  174. package/src/diagnostics.ts +138 -0
  175. package/src/gate-demand.ts +109 -0
  176. package/src/host-instance/index.ts +9 -3
  177. package/src/index.ts +33 -2
  178. package/src/modules/animated/create-animated-component.ts +67 -19
  179. package/src/modules/app-registry/index.ts +3 -1
  180. package/src/modules/status-bar/index.ts +13 -4
  181. package/src/primitives/index.ts +73 -2
  182. package/src/primitives/shared.ts +70 -9
  183. package/src/register.ts +43 -0
  184. package/src/render/index.ts +59 -7
  185. package/src/renderer/index.ts +209 -37
  186. package/src/services/color-scheme.service.ts +3 -1
  187. package/src/services/window-dimensions.service.ts +11 -4
  188. package/src/state-style.ts +39 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @symbiote-native/angular
2
2
 
3
3
  The **Angular adapter** for [SymbioteNative](../../README.md) — render real native iOS/Android views
4
- from Angular, on the *same* untouched core as React and Vue, with React Native's own renderer
4
+ from Angular, on the _same_ untouched core as React and Vue, with React Native's own renderer
5
5
  never in the path. It is a `Renderer2`/`RendererFactory2` whose calls map onto the engine's
6
6
  four-call mutation API; `@symbiote-native/engine` does the clone-on-write commit into Fabric.
7
7
 
@@ -34,7 +34,7 @@ own top-level dependencies. There's no `create-symbiote` scaffolder yet, so the
34
34
 
35
35
  ## Use it
36
36
 
37
- The native entry reaches the *same* `registerRunnable` seam as React and Vue — only the adapter
37
+ The native entry reaches the _same_ `registerRunnable` seam as React and Vue — only the adapter
38
38
  changes. It hands the surface's `rootTag` to `mount` from `@symbiote-native/angular`, which drives the
39
39
  engine through Angular's `Renderer2`:
40
40
 
@@ -94,7 +94,7 @@ detection + the descriptor→`createElement` bridge).
94
94
  One deliberate gap, tracked, not blocking the canary — **third-party React component packages**
95
95
  (`@react-native-community/slider`) run only under the React adapter: their body calls React hooks
96
96
  off the React dispatcher, which is null under Angular. `@symbiote-native/slider` (this repo's own
97
- wrapper) *does* ship a real Angular build, reachable through the same `createNode`-by-ViewConfig
97
+ wrapper) _does_ ship a real Angular build, reachable through the same `createNode`-by-ViewConfig
98
98
  path Angular uses for its own primitives — that wrapper is what makes a third-party native view
99
99
  usable from a non-React adapter at all.
100
100
 
@@ -0,0 +1,636 @@
1
+ // Compile-time lowering of `<View>`/`<Text>`/`<Pressable>`/`<TextInput>` to their intrinsic tags —
2
+ // the automated form of the hand-written technique already proven in
3
+ // examples/angular/src/screens/BenchmarkScreen.ts's `LoweredBenchmarkRows` (33.2% headless cut on
4
+ // Create-1000, angular-adapter-change-detection §19). That technique needs an author to spell the
5
+ // intrinsic tag AND keep the wrapper out of the template's own `imports` — this plugin does both
6
+ // mechanically, for any ordinary app component.
7
+ //
8
+ // THE SEAM. Runs in the SAME Babel pass as `babel-register-composed.cjs`, BEFORE the linker
9
+ // (`babel-linker.cjs`) — Stage A (`ngc --compilationMode partial`) has already emitted
10
+ // `ɵɵngDeclareComponent({ template: \`...\`, dependencies: [...] })`, and at THIS stage `template`
11
+ // is a plain string and `dependencies` a plain array the linker has not yet turned into Ivy. Two
12
+ // edits on that ONE object, matched by the same CallExpression:
13
+ //
14
+ // template `<View class="x">` -> `<symbiote-view class="x">`
15
+ // dependencies [{ type: View, selector: "symbiote-view, View" }, ...] -> entry REMOVED
16
+ //
17
+ // The second edit is not cleanup, it IS the mechanism. `dependencies`' `selector` string is BOTH
18
+ // spellings of View's dual selector at once (`'symbiote-view, View'` — see
19
+ // `adapters/angular/src/primitives/index.ts`), so leaving the entry in place while rewriting only
20
+ // the template text makes `symbiote-view` keep resolving to the real component: a rewritten tag
21
+ // with its dependency intact is not lowered, it looks lowered and silently is not. Confirmed
22
+ // against the installed compiler-cli (2026-08-31): removing a component's `ɵɵngDeclareComponent`
23
+ // `dependencies` entry with NO corresponding `schemas` change still links and runs — `schemas`
24
+ // (`CUSTOM_ELEMENTS_SCHEMA`) is consulted only by Stage A's ngtsc type-checker against the
25
+ // ORIGINAL source, never by the linker against this declared metadata, so an ordinary app
26
+ // component needs no `schemas`/`CUSTOM_ELEMENTS_SCHEMA` change at all to become lowerable — unlike
27
+ // `LoweredBenchmarkRows`, which types `<symbiote-view>` directly in source and therefore DOES need
28
+ // it, for ngtsc's sake, not the linker's. `Pressable`/`TextInput` carry a SINGLE-name selector
29
+ // (`'Pressable'`, `'TextInput'`, no comma), so removing their dependency entry is the same
30
+ // single-token removal, not a dual-spelling split.
31
+ //
32
+ // WHY View/Text/Pressable/TextInput AND NOT MORE — `HOST_PRIMITIVES` lists five; `Image` is not
33
+ // here because it has no `observesState`/`intrinsicWhen` prerequisite blocking it, it is simply
34
+ // not yet done. The other four are done as of 2026-09-01:
35
+ //
36
+ // Pressable `observesState: true`. Landed here once two prerequisites closed. First, the
37
+ // engine-side press machine (`registerPressableBehavior`, called from
38
+ // `register.ts`, below). Second — RETIRED 2026-08-31, corrected here because the
39
+ // header used to describe it as still open — the anchor-registry collision:
40
+ // `symbiote-pressable` used to be one spelling of the COMPOSED Pressable's own dual
41
+ // selector (`'Pressable, symbiote-pressable'`), which forced it unconditionally into
42
+ // `ANCHOR_HOST_COMPONENTS` and made a bare lowered tag collide with its own anchor.
43
+ // The composed selector dropped the vestigial second spelling (nothing ever rendered
44
+ // or matched it) and `symbiote-pressable` came out of `ANCHOR_HOST_COMPONENTS`, so
45
+ // the bare tag now resolves unambiguously to the engine-node behavior — see
46
+ // Pressable's own `@Component` comment and `.claude/rules/host-primitive-tier.md`.
47
+ // TextInput `intrinsicWhen: { prop: 'multiline', intrinsic: 'symbiote-text-input-multiline' }`
48
+ // — `multiline` selects between two DIFFERENT native views, not one view with a
49
+ // flag, so a runtime value refuses (`REFUSAL_CATEGORIES.dynamicIntrinsicChoice`,
50
+ // `staticTruthOf`/`intrinsicFor` below). The wrapper renders `-managed`/
51
+ // `-multiline-managed` tags (`component-names/shared.ts`), so the LOWERED plain pair
52
+ // never collides with a wrapper-built node the way a shared tag would have.
53
+ //
54
+ // THE REFUSAL CRITERION (agreed across cross-session work, replaces a per-category checklist): a
55
+ // compile-time refusal is owed exactly where the transform would make a decision FROM THE STATIC
56
+ // ATTRIBUTE LIST that the runtime cannot replay. A prop FOLD is never a reason to refuse — the
57
+ // engine folds by key, at commit time, on a lowered node exactly as it would on a wrapped one
58
+ // (`core/engine/src/accessibility-props.ts`'s aria fold is the reference case). Tag CHOICE is
59
+ // (TextInput's `intrinsicWhen`, above). For View/Text, with `observesState` unset, the one thing
60
+ // this criterion reaches is `#ref`:
61
+ //
62
+ // `#ref` on a WRAPPED `<View>`/`<Text>` yields the `ViewHost`/`TextHost` COMPONENT INSTANCE
63
+ // (`SymbiotePrimitiveHost`'s own `nativeElement` getter, `style` @Input, `ngDoCheck`) — Angular's
64
+ // own default resolves a bare template reference to a matched component/directive instance
65
+ // before falling back to the element. A lowered bare `<symbiote-view #ref>` matches no directive
66
+ // (see the dependency-removal step above) and hands back the raw engine node DIRECTLY instead.
67
+ // Same object, reached two different ways, is fine; a DIFFERENT surface is the hazard this
68
+ // project's own `instance-bound-directive` fixture row exists for (`core/components/
69
+ // lowering-fixtures.cjs`) — lowering must not change what an app's `ref` receives, in EITHER
70
+ // direction. Unlike Solid (whose View/Text explicitly forward the SAME node either way, so only
71
+ // Pressable there needs the refusal — `.claude/rules/solid-host-primitive-lowering.md`), Angular's
72
+ // wrapper does NOT forward identically, so this refuses UNIVERSALLY here, the same width as Vue's
73
+ // — same verdict, different per-adapter reason, which is the right shape
74
+ // (`<prop_types_split_agnostic_vs_per_adapter>`'s "must every adapter answer identically by
75
+ // construction" test says no). `#ref` on Pressable would ADD a handle it never had (no public
76
+ // ref on any adapter's Pressable, per `lowering-fixtures.cjs`'s own `instance-bound-directive`
77
+ // row) and on TextInput would SWAP its imperative handle for a bare public instance — both
78
+ // refuse for the same reason View/Text do, so the check below applies to all four names
79
+ // uniformly rather than needing a per-primitive allow-list the way Solid's does.
80
+ //
81
+ // THE ALL-OR-NOTHING RULE, per tag name per template. If ONE `<View>` in a template carries `#ref`
82
+ // (or, for `TextInput`, one instance has a dynamic `multiline`) and refuses, EVERY occurrence of
83
+ // that name in this SAME template stays a component — not just the refusing one.
84
+ // `dependencies`' selector covers the WHOLE template at once, so keeping the entry (because the
85
+ // refusing element still needs it) means the bare tag keeps resolving to the component everywhere
86
+ // in this template regardless of which individual tags this plugin rewrote the text of. Rewriting
87
+ // only SOME occurrences while the dependency survives is not partial coverage, it is every
88
+ // "lowered" spelling silently still being the component — the same failure shape
89
+ // `LoweredBenchmarkRows`'s own header names for the hand-written technique.
90
+ //
91
+ // STYLE ON A STATE-OBSERVING PRIMITIVE (`observesState`, Pressable only today).
92
+ // `REFUSAL_CATEGORIES.stateInTemplate` never fires here or in any transform — coverage is decided
93
+ // by INVOCATION, not by proving the shape (`core/components/lowering-fixtures.cjs`'s own header:
94
+ // "a transform that reports `refuse` here has wired substitution as the mechanism instead of as
95
+ // the optimisation"). Every `style` shape lowers; only the EMISSION differs, and
96
+ // `REFUSAL_CATEGORIES.emitStyleExpressionOnce` sets the one real constraint — an expression capable
97
+ // of doing work (a call, a computed member, a conditional…) is printed in the output exactly ONCE.
98
+ //
99
+ // Angular's template grammar cannot write an inline arrow function at all (`parseTemplate` throws
100
+ // on `[style]="({pressed}) => ({...})"`, verified 2026-08-31 — `specialisable-state-style`/
101
+ // `nested-function-state-style` are UNWRITABLE here, see `lowering-parity.test.ts`), so the
102
+ // "literal function, call it directly" bucket other transforms have simply does not exist in this
103
+ // adapter. Two buckets only:
104
+ //
105
+ // inert object/array/string LITERAL — provably not a function, left untouched.
106
+ // reference a bare identifier or a non-computed property chain (`fnStyle`, `this.a.b`) — safe
107
+ // to print TWICE (a read, not work): `typeof (e) === 'function' ? (e)({pressed}) :
108
+ // (e)`, split across `[style]`/`[activeStyle]`.
109
+ // opaque anything else (`getStyle()`, `bag[i]`, `flag ? a : b`, …) — CANNOT be read twice.
110
+ // Angular templates cannot call an arbitrary imported function (`resolveStateStyle
111
+ // (e)` in a template resolves to `ctx.resolveStateStyle(e)`, a component-INSTANCE
112
+ // method lookup this transform has no component to add one to — verified against the
113
+ // real linker, `state-style.ts`'s own header). A PIPE is the one thing that resolves
114
+ // through `dependencies` like a component does, so this emits
115
+ // `@let v = (e | resolveStateStyle); [style]="v.style" [activeStyle]="v.activeStyle"`
116
+ // — `e` evaluated exactly once (`ɵɵpipeBind1`, one call), `v` read twice for free.
117
+ //
118
+ // .cjs because Babel `require()`s it directly (Metro loads it via a raw require, no transpile step
119
+ // available) and this package is otherwise ESM-flavoured.
120
+
121
+ const {
122
+ HOST_PRIMITIVES,
123
+ REFUSAL_CATEGORIES,
124
+ } = require('@symbiote-native/components/host-primitives');
125
+ const { parseTemplate } = require('@angular/compiler');
126
+
127
+ const SOURCE = '@symbiote-native/angular';
128
+ const PIPE_NAME = 'resolveStateStyle';
129
+ const PIPE_EXPORT = 'SymbioteStateStylePipe';
130
+ const PIPE_MODULE = `${SOURCE}/state-style`;
131
+
132
+ const LOWERABLE_NAMES = [
133
+ 'View',
134
+ 'Text',
135
+ 'Pressable',
136
+ 'TextInput',
137
+ // Fold-only: neither sets `observesState` nor `intrinsicWhen`, so every occurrence lowers
138
+ // through the same generic path as View/Text — no new refusal category needed.
139
+ 'Image',
140
+ 'InputAccessoryView',
141
+ // Carries an ENGINE machine (`registerSwitchBehavior`) but sets neither `observesState` nor
142
+ // `intrinsicWhen`, so this transform's verdict is unaffected by it — same generic path.
143
+ 'Switch',
144
+ ];
145
+
146
+ // name -> a projection of the shared spec. Built once at plugin load. This is a WHITELIST — see
147
+ // Solid's own LOWERABLE map for why a field the spec grows and this list does not copy simply
148
+ // never reaches the detections below (measured there: `intrinsicWhen` implemented end to end and
149
+ // silently inert because the projection dropped the field).
150
+ const SPECS = new Map(
151
+ LOWERABLE_NAMES.map(name => [
152
+ name,
153
+ {
154
+ intrinsic: HOST_PRIMITIVES[name].intrinsic,
155
+ observesState: HOST_PRIMITIVES[name].observesState === true,
156
+ intrinsicWhen: HOST_PRIMITIVES[name].intrinsicWhen,
157
+ },
158
+ ]),
159
+ );
160
+
161
+ function isNgDeclareComponentCall(node) {
162
+ return (
163
+ node.type === 'CallExpression' &&
164
+ node.callee.type === 'MemberExpression' &&
165
+ !node.callee.computed &&
166
+ node.callee.property.type === 'Identifier' &&
167
+ node.callee.property.name === 'ɵɵngDeclareComponent'
168
+ );
169
+ }
170
+
171
+ function propertyOf(objectExpression, name) {
172
+ return objectExpression.properties.find(
173
+ prop =>
174
+ prop.type === 'ObjectProperty' &&
175
+ !prop.computed &&
176
+ ((prop.key.type === 'Identifier' && prop.key.name === name) ||
177
+ (prop.key.type === 'StringLiteral' && prop.key.value === name)),
178
+ );
179
+ }
180
+
181
+ // ngc's own partial-declare emit always writes `template` as a backtick template literal with NO
182
+ // interpolation, never a plain StringLiteral (verified against the installed compiler-cli,
183
+ // 2026-08-31) — presumably to dodge quote-escaping the author's own HTML. Read/write through the
184
+ // one quasi; bail (return undefined) on anything else rather than guess.
185
+ function templateTextOf(valueNode) {
186
+ if (
187
+ valueNode.type === 'TemplateLiteral' &&
188
+ valueNode.expressions.length === 0 &&
189
+ valueNode.quasis.length === 1
190
+ ) {
191
+ return valueNode.quasis[0].value.raw;
192
+ }
193
+ if (valueNode.type === 'StringLiteral') return valueNode.value;
194
+ return undefined;
195
+ }
196
+
197
+ // Per-file flag for the Program-exit guard below, set the moment this transform edits anything.
198
+ const EDITED_KEY = 'symbiote-lower-host-primitives/edited';
199
+
200
+ function setTemplateText(valueNode, text) {
201
+ if (valueNode.type === 'TemplateLiteral') {
202
+ valueNode.quasis[0].value.raw = text;
203
+ valueNode.quasis[0].value.cooked = text;
204
+ return;
205
+ }
206
+ valueNode.value = text;
207
+ }
208
+
209
+ // Every shape this codebase's templates actually nest a lowerable element under, empirically
210
+ // checked against the installed `@angular/compiler` (2026-08-31): a plain Element/Template holds
211
+ // `.children`; `@if`/`@else` holds `.branches[].children`; `@switch` holds `.groups[].children`
212
+ // (the group, not each case, carries the children); `@for` holds `.children` plus a SEPARATE
213
+ // `.empty` block. NOT walked: `@defer`'s placeholder/loading/error sub-blocks — grepped, nothing
214
+ // in this repo's templates uses `@defer` today; add those four properties here before the first
215
+ // one does.
216
+ function childrenOf(node) {
217
+ const out = [];
218
+ if (Array.isArray(node.children)) out.push(...node.children);
219
+ if (Array.isArray(node.branches)) {
220
+ for (const branch of node.branches) out.push(...(branch.children || []));
221
+ }
222
+ if (Array.isArray(node.groups)) {
223
+ for (const group of node.groups) out.push(...(group.children || []));
224
+ }
225
+ if (node.empty !== undefined && node.empty !== null) {
226
+ out.push(...(node.empty.children || []));
227
+ }
228
+ return out;
229
+ }
230
+
231
+ function walk(nodes, visit) {
232
+ for (const node of nodes) {
233
+ visit(node);
234
+ walk(childrenOf(node), visit);
235
+ }
236
+ }
237
+
238
+ // `.name` alone is not enough to prove this is a real TmplAstElement — check the element-specific
239
+ // array fields too, so a differently-shaped node that merely happens to carry a `.name` (unlikely
240
+ // today, cheap to guard) cannot be misread as a lowerable tag.
241
+ function isElementNamed(node, name) {
242
+ return (
243
+ node.name === name &&
244
+ Array.isArray(node.attributes) &&
245
+ Array.isArray(node.inputs) &&
246
+ Array.isArray(node.outputs)
247
+ );
248
+ }
249
+
250
+ // REFUSAL_CATEGORIES.instanceBoundDirective — see the header's `#ref` section for why this refuses
251
+ // UNIVERSALLY, on all four names, rather than being scoped the way Solid's per-primitive list is.
252
+ function hasInstanceBoundRef(element) {
253
+ return Array.isArray(element.references) && element.references.length > 0;
254
+ }
255
+
256
+ // REFUSAL_CATEGORIES.dynamicIntrinsicChoice. IDENTITY, not truthiness — see host-primitives.cjs's
257
+ // own comment on `intrinsicWhen`: a bare attribute is `true`, an explicit boolean literal is
258
+ // itself, absence is `false`, and everything else (including a truthy non-boolean literal like
259
+ // `multiline={1}`) refuses. Verified against the installed `@angular/compiler`, 2026-09-01: a bare
260
+ // `multiline` parses as a plain TextAttribute (`.attributes`), a bound `[multiline]="expr"` as a
261
+ // BoundAttribute (`.inputs`) whose `.value.ast` is a `LiteralPrimitive` for a literal and something
262
+ // else (`PropertyRead`, `Call`, …) for anything the transform cannot resolve.
263
+ function staticTruthOf(element, propName) {
264
+ const plain = element.attributes.find(attribute => attribute.name === propName);
265
+ if (plain !== undefined) return true;
266
+ const bound = element.inputs.find(input => input.name === propName);
267
+ if (bound === undefined) return false;
268
+ const ast = bound.value.ast;
269
+ if (ast.constructor.name !== 'LiteralPrimitive') return undefined;
270
+ return typeof ast.value === 'boolean' ? ast.value : undefined;
271
+ }
272
+
273
+ function selectsIntrinsicStatically(element, spec) {
274
+ return (
275
+ spec.intrinsicWhen === undefined ||
276
+ staticTruthOf(element, spec.intrinsicWhen.prop) !== undefined
277
+ );
278
+ }
279
+
280
+ function intrinsicFor(element, spec) {
281
+ if (spec.intrinsicWhen === undefined) return spec.intrinsic;
282
+ return staticTruthOf(element, spec.intrinsicWhen.prop) === true
283
+ ? spec.intrinsicWhen.intrinsic
284
+ : spec.intrinsic;
285
+ }
286
+
287
+ // `ngModel` needs the ControlValueAccessor the component PROVIDES, and an element cannot be one at
288
+ // all — no rename rescues it, so it refuses on every primitive.
289
+ //
290
+ // `[(value)]` used to refuse here and no longer does, which is worth stating: it desugars to
291
+ // `(valueChange)`, an @Output the component derives from the raw `change` payload — and the LOWERED
292
+ // path already carries the same fold under RN's own spelling, `node.props.onValueChange(value,
293
+ // event)`, called by both behaviors. The renderer routes the binding there (`renderer/index.ts`,
294
+ // `listen()`), so both paths agree. Refusing was tried first and cost the lowering of BOTH
295
+ // primitives outright: `[(value)]` is not one spelling among several, it is how every Angular
296
+ // template writes a Switch or a TextInput, and an optimisation that asks consumers to write
297
+ // differently does not exist.
298
+ //
299
+ // `valueChange` is not a native event and no behavior emits it: `text-input.ts` and `switch/
300
+ // shared.ts` DERIVE it from `change` purely so `[(value)]` works, which is the idiom every Angular
301
+ // template actually writes. Lowered, `[(value)]="name"` desugars to a `(valueChange)` listener that
302
+ // the engine registers and nothing ever fires — the native field echoes keystrokes on its own, so
303
+ // the input looks alive while every value read off it stays frozen. Device-diagnosed 2026-09-02 on
304
+ // `CanaryScreen`; nothing is red, on either side.
305
+ //
306
+ // `ngModel` is the same class one step further out: it needs the ControlValueAccessor the component
307
+ // PROVIDES, and an element cannot be one at all — so it refuses on every primitive, not per name.
308
+ //
309
+ // Per-adapter by construction, so it stays here rather than in `host-primitives.cjs`: which outputs
310
+ // are derived is a fact about Angular's component surface, and the shared spec must not carry one
311
+ // answer for five adapters (`<prop_types_split_agnostic_vs_per_adapter>`, same test).
312
+ const FORMS_BINDINGS = ['ngModel', 'ngModelChange'];
313
+
314
+ // `style` is the one binding name Angular will not hand to a plain element as a VALUE. It compiles
315
+ // to the styling instructions, which decompose the value key by key through Angular's own CSS
316
+ // engine — and that engine cannot represent an RN StyleProp: an ARRAY makes `applyStyling` use
317
+ // each element as a style KEY and throw `prop.indexOf is not a function` mid-change-detection,
318
+ // killing the tick for the whole app. `primitives/shared.ts` states the constraint from the other
319
+ // side: every composed component declares `style` as a real `@Input()` precisely so the binding
320
+ // never reaches that engine. A component input SHADOWS the instruction, so an un-lowered element
321
+ // is safe and a lowered one is not — this transform is what moves it across that line.
322
+ // Device-diagnosed 2026-09-02 on `ImageBackground`'s `<Image [style]="imageStyle">`.
323
+ //
324
+ // A BOUND `[style]` is not refused, it is RENAMED (`styleKeyRenameEdit` below) so the instruction
325
+ // never sees it. A STATIC `style="…"` is refused instead: its value is CSS text, which the engine's
326
+ // prop layer would read as a registered class name rather than as declarations.
327
+ //
328
+ // `class` is NOT in the same class of problem: `ɵɵclassMap` resolves through
329
+ // `addClass`/`removeClass`, which this adapter's renderer implements against the engine's class
330
+ // registry, so a lowered element keeps its classes.
331
+ const LOWERED_STYLE_PROP = 'symbioteStyle';
332
+
333
+ function bindsStaticStyle(element) {
334
+ return element.attributes.some(attribute => attribute.name === 'style');
335
+ }
336
+
337
+ // Renames the BINDING, never the value — `[style]="x"` becomes `[symbioteStyle]="x"`, which is an
338
+ // ordinary property binding and reaches `Renderer2.setProperty`, where the renderer's PROP_ALIASES
339
+ // folds it straight back to `style`. Runs for every lowered element, alongside (and independent of)
340
+ // the state-style specialisation, which edits only the VALUE span.
341
+ function styleKeyRenameEdit(element) {
342
+ const styleAttr = element.inputs.find(input => input.name === 'style');
343
+ if (styleAttr === undefined || styleAttr.keySpan === undefined) return [];
344
+ return [
345
+ {
346
+ start: styleAttr.keySpan.start.offset,
347
+ end: styleAttr.keySpan.end.offset,
348
+ text: LOWERED_STYLE_PROP,
349
+ },
350
+ ];
351
+ }
352
+
353
+ function bindsComponentOnlyName(element) {
354
+ const banned = new Set(FORMS_BINDINGS);
355
+ const named = binding => banned.has(binding.name);
356
+ return (
357
+ element.outputs.some(named) ||
358
+ element.inputs.some(named) ||
359
+ element.attributes.some(named)
360
+ );
361
+ }
362
+
363
+ // The single per-element refusal check the all-or-nothing gate below asks of every occurrence of
364
+ // a name in a template — `#ref` for all four, a runtime-valued `intrinsicWhen` selector for
365
+ // TextInput, and a binding only the component can deliver. `void REFUSAL_CATEGORIES` below keeps
366
+ // the import a real, checkable binding rather than a string copy of the category names used only
367
+ // in comments.
368
+ function elementRefuses(element, spec, name) {
369
+ return (
370
+ hasInstanceBoundRef(element) ||
371
+ !selectsIntrinsicStatically(element, spec) ||
372
+ bindsComponentOnlyName(element) ||
373
+ bindsStaticStyle(element)
374
+ );
375
+ }
376
+ void REFUSAL_CATEGORIES;
377
+
378
+ // A LITERAL is provably not a function, so the template cannot be reading press state through it —
379
+ // `{ borderColor: c }` (LiteralMap), `[a, b]` (LiteralArray), `'x'` (LiteralPrimitive: a string
380
+ // literal specifically, though any LiteralPrimitive is inert here — a number/boolean/null style
381
+ // value is not a function either way).
382
+ function isLiteralStyleValue(ast) {
383
+ const kind = ast.constructor.name;
384
+ return kind === 'LiteralMap' || kind === 'LiteralArray' || kind === 'LiteralPrimitive';
385
+ }
386
+
387
+ // A bare identifier or a non-computed property chain — `fnStyle`, `this.fnStyle`, `this.a.b` — is
388
+ // cheap to print twice: reading a name twice is not work, unlike calling a function or indexing an
389
+ // array twice. `ImplicitReceiver`/`ThisReceiver` are the two base cases `parseTemplate` produces
390
+ // for a bare name and a `this.`-qualified one respectively (verified against the installed
391
+ // `@angular/compiler`, 2026-09-01).
392
+ function isCheapReference(ast) {
393
+ const kind = ast.constructor.name;
394
+ if (kind === 'ImplicitReceiver' || kind === 'ThisReceiver') return true;
395
+ return kind === 'PropertyRead' && isCheapReference(ast.receiver);
396
+ }
397
+
398
+ // Splits a state-observing primitive's `style` into `style`/`activeStyle`, INVOCATION rather than
399
+ // substitution (see the header) — every shape lowers, only the emission differs. Returns text
400
+ // edits in the same `{start, end, text}` shape the tag-rewrite edits use, so both flow through one
401
+ // sort-and-splice pass. `letState` is per-template (reset per `ɵɵngDeclareComponent` call): a
402
+ // counter for unique `@let` names, and a flag telling the caller whether the `resolveStateStyle`
403
+ // pipe import/dependency needs adding at all.
404
+ function styleEditsFor(element, templateText, letState) {
405
+ const styleAttr = element.inputs.find(input => input.name === 'style');
406
+ if (styleAttr === undefined) return [];
407
+ const ast = styleAttr.value.ast;
408
+ if (isLiteralStyleValue(ast)) return [];
409
+
410
+ const valueSpan = styleAttr.valueSpan;
411
+ const exprText = templateText.slice(
412
+ valueSpan.start.offset,
413
+ valueSpan.end.offset,
414
+ );
415
+ const attrEnd = styleAttr.sourceSpan.end.offset;
416
+
417
+ if (isCheapReference(ast)) {
418
+ const resting = `typeof (${exprText}) === 'function' ? (${exprText})({ pressed: false }) : (${exprText})`;
419
+ const pressed = `typeof (${exprText}) === 'function' ? (${exprText})({ pressed: true }) : (${exprText})`;
420
+ return [
421
+ { start: valueSpan.start.offset, end: valueSpan.end.offset, text: resting },
422
+ { start: attrEnd, end: attrEnd, text: ` [activeStyle]="${pressed}"` },
423
+ ];
424
+ }
425
+
426
+ const varName = `symbioteStateStyle${letState.counter}`;
427
+ letState.counter += 1;
428
+ letState.usesPipe = true;
429
+ const letStart = element.startSourceSpan.start.offset;
430
+ return [
431
+ {
432
+ start: letStart,
433
+ end: letStart,
434
+ text: `@let ${varName} = (${exprText} | ${PIPE_NAME}); `,
435
+ },
436
+ {
437
+ start: valueSpan.start.offset,
438
+ end: valueSpan.end.offset,
439
+ text: `${varName}.style`,
440
+ },
441
+ { start: attrEnd, end: attrEnd, text: ` [activeStyle]="${varName}.activeStyle"` },
442
+ ];
443
+ }
444
+
445
+ function findDependencyIndex(dependenciesNode, tagName) {
446
+ if (dependenciesNode === undefined || dependenciesNode.type !== 'ArrayExpression') {
447
+ return -1;
448
+ }
449
+ return dependenciesNode.elements.findIndex(element => {
450
+ if (element === null || element.type !== 'ObjectExpression') return false;
451
+ const selectorProp = propertyOf(element, 'selector');
452
+ if (!selectorProp || selectorProp.value.type !== 'StringLiteral') return false;
453
+ const tokens = selectorProp.value.value.split(',').map(token => token.trim());
454
+ return tokens.includes(tagName);
455
+ });
456
+ }
457
+
458
+ module.exports = function lowerHostPrimitivesPlugin({ types }) {
459
+ // Shared across every `ɵɵngDeclareComponent` call in the FILE, so a second template needing the
460
+ // pipe reuses the one import instead of adding a second binding of the same name.
461
+ let pipeImportLocal;
462
+
463
+ function pipeDependencyEntry(programPath) {
464
+ if (pipeImportLocal === undefined) {
465
+ pipeImportLocal = programPath.scope.generateUidIdentifier(PIPE_EXPORT);
466
+ programPath.unshiftContainer(
467
+ 'body',
468
+ types.importDeclaration(
469
+ [types.importSpecifier(pipeImportLocal, types.identifier(PIPE_EXPORT))],
470
+ types.stringLiteral(PIPE_MODULE),
471
+ ),
472
+ );
473
+ }
474
+ return types.objectExpression([
475
+ types.objectProperty(types.identifier('kind'), types.stringLiteral('pipe')),
476
+ types.objectProperty(types.identifier('type'), types.cloneNode(pipeImportLocal, true)),
477
+ types.objectProperty(types.identifier('name'), types.stringLiteral(PIPE_NAME)),
478
+ ]);
479
+ }
480
+
481
+ return {
482
+ name: 'symbiote-lower-host-primitives',
483
+ visitor: {
484
+ Program: {
485
+ enter(programPath, state) {
486
+ programPath.traverse({
487
+ CallExpression(path) {
488
+ if (!isNgDeclareComponentCall(path.node)) return;
489
+ const [arg] = path.node.arguments;
490
+ if (!arg || arg.type !== 'ObjectExpression') return;
491
+
492
+ const templateProp = propertyOf(arg, 'template');
493
+ if (!templateProp) return;
494
+ const templateText = templateTextOf(templateProp.value);
495
+ if (templateText === undefined) return;
496
+
497
+ const dependenciesProp = propertyOf(arg, 'dependencies');
498
+ let dependenciesNode = dependenciesProp
499
+ ? dependenciesProp.value
500
+ : undefined;
501
+
502
+ // Never touch a template this parser rejects — refusing the WHOLE component is always
503
+ // safe, and this transform has no business guessing past a real parse error.
504
+ let parsed;
505
+ try {
506
+ parsed = parseTemplate(templateText, 'inline.html', {
507
+ preserveWhitespaces: true,
508
+ });
509
+ } catch {
510
+ return;
511
+ }
512
+ if (parsed.errors !== null && parsed.errors.length > 0) return;
513
+
514
+ const occurrencesByName = new Map(
515
+ LOWERABLE_NAMES.map(name => [name, []]),
516
+ );
517
+ walk(parsed.nodes, node => {
518
+ for (const name of LOWERABLE_NAMES) {
519
+ if (isElementNamed(node, name)) occurrencesByName.get(name).push(node);
520
+ }
521
+ });
522
+
523
+ const edits = [];
524
+ const removedDependencyIndices = new Set();
525
+ const letState = { counter: 0, usesPipe: false };
526
+
527
+ for (const name of LOWERABLE_NAMES) {
528
+ const elements = occurrencesByName.get(name);
529
+ if (elements.length === 0) continue;
530
+ const spec = SPECS.get(name);
531
+ // The all-or-nothing rule — see the header. One refusal in the family keeps every
532
+ // occurrence of this name as the component, in this template.
533
+ if (elements.some(element => elementRefuses(element, spec, name)))
534
+ continue;
535
+
536
+ for (const element of elements) {
537
+ const openStart = element.startSourceSpan.start.offset;
538
+ const intrinsic = intrinsicFor(element, spec);
539
+ edits.push({
540
+ start: openStart + 1,
541
+ end: openStart + 1 + name.length,
542
+ text: intrinsic,
543
+ });
544
+ // A self-closing tag (`<View />`) carries an IDENTICAL start/end source span (the
545
+ // whole tag, verified against the installed compiler-cli) — nothing to rewrite
546
+ // there.
547
+ const closeStart = element.endSourceSpan.start.offset;
548
+ if (closeStart !== openStart) {
549
+ edits.push({
550
+ start: closeStart + 2,
551
+ end: closeStart + 2 + name.length,
552
+ text: intrinsic,
553
+ });
554
+ }
555
+ if (spec.observesState) {
556
+ edits.push(...styleEditsFor(element, templateText, letState));
557
+ }
558
+ edits.push(...styleKeyRenameEdit(element));
559
+ }
560
+
561
+ const depIndex = findDependencyIndex(dependenciesNode, name);
562
+ if (depIndex !== -1) removedDependencyIndices.add(depIndex);
563
+ }
564
+
565
+ if (edits.length === 0) return;
566
+
567
+ // Descending by start: each splice only ever touches text strictly AFTER every
568
+ // not-yet-applied (smaller-offset) edit, so offsets never need re-basing mid-loop.
569
+ edits.sort((a, b) => b.start - a.start);
570
+ let nextText = templateText;
571
+ for (const edit of edits) {
572
+ nextText =
573
+ nextText.slice(0, edit.start) + edit.text + nextText.slice(edit.end);
574
+ }
575
+ setTemplateText(templateProp.value, nextText);
576
+ state.set(EDITED_KEY, true);
577
+
578
+ if (removedDependencyIndices.size > 0 && dependenciesNode !== undefined) {
579
+ dependenciesNode.elements = dependenciesNode.elements.filter(
580
+ (_element, index) => !removedDependencyIndices.has(index),
581
+ );
582
+ }
583
+
584
+ if (letState.usesPipe) {
585
+ const pipeEntry = pipeDependencyEntry(programPath);
586
+ if (dependenciesNode === undefined) {
587
+ dependenciesNode = types.arrayExpression([pipeEntry]);
588
+ arg.properties.push(
589
+ types.objectProperty(
590
+ types.identifier('dependencies'),
591
+ dependenciesNode,
592
+ ),
593
+ );
594
+ } else {
595
+ dependenciesNode.elements.push(pipeEntry);
596
+ }
597
+ }
598
+ },
599
+ });
600
+ },
601
+ // This transform edits TWO fields and only one of them survives a linker running in the SAME
602
+ // babel pass: Angular reads an inline template by slicing the FILE'S SOURCE TEXT at the AST
603
+ // node's byte range (`templateFromPartialCode`), so the rewritten `template` string is
604
+ // invisible to it, while the `dependencies` removal — an ordinary array read — lands. What
605
+ // ships then is neither lowered nor un-lowered: the tag is still `<View>` and the directive
606
+ // that answers it has been deleted, so nothing matches, no component template runs, and the
607
+ // screen loses its styles and its handlers. Nothing goes red — every test in this suite
608
+ // links the plugin's PRINTED output, which is a second pass. Device-diagnosed 2026-09-02.
609
+ //
610
+ // Detected by EFFECT rather than by plugin name (the linker's babel key is a generated
611
+ // `base$N`): if `ɵɵdefineComponent` exists at Program exit in a file we edited, the linker
612
+ // ran here. Use `@symbiote-native/angular/metro-transformer`, which prints the lowered code
613
+ // back to text before Metro parses it.
614
+ exit(programPath, state) {
615
+ if (state.get(EDITED_KEY) !== true) return;
616
+ let linkedHere = false;
617
+ programPath.traverse({
618
+ Identifier(path) {
619
+ if (path.node.name === 'ɵɵdefineComponent') linkedHere = true;
620
+ },
621
+ });
622
+ if (!linkedHere) return;
623
+ throw new Error(
624
+ 'symbiote-lower-host-primitives: the Angular linker ran in the same Babel pass, so the ' +
625
+ 'rewritten template never reaches it and only the dependency removal lands — a ' +
626
+ 'half-lowered component that matches no directive at all. Remove this plugin from ' +
627
+ "babel.config.js and set metro.config.js's babelTransformerPath to " +
628
+ "'@symbiote-native/angular/metro-transformer'.",
629
+ );
630
+ },
631
+ },
632
+ },
633
+ };
634
+ };
635
+
636
+ module.exports.LOWERABLE_NAMES = LOWERABLE_NAMES;