@symbiote-native/angular 0.3.3 → 0.5.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 (141) hide show
  1. package/babel-register-composed.cjs +119 -0
  2. package/bin/symbiote-angular-dev.cjs +43 -1
  3. package/build/angular/components/image/shared.d.ts +4 -4
  4. package/build/angular/components/image/shared.js +1 -3
  5. package/build/angular/components/image/shared.js.map +1 -1
  6. package/build/angular/components/{input-accessory-view.d.ts → input-accessory-view/index.d.ts} +1 -1
  7. package/build/angular/components/{input-accessory-view.js → input-accessory-view/index.js} +3 -7
  8. package/build/angular/components/input-accessory-view/index.js.map +1 -0
  9. package/build/angular/components/{keyboard-avoiding-view.js → keyboard-avoiding-view/index.js} +3 -8
  10. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -0
  11. package/build/angular/components/{modal.js → modal/index.js} +3 -8
  12. package/build/angular/components/modal/index.js.map +1 -0
  13. package/build/angular/components/pressable/index.js +13 -14
  14. package/build/angular/components/pressable/index.js.map +1 -1
  15. package/build/angular/components/{refresh-control.js → refresh-control/index.js} +3 -8
  16. package/build/angular/components/refresh-control/index.js.map +1 -0
  17. package/build/angular/components/{safe-area-view.d.ts → safe-area-view/index.d.ts} +1 -1
  18. package/build/angular/components/{safe-area-view.js → safe-area-view/index.js} +3 -7
  19. package/build/angular/components/safe-area-view/index.js.map +1 -0
  20. package/build/angular/components/text-input.js +1 -6
  21. package/build/angular/components/text-input.js.map +1 -1
  22. package/build/angular/components/{touchable.d.ts → touchable/index.d.ts} +1 -1
  23. package/build/angular/components/{touchable.js → touchable/index.js} +4 -4
  24. package/build/angular/components/touchable/index.js.map +1 -0
  25. package/build/angular/components/{touchable-native-feedback.d.ts → touchable-native-feedback/index.d.ts} +1 -1
  26. package/build/angular/components/{touchable-native-feedback.js → touchable-native-feedback/index.js} +4 -9
  27. package/build/angular/components/touchable-native-feedback/index.js.map +1 -0
  28. package/build/angular/{create-portal.js → create-portal/index.js} +1 -1
  29. package/build/angular/create-portal/index.js.map +1 -0
  30. package/build/angular/{create-tunnel.js → create-tunnel/index.js} +1 -1
  31. package/build/angular/create-tunnel/index.js.map +1 -0
  32. package/build/angular/index.d.ts +1 -1
  33. package/build/angular/index.js +1 -1
  34. package/build/angular/index.js.map +1 -1
  35. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +14 -0
  36. package/build/angular/modules/animated/animated-leaf-binder/index.js +91 -0
  37. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +1 -0
  38. package/build/angular/modules/animated/animated-leaf-binder.d.ts +14 -0
  39. package/build/angular/modules/animated/animated-leaf-binder.js +91 -0
  40. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -0
  41. package/build/angular/modules/animated/create-animated-component.d.ts +3 -12
  42. package/build/angular/modules/animated/create-animated-component.js +48 -113
  43. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  44. package/build/angular/modules/{status-bar.js → status-bar/index.js} +1 -1
  45. package/build/angular/modules/status-bar/index.js.map +1 -0
  46. package/build/angular/{render.js → render/index.js} +15 -4
  47. package/build/angular/render/index.js.map +1 -0
  48. package/build/{renderer.js → angular/renderer/index.js} +17 -18
  49. package/build/angular/renderer/index.js.map +1 -0
  50. package/build/components/image/shared.d.ts +4 -4
  51. package/build/components/image/shared.js +1 -3
  52. package/build/components/{input-accessory-view.d.ts → input-accessory-view/index.d.ts} +1 -1
  53. package/build/components/{input-accessory-view.js → input-accessory-view/index.js} +2 -6
  54. package/build/components/{keyboard-avoiding-view.js → keyboard-avoiding-view/index.js} +2 -7
  55. package/build/components/{modal.js → modal/index.js} +2 -7
  56. package/build/components/pressable/index.js +13 -14
  57. package/build/components/{refresh-control.js → refresh-control/index.js} +2 -7
  58. package/build/components/{safe-area-view.d.ts → safe-area-view/index.d.ts} +1 -1
  59. package/build/components/{safe-area-view.js → safe-area-view/index.js} +2 -6
  60. package/build/components/text-input.js +1 -6
  61. package/build/components/{touchable.d.ts → touchable/index.d.ts} +1 -1
  62. package/build/components/{touchable.js → touchable/index.js} +3 -3
  63. package/build/components/{touchable-native-feedback.d.ts → touchable-native-feedback/index.d.ts} +1 -1
  64. package/build/components/{touchable-native-feedback.js → touchable-native-feedback/index.js} +3 -8
  65. package/build/index.d.ts +1 -1
  66. package/build/index.js +1 -1
  67. package/build/modules/animated/animated-leaf-binder/index.d.ts +14 -0
  68. package/build/modules/animated/animated-leaf-binder/index.js +90 -0
  69. package/build/modules/animated/animated-leaf-binder.d.ts +14 -0
  70. package/build/modules/animated/animated-leaf-binder.js +90 -0
  71. package/build/modules/animated/create-animated-component.d.ts +3 -12
  72. package/build/modules/animated/create-animated-component.js +47 -112
  73. package/build/{render.js → render/index.js} +14 -3
  74. package/build/{angular/renderer.js → renderer/index.js} +16 -19
  75. package/package.json +10 -5
  76. package/src/angular-gaps.test.ts +1 -1
  77. package/src/components/image/shared.ts +6 -5
  78. package/src/components/{input-accessory-view.ts → input-accessory-view/index.ts} +11 -8
  79. package/src/components/{input-accessory-view.test.ts → input-accessory-view/input-accessory-view.test.ts} +2 -2
  80. package/src/components/{keyboard-avoiding-view.ts → keyboard-avoiding-view/index.ts} +2 -7
  81. package/src/components/{keyboard-avoiding-view.test.ts → keyboard-avoiding-view/keyboard-avoiding-view.test.ts} +2 -2
  82. package/src/components/{modal.ts → modal/index.ts} +2 -7
  83. package/src/components/{modal.test.ts → modal/modal.test.ts} +2 -2
  84. package/src/components/pressable/index.ts +17 -15
  85. package/src/components/{refresh-control.ts → refresh-control/index.ts} +2 -7
  86. package/src/components/{refresh-control.test.ts → refresh-control/refresh-control.test.ts} +2 -2
  87. package/src/components/{safe-area-view.ts → safe-area-view/index.ts} +7 -8
  88. package/src/components/{safe-area-view.test.ts → safe-area-view/safe-area-view.test.ts} +2 -2
  89. package/src/components/text-input.ts +1 -6
  90. package/src/components/{touchable.ts → touchable/index.ts} +3 -3
  91. package/src/components/{touchable.test.ts → touchable/touchable.test.ts} +2 -2
  92. package/src/components/{touchable-native-feedback.ts → touchable-native-feedback/index.ts} +3 -9
  93. package/src/components/{touchable-native-feedback.test.ts → touchable-native-feedback/touchable-native-feedback.test.ts} +2 -2
  94. package/src/{create-portal.test.ts → create-portal/create-portal.test.ts} +3 -3
  95. package/src/{create-tunnel.test.ts → create-tunnel/create-tunnel.test.ts} +3 -3
  96. package/src/index.ts +1 -1
  97. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +116 -0
  98. package/src/modules/animated/animated-leaf-binder/index.ts +101 -0
  99. package/src/modules/animated/animated-leaf-binder.test.ts +116 -0
  100. package/src/modules/animated/animated-leaf-binder.ts +101 -0
  101. package/src/modules/animated/animated-scroll-view.test.ts +17 -0
  102. package/src/modules/animated/create-animated-component.ts +53 -123
  103. package/src/modules/{status-bar.test.ts → status-bar/status-bar.test.ts} +2 -2
  104. package/src/{render.ts → render/index.ts} +14 -2
  105. package/src/{render.test.ts → render/render.test.ts} +1 -1
  106. package/src/{renderer.ts → renderer/index.ts} +58 -58
  107. package/src/{renderer.test.ts → renderer/renderer.test.ts} +24 -1
  108. package/build/angular/components/input-accessory-view.js.map +0 -1
  109. package/build/angular/components/keyboard-avoiding-view.js.map +0 -1
  110. package/build/angular/components/modal.js.map +0 -1
  111. package/build/angular/components/refresh-control.js.map +0 -1
  112. package/build/angular/components/safe-area-view.js.map +0 -1
  113. package/build/angular/components/touchable-native-feedback.js.map +0 -1
  114. package/build/angular/components/touchable.js.map +0 -1
  115. package/build/angular/create-portal.js.map +0 -1
  116. package/build/angular/create-tunnel.js.map +0 -1
  117. package/build/angular/modules/status-bar.js.map +0 -1
  118. package/build/angular/render.js.map +0 -1
  119. package/build/angular/renderer.js.map +0 -1
  120. /package/build/angular/components/{keyboard-avoiding-view.d.ts → keyboard-avoiding-view/index.d.ts} +0 -0
  121. /package/build/angular/components/{modal.d.ts → modal/index.d.ts} +0 -0
  122. /package/build/angular/components/{refresh-control.d.ts → refresh-control/index.d.ts} +0 -0
  123. /package/build/angular/{create-portal.d.ts → create-portal/index.d.ts} +0 -0
  124. /package/build/angular/{create-tunnel.d.ts → create-tunnel/index.d.ts} +0 -0
  125. /package/build/angular/modules/{status-bar.d.ts → status-bar/index.d.ts} +0 -0
  126. /package/build/angular/{render.d.ts → render/index.d.ts} +0 -0
  127. /package/build/angular/{renderer.d.ts → renderer/index.d.ts} +0 -0
  128. /package/build/components/{keyboard-avoiding-view.d.ts → keyboard-avoiding-view/index.d.ts} +0 -0
  129. /package/build/components/{modal.d.ts → modal/index.d.ts} +0 -0
  130. /package/build/components/{refresh-control.d.ts → refresh-control/index.d.ts} +0 -0
  131. /package/build/{create-portal.d.ts → create-portal/index.d.ts} +0 -0
  132. /package/build/{create-portal.js → create-portal/index.js} +0 -0
  133. /package/build/{create-tunnel.d.ts → create-tunnel/index.d.ts} +0 -0
  134. /package/build/{create-tunnel.js → create-tunnel/index.js} +0 -0
  135. /package/build/modules/{status-bar.d.ts → status-bar/index.d.ts} +0 -0
  136. /package/build/modules/{status-bar.js → status-bar/index.js} +0 -0
  137. /package/build/{render.d.ts → render/index.d.ts} +0 -0
  138. /package/build/{renderer.d.ts → renderer/index.d.ts} +0 -0
  139. /package/src/{create-portal.ts → create-portal/index.ts} +0 -0
  140. /package/src/{create-tunnel.ts → create-tunnel/index.ts} +0 -0
  141. /package/src/modules/{status-bar.ts → status-bar/index.ts} +0 -0
@@ -52,12 +52,14 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
52
52
  return useValue ? value : void 0;
53
53
  };
54
54
  import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, Directive, ElementRef, inject, ViewChild, } from '@angular/core';
55
- import { AnimatedProps, attachNativeEventHandler, dlog, isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, whenCommitted, } from '@symbiote-native/engine';
55
+ import { isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
56
+ import { selectScrollIntrinsics } from '@symbiote-native/components';
56
57
  import { Image, ScrollView, Text, View } from '../../components';
57
58
  import { anchorHostStyle, ViewHost, TextHost, ImageHost, ScrollViewHost, SymbioteHostPropsDirective, } from '../../primitives';
58
59
  import { FlatList } from '../../components/flat-list';
59
60
  import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps, } from '../../components/image/shared';
60
61
  import { SectionList } from '../../components/section-list';
62
+ import { AnimatedLeafBinder } from './animated-leaf-binder';
61
63
  // RN's prop carrying explicit (already-rasterized) values that override the animated prop in
62
64
  // the COMMITTED props (sticky-header passthrough). Named once so the directive input and the
63
65
  // reconcile agree.
@@ -105,12 +107,11 @@ let AnimatedComponentBase = (() => {
105
107
  // The directive sitting on the inner host primitive; its `node` is the committed SymbioteNode
106
108
  // the leaf binds to. Inherited by the decorated subclass (Angular collects base-class queries).
107
109
  hostDirective = __runInitializers(this, _hostDirective_initializers, void 0);
108
- // The leaf currently wired into the value graph. Null until the first reconcile.
109
- attached = (__runInitializers(this, _hostDirective_extraInitializers), null);
110
- // The whenCommitted waiter for the current leaf's node binding, cancelled before the next.
111
- cancelBind;
112
- // Detachers for any Animated.event prop bound natively to the committed node (JS path: none).
113
- eventDetachers = [];
110
+ // The leaf-lifecycle orchestration (build/attach/swap/bind/detach an AnimatedProps leaf) is a
111
+ // Pure Fabrication shared with AnimatedImage, which cannot extend this class (it must extend
112
+ // ImageBase see the file header). `resolveNode` stays a resolver, not a captured node: the
113
+ // committed node only exists after `hostDirective` is populated post-view-init.
114
+ leafBinder = (__runInitializers(this, _hostDirective_extraInitializers), new AnimatedLeafBinder(() => this.hostNode(), this.constructor.name));
114
115
  // Set once the view (and the inner host directive) exists; ngOnChanges before then is a no-op.
115
116
  viewReady = false;
116
117
  // The concrete props for the host element each change-detection pass: animated entries reduced
@@ -134,73 +135,21 @@ let AnimatedComponentBase = (() => {
134
135
  this.reconcile();
135
136
  }
136
137
  ngOnDestroy() {
137
- this.cancelBind?.();
138
- this.cancelBind = undefined;
139
- this.detachEvents();
140
- if (this.attached !== null) {
141
- this.attached.__detach();
142
- this.attached = null;
143
- }
138
+ this.leafBinder.destroy();
144
139
  }
145
- // Build a fresh AnimatedProps leaf from the current props (the React/Vue per-render
146
- // useMemo(rest)), wire it into the graph, and swap the previous one out. Attach the NEW leaf
147
- // BEFORE detaching the OLD one: a shared Value self-detaches (dropping its native node) the
148
- // instant its child count hits zero, so detaching first would kill a running native animation
149
- // on any unrelated re-render (mirrors RN's AnimatedComponent._attachProps). Then bind the
150
- // committed node, go native if wanted, and rebind native events.
140
+ // A non-stable [animatedProps]/[style] input (a fresh object literal in the template) pushes
141
+ // a new reference on every change-detection pass of the HOST view, re-triggering this on every
142
+ // unrelated interaction elsewhere in the app DEBUG=1 turns this into a visible tick count in
143
+ // AnimatedLeafBinder.reconcile, so an unexpectedly high rate for a component nobody is
144
+ // animating is the tell (see AnimatedScrollView's stable-reference fix in examples/angular/App.ts).
151
145
  reconcile() {
152
146
  if (!this.viewReady)
153
147
  return;
154
- // A non-stable [animatedProps]/[style] input (a fresh object literal in the template)
155
- // pushes a new reference on every change-detection pass of the HOST view, re-triggering
156
- // this on every unrelated interaction elsewhere in the app — DEBUG=1 turns this into a
157
- // visible tick count, so an unexpectedly high rate for a component nobody is animating
158
- // is the tell (see AnimatedScrollView's stable-reference fix in examples/angular/App.ts).
159
- dlog(`AnimatedComponentBase reconcile (${this.constructor.name})`);
160
148
  const props = this.mergedProps();
161
149
  const hasPassthroughAnimatedValues = this.passthroughAnimatedPropExplicitValues !== null &&
162
150
  this.passthroughAnimatedPropExplicitValues !== undefined;
163
151
  const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
164
- const newLeaf = new AnimatedProps(props);
165
- newLeaf.__attach();
166
- if (this.attached !== null && this.attached !== newLeaf)
167
- this.attached.__detach();
168
- this.attached = newLeaf;
169
- this.bindNode(newLeaf, props, wantsNative);
170
- }
171
- // Bind the leaf to the host's Fabric node THROUGH whenCommitted: under Angular's zoneless
172
- // batched change detection the inner host's tag does not exist yet at ngAfterViewInit time
173
- // (the same async-commit gotcha Vue documents), so binding eagerly would no-op. whenCommitted
174
- // runs the action now if already committed, else after the commit that assigns the tag.
175
- bindNode(leaf, props, wantsNative) {
176
- this.cancelBind?.();
177
- this.cancelBind = undefined;
178
- const node = this.hostNode();
179
- if (node === null)
180
- return;
181
- this.cancelBind = whenCommitted(node, () => {
182
- leaf.setNativeView(node);
183
- if (wantsNative)
184
- leaf.__makeNative();
185
- this.attachEvents(node, props);
186
- });
187
- }
188
- // Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
189
- // to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
190
- // native event handler with a committed tag, so the JS path stays the fallback. Rebound each
191
- // reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
192
- attachEvents(node, props) {
193
- this.detachEvents();
194
- for (const key of Object.keys(props)) {
195
- const attachment = attachNativeEventHandler(node, key, props[key]);
196
- if (attachment !== undefined)
197
- this.eventDetachers.push(attachment.detach);
198
- }
199
- }
200
- detachEvents() {
201
- for (const detach of this.eventDetachers)
202
- detach();
203
- this.eventDetachers = [];
152
+ this.leafBinder.reconcile(props, wantsNative);
204
153
  }
205
154
  // Merge the dedicated `style` input over the generic `animatedProps` bag into one props map —
206
155
  // the equivalent of React/Vue `rest`. `style` wins so `[style]` and `[animatedProps].style`
@@ -333,9 +282,10 @@ let AnimatedImage = (() => {
333
282
  // comment on AnimatedComponentBase.elementRef.
334
283
  elementRef = inject(ElementRef);
335
284
  hostDirective = __runInitializers(this, _hostDirective_initializers, void 0);
336
- attached = (__runInitializers(this, _hostDirective_extraInitializers), null);
337
- cancelBind;
338
- eventDetachers = [];
285
+ // Same Pure Fabrication AnimatedComponentBase holds — see its matching comment. AnimatedImage
286
+ // can't extend AnimatedComponentBase (it must extend ImageBase, see the file header), so it
287
+ // gets the leaf-lifecycle via composition instead of duplicating it.
288
+ leafBinder = (__runInitializers(this, _hostDirective_extraInitializers), new AnimatedLeafBinder(() => this.hostNode(), this.constructor.name));
339
289
  viewReady = false;
340
290
  get animatedImageProps() {
341
291
  const reduced = reduceProps(this.mergedProps());
@@ -356,13 +306,7 @@ let AnimatedImage = (() => {
356
306
  this.reconcile();
357
307
  }
358
308
  ngOnDestroy() {
359
- this.cancelBind?.();
360
- this.cancelBind = undefined;
361
- this.detachEvents();
362
- if (this.attached !== null) {
363
- this.attached.__detach();
364
- this.attached = null;
365
- }
309
+ this.leafBinder.destroy();
366
310
  }
367
311
  reconcile() {
368
312
  if (!this.viewReady)
@@ -371,38 +315,7 @@ let AnimatedImage = (() => {
371
315
  const hasPassthroughAnimatedValues = this.passthroughAnimatedPropExplicitValues !== null &&
372
316
  this.passthroughAnimatedPropExplicitValues !== undefined;
373
317
  const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
374
- const newLeaf = new AnimatedProps(props);
375
- newLeaf.__attach();
376
- if (this.attached !== null && this.attached !== newLeaf)
377
- this.attached.__detach();
378
- this.attached = newLeaf;
379
- this.bindNode(newLeaf, props, wantsNative);
380
- }
381
- bindNode(leaf, props, wantsNative) {
382
- this.cancelBind?.();
383
- this.cancelBind = undefined;
384
- const node = this.hostNode();
385
- if (node === null)
386
- return;
387
- this.cancelBind = whenCommitted(node, () => {
388
- leaf.setNativeView(node);
389
- if (wantsNative)
390
- leaf.__makeNative();
391
- this.attachEvents(node, props);
392
- });
393
- }
394
- attachEvents(node, props) {
395
- this.detachEvents();
396
- for (const key of Object.keys(props)) {
397
- const attachment = attachNativeEventHandler(node, key, props[key]);
398
- if (attachment !== undefined)
399
- this.eventDetachers.push(attachment.detach);
400
- }
401
- }
402
- detachEvents() {
403
- for (const detach of this.eventDetachers)
404
- detach();
405
- this.eventDetachers = [];
318
+ this.leafBinder.reconcile(props, wantsNative);
406
319
  }
407
320
  mergedProps() {
408
321
  const props = { ...(this.animatedProps ?? {}) };
@@ -429,7 +342,7 @@ let AnimatedScrollView = (() => {
429
342
  inputs: ANIMATED_INPUTS,
430
343
  template: `
431
344
  <symbiote-scroll-view [symbioteHostProps]="reducedProps">
432
- <symbiote-scroll-content [collapsable]="false">
345
+ <symbiote-scroll-content [symbioteHostProps]="contentProps">
433
346
  <ng-content></ng-content>
434
347
  </symbiote-scroll-content>
435
348
  </symbiote-scroll-view>
@@ -454,11 +367,33 @@ let AnimatedScrollView = (() => {
454
367
  // receives touch (swallowed by the outer scroll view; iOS doesn't gate on this). The real
455
368
  // ScrollView component already defaults this (scroll-view/shared.ts), but AnimatedScrollView
456
369
  // talks to the raw primitive directly and has no such defaulting, so it must apply it too.
370
+ //
371
+ // scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the OTHER thing
372
+ // the real ScrollView applies that this bespoke template used to skip entirely — without it,
373
+ // iOS Fabric never clips the scroll view's content to its own frame (Android's native
374
+ // ViewGroup clips regardless of the style prop, which is why this was invisible there). This
375
+ // wrapper has no dedicated `horizontal` input (only the generic `animatedProps`/`style`
376
+ // surface), and its template is hardcoded to the vertical intrinsics
377
+ // (symbiote-scroll-view/symbiote-scroll-content, not the horizontal variants), so
378
+ // selectScrollIntrinsics is always called with isHorizontal=false here — matching what the
379
+ // template can actually render.
457
380
  get reducedProps() {
458
381
  const reduced = super.reducedProps;
459
- return reduced.nestedScrollEnabled === undefined
460
- ? { ...reduced, nestedScrollEnabled: true }
461
- : reduced;
382
+ const { scrollViewBaseStyle } = selectScrollIntrinsics(false, undefined);
383
+ const withScrollBase = {
384
+ ...reduced,
385
+ style: [scrollViewBaseStyle, reduced.style],
386
+ };
387
+ return withScrollBase.nestedScrollEnabled === undefined
388
+ ? { ...withScrollBase, nestedScrollEnabled: true }
389
+ : withScrollBase;
390
+ }
391
+ // The content (inner) view's props: contentStyle from the same intrinsics selection, plus
392
+ // `collapsable: false` (the Android multi-child fix this file's tests already cover) — mirrors
393
+ // the real ScrollView's own `contentProps` getter (scroll-view/shared.ts).
394
+ get contentProps() {
395
+ const { contentStyle } = selectScrollIntrinsics(false, undefined);
396
+ return { style: contentStyle, collapsable: false };
462
397
  }
463
398
  };
464
399
  return AnimatedScrollView = _classThis;
@@ -6,9 +6,9 @@
6
6
  // adapters/vue/src/render.ts.
7
7
  import { createElement as createEngineElement, createSurface, disposeRoot, dlog, toPublicInstance, } from '@symbiote-native/engine';
8
8
  import { descriptorFor } from '@symbiote-native/components';
9
- import { ApplicationRef, createComponent, createEnvironmentInjector, DOCUMENT, RendererFactory2, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal, } from '@angular/core';
10
- import { SymbioteRendererFactory } from './renderer';
11
- import { ColorSchemeService, WindowDimensionsService } from './services';
9
+ import { ApplicationRef, createComponent, createEnvironmentInjector, DOCUMENT, ErrorHandler, RendererFactory2, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal, } from '@angular/core';
10
+ import { SymbioteRendererFactory } from '../renderer';
11
+ import { ColorSchemeService, WindowDimensionsService } from '../services';
12
12
  // The two FFI-edge casts of this adapter, both confined here to bootstrap. Angular's core
13
13
  // types model a browser: the injector parent is a non-null EnvironmentInjector, the host is
14
14
  // a DOM Element. Our root has neither shape — a null parent IS a root injector, and the host
@@ -98,6 +98,17 @@ export function mount(rootTag, rootComponent, options) {
98
98
  // `@Component` boundary — SignalView-compiled children are skip-eligible regardless of
99
99
  // this scheduler.
100
100
  ...provideZonelessChangeDetectionInternal(),
101
+ // Angular's own INTERNAL_APPLICATION_ERROR_HANDLER (core.mjs) reports a tick()
102
+ // exception by calling `injector.get(ErrorHandler)` — a normal `bootstrapApplication`
103
+ // registers this token by default (platform-browser's BROWSER_MODULE_PROVIDERS), but
104
+ // our from-scratch environment injector never did, so that lookup itself threw
105
+ // NG0201 and REPLACED whatever the real error was with an unrelated "No provider
106
+ // found for ErrorHandler" — the real exception never got logged, and the NG0201 itself
107
+ // propagated out of a bare Timeout callback uncaught (nothing above it in the stack to
108
+ // catch it), i.e. any async tick() exception, anywhere in the app, crashed hard instead
109
+ // of being reported. Providing the default `ErrorHandler` (same one bootstrapApplication
110
+ // ships) restores the intended behavior: `console.error('ERROR', e)` and keep running.
111
+ { provide: ErrorHandler, useClass: ErrorHandler },
101
112
  ColorSchemeService,
102
113
  WindowDimensionsService,
103
114
  ], rootInjectorParent());
@@ -6,7 +6,7 @@
6
6
  // that the same engine mutation API drives both frameworks.
7
7
  import { appendChild, createAnchor, createElement, createRawText, dlog, getExplicitStyle, insertBefore, isDebug, isSymbioteNode, removeChild, routeProp, setEventListener, setText, toPublicInstance, RAW_TEXT_COMPONENT, SymbioteSurface, } from '@symbiote-native/engine';
8
8
  import { descriptorFor } from '@symbiote-native/components';
9
- import { getScrollViewProjection, removeScrollViewProjectedChild, } from './components/scroll-view/projection';
9
+ import { getScrollViewProjection, removeScrollViewProjectedChild, } from '../components/scroll-view/projection';
10
10
  function isSurface(parent) {
11
11
  return parent instanceof SymbioteSurface;
12
12
  }
@@ -47,29 +47,27 @@ const PRIMITIVE_SELECTOR_ALIAS = {
47
47
  // anchor host (see the set comment below). Tests register their own child-component selectors
48
48
  // through this at module load; app-authored composed components can too.
49
49
  export function registerComposedComponent(selector) {
50
- ANCHOR_HOST_COMPONENTS.add(selector);
50
+ ANCHOR_HOST_COMPONENTS.add(selector.toLowerCase());
51
51
  }
52
+ // Lowercased at construction — createElement's lookup and registerComposedComponent's insert
53
+ // both normalize to lowercase (Angular lowercases a dynamically-mounted component's selector at
54
+ // runtime, see §11a), so the literal entries below must match that or every capitalized one
55
+ // (all but the handful already written lowercase) silently never matches .has() and falls
56
+ // through to a real Fabric createNode — the exact "Unimplemented component" / extra-wrapper-node
57
+ // bug this Set exists to prevent.
52
58
  const ANCHOR_HOST_COMPONENTS = new Set([
53
59
  // Composed Angular components render their real Fabric descriptor tree from the template;
54
60
  // their Angular host element is only a framework bookkeeping node and must not paint. This
55
61
  // is NOT limited to adapter-authored components — ANY custom composed @Component used as a
56
- // plain <Tag> inside another template needs its selector listed here too, adapter or app
57
- // code alike, or Angular's automatic host-element creation for it falls through to a raw
58
- // Fabric createNode call with an unrecognized view name, which paints RN's own "Unimplemented
59
- // component: <Tag>" fallback view instead (a real device-visible bug, not a silent no-op
60
- // discovered when examples/angular's own new demo components hit this).
61
- 'AccessibilityDemo',
62
+ // plain <Tag> inside another template needs its selector listed here too, or Angular's
63
+ // automatic host-element creation for it falls through to a raw Fabric createNode call with
64
+ // an unrecognized view name, which paints RN's own "Unimplemented component: <Tag>" fallback
65
+ // view instead (a real device-visible bug, not a silent no-op). This Set holds only
66
+ // adapter/engine-owned selectors; app code and third-party packages self-register their own
67
+ // composed components through registerComposedComponent instead of being hardcoded here.
62
68
  'ActivityIndicator',
63
- 'AnimatedDemo',
64
- 'AnimatedParityDemo',
65
69
  'Button',
66
70
  'FlatList',
67
- 'NativeModulesDemo',
68
- 'ParityDemo',
69
- 'PlatformColorDemo',
70
- 'RefApiDemo',
71
- 'ResponderDemo',
72
- 'Slider',
73
71
  'AnimatedView',
74
72
  'symbiote-animated-view',
75
73
  'AnimatedText',
@@ -103,7 +101,7 @@ const ANCHOR_HOST_COMPONENTS = new Set([
103
101
  'VirtualizedList',
104
102
  'VirtualizedSectionList',
105
103
  'symbiote-pressable',
106
- ]);
104
+ ].map(selector => selector.toLowerCase()));
107
105
  // Inserting a bare raw-text node anywhere but inside a <Text> is invalid in Fabric (a
108
106
  // stray RCTRawText would paint). Angular's ɵɵtext only ever lands text inside a <Text>,
109
107
  // but guard anyway for parity with the Vue adapter and to fail loudly on a bad template.
@@ -134,7 +132,7 @@ export class SymbioteRenderer {
134
132
  // ViewConfig). toPublicInstance grafts the imperative API (measure / setNativeProps /
135
133
  // focus) onto the raw node in place, returning the SAME identity the commit mirror keys on.
136
134
  const engineName = PRIMITIVE_SELECTOR_ALIAS[name] ?? name;
137
- if (ANCHOR_HOST_COMPONENTS.has(engineName)) {
135
+ if (ANCHOR_HOST_COMPONENTS.has(engineName.toLowerCase())) {
138
136
  const anchor = tagAnchorForDebug(createAnchor());
139
137
  dlog(`angular createElement ${name} -> anchor host ${describeHost(anchor)}`);
140
138
  return anchor;
@@ -355,4 +353,3 @@ export class SymbioteRendererFactory {
355
353
  return (this.renderer ??= new SymbioteRenderer(this.surface));
356
354
  }
357
355
  }
358
- //# sourceMappingURL=renderer.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/angular",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "SymbioteNative's Angular adapter — a Renderer2/RendererFactory2 with DOM-less bootstrap driving real native iOS/Android views through the same engine as the other adapters.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,6 +30,7 @@
30
30
  "./metro-css-parser": "./metro-css-parser.cjs",
31
31
  "./metro-config": "./metro-config.cjs",
32
32
  "./babel-linker": "./babel-linker.cjs",
33
+ "./babel-register-composed": "./babel-register-composed.cjs",
33
34
  "./tsconfig.angular.base.json": "./tsconfig.angular.base.json"
34
35
  },
35
36
  "files": [
@@ -39,6 +40,7 @@
39
40
  "metro-css-parser.cjs",
40
41
  "metro-config.cjs",
41
42
  "babel-linker.cjs",
43
+ "babel-register-composed.cjs",
42
44
  "tsconfig.angular.base.json"
43
45
  ],
44
46
  "bin": {
@@ -50,20 +52,23 @@
50
52
  "dependencies": {
51
53
  "@angular/compiler-cli": "^22",
52
54
  "@react-native/typescript-config": "0.86.0",
53
- "@symbiote-native/components": "0.2.3",
54
- "@symbiote-native/engine": "0.1.4",
55
- "@symbiote-native/css-parser": "0.2.1"
55
+ "@symbiote-native/components": "0.2.5",
56
+ "@symbiote-native/css-parser": "0.2.2"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "@angular/core": ">=20",
60
+ "@symbiote-native/engine": ">=0.1.6",
59
61
  "react-native": ">=0.86"
60
62
  },
61
63
  "devDependencies": {
62
64
  "@angular/compiler": "^22",
63
65
  "@angular/core": "^22",
66
+ "@babel/core": "^7.25.2",
67
+ "@types/babel__core": "^7.20.5",
64
68
  "@types/node": "^26.0.0",
65
69
  "typescript": "~6.0.0",
66
- "@symbiote-native/test-utils": "0.1.5"
70
+ "@symbiote-native/test-utils": "0.1.5",
71
+ "@symbiote-native/engine": "0.1.6"
67
72
  },
68
73
  "scripts": {
69
74
  "ng:build": "ngc -p tsconfig.angular.json",
@@ -62,7 +62,7 @@ describe('Angular adapter gap regressions', () => {
62
62
 
63
63
  it('Button forwards its full accessibility and TV-focus surface through TouchableOpacity', () => {
64
64
  const buttonSource = readSource('adapters/angular/src/components/button.ts');
65
- const touchableSource = readSource('adapters/angular/src/components/touchable.ts');
65
+ const touchableSource = readSource('adapters/angular/src/components/touchable/index.ts');
66
66
  const pressableSource = readSource('adapters/angular/src/components/pressable/index.ts');
67
67
 
68
68
  for (const binding of [
@@ -9,7 +9,12 @@ import {
9
9
  type IImageSourceProp,
10
10
  type IResizeMode,
11
11
  } from '@symbiote-native/components';
12
- import type { IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
12
+ import {
13
+ isSymbioteEvent,
14
+ type IStyleProp,
15
+ type ISymbioteEvent,
16
+ type IViewStyle,
17
+ } from '@symbiote-native/engine';
13
18
 
14
19
  export { setImageSourceResolver } from '@symbiote-native/components';
15
20
  export type {
@@ -153,10 +158,6 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
153
158
  return typeof value === 'object' && value !== null ? value : undefined;
154
159
  }
155
160
 
156
- function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
157
- return typeof value === 'object' && value !== null && Reflect.has(value, 'nativeEvent');
158
- }
159
-
160
161
  export function resolveImageProps(input: Record<string, unknown>): Record<string, unknown> {
161
162
  const passthrough: IImagePassthroughProps = {
162
163
  resizeMethod: input['resizeMethod'] as IImageProps['resizeMethod'],
@@ -21,14 +21,17 @@ import {
21
21
  type IAccessibilityStateValue,
22
22
  type IAriaProps,
23
23
  } from '@symbiote-native/components';
24
- import type { IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
25
- import { anchorHostStyle, InputAccessoryViewHost, SymbioteHostPropsDirective } from '../primitives';
26
-
27
- // Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
28
- // so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
29
- function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
30
- return typeof value === 'object' && value !== null && 'nativeEvent' in value;
31
- }
24
+ import {
25
+ isSymbioteEvent,
26
+ type IStyleProp,
27
+ type ISymbioteEvent,
28
+ type IViewStyle,
29
+ } from '@symbiote-native/engine';
30
+ import {
31
+ anchorHostStyle,
32
+ InputAccessoryViewHost,
33
+ SymbioteHostPropsDirective,
34
+ } from '../../primitives';
32
35
 
33
36
  // Mirrors React's IInputAccessoryViewProps minus children (Angular takes children via
34
37
  // <ng-content>), declared per-adapter over the shared a11y base.
@@ -4,8 +4,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
  import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
5
5
  import { installFabric } from '@symbiote-native/test-utils';
6
6
 
7
- import { mount, unmount } from '../render';
8
- import { InputAccessoryView } from './input-accessory-view';
7
+ import { mount, unmount } from '../../render';
8
+ import { InputAccessoryView } from './index';
9
9
 
10
10
  const ROOT_TAG = 913;
11
11
  const fabric = installFabric();
@@ -42,21 +42,16 @@ import {
42
42
  Keyboard,
43
43
  KEYBOARD_EVENT,
44
44
  dlog,
45
+ isSymbioteEvent,
45
46
  type IEventSubscription,
46
47
  type IStyleProp,
47
48
  type ISymbioteEvent,
48
49
  type IViewStyle,
49
50
  } from '@symbiote-native/engine';
50
- import { anchorHostStyle, SymbioteHostPropsDirective, ViewHost } from '../primitives';
51
+ import { anchorHostStyle, SymbioteHostPropsDirective, ViewHost } from '../../primitives';
51
52
 
52
53
  export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
53
54
 
54
- // Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
55
- // so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
56
- function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
57
- return typeof value === 'object' && value !== null && 'nativeEvent' in value;
58
- }
59
-
60
55
  // Mirrors React's IKeyboardAvoidingViewProps minus children (Angular takes children via
61
56
  // <ng-content>), declared per-adapter over the shared accessibility base since a framework-specific
62
57
  // children field keeps it from being fully shared across adapters.
@@ -4,8 +4,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
  import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
5
5
  import { installFabric } from '@symbiote-native/test-utils';
6
6
 
7
- import { mount, unmount } from '../render';
8
- import { KeyboardAvoidingView } from './keyboard-avoiding-view';
7
+ import { mount, unmount } from '../../render';
8
+ import { KeyboardAvoidingView } from './index';
9
9
 
10
10
  const ROOT_TAG = 911;
11
11
  const fabric = installFabric();
@@ -39,11 +39,12 @@ import {
39
39
  } from '@symbiote-native/components';
40
40
  import {
41
41
  dlog,
42
+ isSymbioteEvent,
42
43
  type IStyleProp,
43
44
  type ISymbioteEvent,
44
45
  type IViewStyle,
45
46
  } from '@symbiote-native/engine';
46
- import { anchorHostStyle, ModalHost, SymbioteHostPropsDirective, ViewHost } from '../primitives';
47
+ import { anchorHostStyle, ModalHost, SymbioteHostPropsDirective, ViewHost } from '../../primitives';
47
48
 
48
49
  export type {
49
50
  IModalAnimationType,
@@ -52,12 +53,6 @@ export type {
52
53
  IModalOrientationChangeEvent,
53
54
  } from '@symbiote-native/components';
54
55
 
55
- // Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
56
- // so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
57
- function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
58
- return typeof value === 'object' && value !== null && 'nativeEvent' in value;
59
- }
60
-
61
56
  const ORIENTATIONS: ReadonlyArray<IModalOrientation> = ['portrait', 'landscape'];
62
57
 
63
58
  function readOrientation(event: ISymbioteEvent): IModalOrientationChangeEvent | undefined {
@@ -4,8 +4,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
  import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
5
5
  import { installFabric } from '@symbiote-native/test-utils';
6
6
 
7
- import { mount, unmount } from '../render';
8
- import { Modal } from './modal';
7
+ import { mount, unmount } from '../../render';
8
+ import { Modal } from './index';
9
9
 
10
10
  const ROOT_TAG = 912;
11
11
  const fabric = installFabric();