@symbiote-native/angular 0.6.2 → 0.8.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 (191) hide show
  1. package/README.md +3 -3
  2. package/bin/symbiote-angular-dev.cjs +20 -19
  3. package/build/angular/anchor-host-registry.js +2 -2
  4. package/build/angular/anchor-host-registry.js.map +1 -1
  5. package/build/angular/bootstrap.js +1 -1
  6. package/build/angular/bootstrap.js.map +1 -1
  7. package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
  8. package/build/angular/components/activity-indicator/index.android.js +12 -4
  9. package/build/angular/components/activity-indicator/index.android.js.map +1 -1
  10. package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
  11. package/build/angular/components/activity-indicator/index.ios.js +8 -3
  12. package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
  13. package/build/angular/components/activity-indicator/shared.d.ts +1 -1
  14. package/build/angular/components/activity-indicator/shared.js +4 -4
  15. package/build/angular/components/activity-indicator/shared.js.map +1 -1
  16. package/build/angular/components/button.d.ts +4 -2
  17. package/build/angular/components/button.js +41 -22
  18. package/build/angular/components/button.js.map +1 -1
  19. package/build/angular/components/flat-list/index.d.ts +6 -4
  20. package/build/angular/components/flat-list/index.js +80 -50
  21. package/build/angular/components/flat-list/index.js.map +1 -1
  22. package/build/angular/components/image/index.android.d.ts +8 -3
  23. package/build/angular/components/image/index.android.js +42 -10
  24. package/build/angular/components/image/index.android.js.map +1 -1
  25. package/build/angular/components/image/index.ios.d.ts +8 -3
  26. package/build/angular/components/image/index.ios.js +42 -10
  27. package/build/angular/components/image/index.ios.js.map +1 -1
  28. package/build/angular/components/image/shared.d.ts +4 -1
  29. package/build/angular/components/image/shared.js +51 -8
  30. package/build/angular/components/image/shared.js.map +1 -1
  31. package/build/angular/components/image-background.d.ts +2 -1
  32. package/build/angular/components/image-background.js +26 -10
  33. package/build/angular/components/image-background.js.map +1 -1
  34. package/build/angular/components/input-accessory-view/index.d.ts +9 -4
  35. package/build/angular/components/input-accessory-view/index.js +49 -10
  36. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  37. package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -1
  38. package/build/angular/components/keyboard-avoiding-view/index.js +44 -12
  39. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  40. package/build/angular/components/modal/index.d.ts +11 -8
  41. package/build/angular/components/modal/index.js +51 -29
  42. package/build/angular/components/modal/index.js.map +1 -1
  43. package/build/angular/components/pressable/index.d.ts +12 -6
  44. package/build/angular/components/pressable/index.js +66 -13
  45. package/build/angular/components/pressable/index.js.map +1 -1
  46. package/build/angular/components/refresh-control/index.d.ts +8 -4
  47. package/build/angular/components/refresh-control/index.js +54 -16
  48. package/build/angular/components/refresh-control/index.js.map +1 -1
  49. package/build/angular/components/safe-area-view/index.d.ts +9 -4
  50. package/build/angular/components/safe-area-view/index.js +48 -9
  51. package/build/angular/components/safe-area-view/index.js.map +1 -1
  52. package/build/angular/components/scroll-view/index.android.d.ts +2 -3
  53. package/build/angular/components/scroll-view/index.android.js +88 -61
  54. package/build/angular/components/scroll-view/index.android.js.map +1 -1
  55. package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
  56. package/build/angular/components/scroll-view/index.ios.js +24 -18
  57. package/build/angular/components/scroll-view/index.ios.js.map +1 -1
  58. package/build/angular/components/scroll-view/projection.d.ts +35 -0
  59. package/build/angular/components/scroll-view/projection.js +279 -53
  60. package/build/angular/components/scroll-view/projection.js.map +1 -1
  61. package/build/angular/components/scroll-view/shared.d.ts +14 -5
  62. package/build/angular/components/scroll-view/shared.js +225 -75
  63. package/build/angular/components/scroll-view/shared.js.map +1 -1
  64. package/build/angular/components/scroll-view/sticky-header.js +21 -22
  65. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  66. package/build/angular/components/section-list/index.d.ts +7 -1
  67. package/build/angular/components/section-list/index.js +91 -54
  68. package/build/angular/components/section-list/index.js.map +1 -1
  69. package/build/angular/components/switch/index.android.d.ts +7 -3
  70. package/build/angular/components/switch/index.android.js +52 -10
  71. package/build/angular/components/switch/index.android.js.map +1 -1
  72. package/build/angular/components/switch/index.ios.d.ts +7 -3
  73. package/build/angular/components/switch/index.ios.js +52 -10
  74. package/build/angular/components/switch/index.ios.js.map +1 -1
  75. package/build/angular/components/switch/shared.d.ts +13 -4
  76. package/build/angular/components/switch/shared.js +71 -7
  77. package/build/angular/components/switch/shared.js.map +1 -1
  78. package/build/angular/components/text-input.d.ts +11 -3
  79. package/build/angular/components/text-input.js +68 -8
  80. package/build/angular/components/text-input.js.map +1 -1
  81. package/build/angular/components/touchable/index.d.ts +40 -9
  82. package/build/angular/components/touchable/index.js +303 -67
  83. package/build/angular/components/touchable/index.js.map +1 -1
  84. package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
  85. package/build/angular/components/touchable-native-feedback/index.js +63 -27
  86. package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
  87. package/build/angular/components/virtualized-list/directives.js +22 -18
  88. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  89. package/build/angular/components/virtualized-list/index.d.ts +15 -4
  90. package/build/angular/components/virtualized-list/index.js +326 -94
  91. package/build/angular/components/virtualized-list/index.js.map +1 -1
  92. package/build/angular/components/virtualized-section-list/directives.js +12 -12
  93. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  94. package/build/angular/components/virtualized-section-list/index.d.ts +19 -1
  95. package/build/angular/components/virtualized-section-list/index.js +93 -22
  96. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  97. package/build/angular/components.d.ts +2 -2
  98. package/build/angular/components.js +5 -0
  99. package/build/angular/components.js.map +1 -1
  100. package/build/angular/create-portal/index.js +34 -36
  101. package/build/angular/create-portal/index.js.map +1 -1
  102. package/build/angular/create-tunnel/index.js +19 -22
  103. package/build/angular/create-tunnel/index.js.map +1 -1
  104. package/build/angular/descriptor-to-angular/index.js +6 -4
  105. package/build/angular/descriptor-to-angular/index.js.map +1 -1
  106. package/build/angular/diagnostics.d.ts +33 -0
  107. package/build/angular/diagnostics.js +82 -0
  108. package/build/angular/diagnostics.js.map +1 -0
  109. package/build/angular/host-instance/index.js +1 -1
  110. package/build/angular/host-instance/index.js.map +1 -1
  111. package/build/angular/index.d.ts +9 -4
  112. package/build/angular/index.js +17 -3
  113. package/build/angular/index.js.map +1 -1
  114. package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
  115. package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
  116. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
  117. package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
  118. package/build/angular/modules/animated/create-animated-component.js +106 -55
  119. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  120. package/build/angular/modules/status-bar/index.js +4 -4
  121. package/build/angular/modules/status-bar/index.js.map +1 -1
  122. package/build/angular/primitives/index.d.ts +20 -3
  123. package/build/angular/primitives/index.js +84 -48
  124. package/build/angular/primitives/index.js.map +1 -1
  125. package/build/angular/primitives/shared.d.ts +48 -2
  126. package/build/angular/primitives/shared.js +158 -11
  127. package/build/angular/primitives/shared.js.map +1 -1
  128. package/build/angular/render/index.js +69 -38
  129. package/build/angular/render/index.js.map +1 -1
  130. package/build/angular/renderer/index.d.ts +1 -0
  131. package/build/angular/renderer/index.js +37 -2
  132. package/build/angular/renderer/index.js.map +1 -1
  133. package/build/angular/services/color-scheme.service.js +3 -3
  134. package/build/angular/services/color-scheme.service.js.map +1 -1
  135. package/build/angular/services/window-dimensions.service.js +4 -4
  136. package/build/angular/services/window-dimensions.service.js.map +1 -1
  137. package/package.json +10 -8
  138. package/src/anchor-host-registry.ts +2 -2
  139. package/src/bootstrap.ts +4 -1
  140. package/src/components/activity-indicator/index.android.ts +8 -1
  141. package/src/components/activity-indicator/index.ios.ts +4 -0
  142. package/src/components/activity-indicator/shared.ts +32 -13
  143. package/src/components/button.ts +46 -19
  144. package/src/components/flat-list/index.ts +122 -62
  145. package/src/components/image/index.android.ts +58 -7
  146. package/src/components/image/index.ios.ts +58 -7
  147. package/src/components/image/shared.ts +92 -21
  148. package/src/components/image-background.ts +29 -9
  149. package/src/components/input-accessory-view/index.ts +67 -12
  150. package/src/components/keyboard-avoiding-view/index.ts +61 -15
  151. package/src/components/modal/index.ts +72 -33
  152. package/src/components/pressable/index.ts +117 -28
  153. package/src/components/refresh-control/index.ts +74 -18
  154. package/src/components/safe-area-view/index.ts +64 -10
  155. package/src/components/scroll-view/index.android.ts +62 -47
  156. package/src/components/scroll-view/index.ios.ts +19 -17
  157. package/src/components/scroll-view/projection.ts +363 -64
  158. package/src/components/scroll-view/shared.ts +312 -90
  159. package/src/components/scroll-view/sticky-header.ts +36 -24
  160. package/src/components/section-list/index.ts +86 -53
  161. package/src/components/switch/index.android.ts +60 -8
  162. package/src/components/switch/index.ios.ts +60 -8
  163. package/src/components/switch/shared.ts +93 -6
  164. package/src/components/text-input.ts +95 -13
  165. package/src/components/touchable/index.ts +348 -64
  166. package/src/components/touchable-native-feedback/index.ts +94 -31
  167. package/src/components/virtualized-list/directives.ts +19 -5
  168. package/src/components/virtualized-list/index.ts +380 -85
  169. package/src/components/virtualized-section-list/directives.ts +6 -3
  170. package/src/components/virtualized-section-list/index.ts +146 -33
  171. package/src/components.ts +9 -1
  172. package/src/create-portal/index.ts +31 -31
  173. package/src/create-tunnel/index.ts +16 -17
  174. package/src/descriptor-to-angular/index.ts +29 -8
  175. package/src/diagnostics.ts +138 -0
  176. package/src/host-instance/index.ts +9 -3
  177. package/src/index.ts +61 -3
  178. package/src/modules/animated/animated-leaf-binder.ts +28 -77
  179. package/src/modules/animated/create-animated-component.ts +115 -47
  180. package/src/modules/app-registry/index.ts +9 -9
  181. package/src/modules/status-bar/index.ts +13 -4
  182. package/src/primitives/index.ts +47 -2
  183. package/src/primitives/shared.ts +170 -10
  184. package/src/render/index.ts +77 -39
  185. package/src/renderer/index.ts +76 -14
  186. package/src/services/color-scheme.service.ts +3 -1
  187. package/src/services/window-dimensions.service.ts +11 -4
  188. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
  189. package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
  190. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
  191. package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
@@ -1,22 +1,18 @@
1
- // createAnimatedComponent for @symbiote-native/angular: the Angular twin of the React/Vue
2
- // wrapper. React/Vue have a runtime HOC (`createAnimatedComponent(Component)` builds a
3
- // fresh wrapped component on the fly). Angular has NO such idiom AND it cannot have one:
4
- // the AOT-under-Metro build compiles every @Component at build
5
- // time and ships NO JIT compiler to Hermes, so a runtime `Component({...})(class)` would
6
- // throw. The idiomatic, AOT-safe equivalent is therefore an EXPLICIT set of standalone
7
- // components — AnimatedView / AnimatedText / AnimatedImage / AnimatedScrollView — each a
8
- // thin @Component over the matching host primitive, all sharing one abstract lifecycle
9
- // base. The mechanism (build an AnimatedProps leaf, reduce animated props to current
10
- // values for the first paint, bind the leaf to the committed host node, swap leaves on
11
- // re-render, tear down on destroy) lives once in AnimatedComponentBase; only the per-host
12
- // template differs.
1
+ // createAnimatedComponent for @symbiote-native/angular. React/Vue have a runtime HOC
2
+ // (`createAnimatedComponent(Component)` builds a fresh wrapped component on the fly); Angular
3
+ // can't — the AOT-under-Metro build compiles every @Component ahead of time and ships no JIT
4
+ // compiler to Hermes, so a runtime `Component({...})(class)` would throw. The AOT-safe
5
+ // equivalent is an EXPLICIT set of standalone components — AnimatedView / AnimatedText /
6
+ // AnimatedImage / AnimatedScrollView — each a thin @Component over the matching host primitive,
7
+ // sharing one abstract lifecycle base. The mechanism (build an AnimatedProps leaf, reduce
8
+ // animated props to current values for the first paint, bind the leaf to the committed host
9
+ // node, swap leaves on re-render, tear down on destroy) lives once in AnimatedComponentBase.
13
10
  //
14
11
  // The framework-agnostic half (reduceProps / readPassthroughStyle / resolveHostNode /
15
12
  // AnimatedProps / attachNativeEventHandler / isNativeAnimatedAvailable) comes from
16
- // @symbiote-native/engine, shared verbatim with React and Vue. Angular supplies
17
- // only the lifecycle wiring. The per-frame path NEVER goes through Angular change
18
- // detection: value.setValue / animation -> flushValue -> AnimatedProps.update() ->
19
- // setNativeProps(node), exactly as on the other adapters.
13
+ // @symbiote-native/engine, shared with React and Vue. The per-frame path NEVER goes through
14
+ // Angular change detection: value.setValue / animation -> flushValue -> AnimatedProps.update()
15
+ // -> setNativeProps(node), same as the other adapters.
20
16
 
21
17
  import {
22
18
  ChangeDetectionStrategy,
@@ -25,8 +21,10 @@ import {
25
21
  Directive,
26
22
  ElementRef,
27
23
  inject,
24
+ signal,
28
25
  ViewChild,
29
26
  type AfterViewInit,
27
+ type DoCheck,
30
28
  type OnChanges,
31
29
  type OnDestroy,
32
30
  type Type,
@@ -43,11 +41,12 @@ import { selectScrollIntrinsics } from '@symbiote-native/components';
43
41
  import { Image, ScrollView, Text, View } from '../../components';
44
42
  import {
45
43
  anchorHostStyle,
46
- ViewHost,
47
- TextHost,
48
44
  ImageHost,
49
45
  ScrollViewHost,
50
46
  SymbioteHostPropsDirective,
47
+ SymbioteStyleInputDirective,
48
+ TextHost,
49
+ ViewHost,
51
50
  } from '../../primitives';
52
51
  import { FlatList } from '../../components/flat-list';
53
52
  import {
@@ -67,11 +66,14 @@ const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
67
66
  // The inputs every animated wrapper accepts. `style` is the (possibly animated) style — the
68
67
  // common path (TouchableOpacity passes `[style, { opacity }]`). `animatedProps` is the generic
69
68
  // escape hatch for any OTHER prop that may hold an AnimatedNode or an Animated.event handler,
70
- // plus static host props to forward (the React/Vue `rest`). The passthrough drives native.
71
- // Each concrete @Component re-lists these (Angular's inputs-on-base convention, mirroring
72
- // Switch/Image), so they are inherited consistently.
69
+ // plus static host props to forward (the React/Vue `rest`). Each concrete @Component re-lists
70
+ // these (Angular's inputs-on-base convention, mirroring Switch/Image).
73
71
  export const ANIMATED_INPUTS = ['style', 'animatedProps', PASSTHROUGH_PROP];
74
- export const ANIMATED_IMAGE_INPUTS = [...IMAGE_INPUTS, 'animatedProps', PASSTHROUGH_PROP];
72
+ export const ANIMATED_IMAGE_INPUTS = [
73
+ ...IMAGE_INPUTS,
74
+ 'animatedProps',
75
+ PASSTHROUGH_PROP,
76
+ ];
75
77
 
76
78
  // The shared lifecycle for every animated wrapper. Concrete subclasses add ONLY a @Component
77
79
  // decorator (selector + the host-primitive template) — no behavior. Plain fields (no @Input
@@ -79,7 +81,9 @@ export const ANIMATED_IMAGE_INPUTS = [...IMAGE_INPUTS, 'animatedProps', PASSTHRO
79
81
  // @Directive() (no selector) is the Angular-sanctioned decorator for an abstract base that
80
82
  // declares lifecycle hooks — without it ngtsc rejects the inherited hooks (NG2007).
81
83
  @Directive()
82
- export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges, OnDestroy {
84
+ export abstract class AnimatedComponentBase
85
+ implements AfterViewInit, DoCheck, OnChanges, OnDestroy
86
+ {
83
87
  style: unknown;
84
88
  animatedProps: Record<string, unknown> | undefined;
85
89
  passthroughAnimatedPropExplicitValues: unknown;
@@ -92,7 +96,8 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
92
96
 
93
97
  // The directive sitting on the inner host primitive; its `node` is the committed SymbioteNode
94
98
  // the leaf binds to. Inherited by the decorated subclass (Angular collects base-class queries).
95
- @ViewChild(SymbioteHostPropsDirective) private hostDirective?: SymbioteHostPropsDirective;
99
+ @ViewChild(SymbioteHostPropsDirective)
100
+ private hostDirective?: SymbioteHostPropsDirective;
96
101
 
97
102
  // The leaf-lifecycle orchestration (build/attach/swap/bind/detach an AnimatedProps leaf) is a
98
103
  // Pure Fabrication shared with AnimatedImage, which cannot extend this class (it must extend
@@ -110,15 +115,38 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
110
115
  // array, which the commit layer flattens) so the ShadowTree carries the current transform.
111
116
  get reducedProps(): Record<string, unknown> {
112
117
  const reduced = reduceProps(this.mergedProps());
113
- const passthroughStyle = readPassthroughStyle(this.passthroughAnimatedPropExplicitValues);
118
+ const passthroughStyle = readPassthroughStyle(
119
+ this.passthroughAnimatedPropExplicitValues,
120
+ );
114
121
  if (passthroughStyle !== undefined) {
115
122
  reduced['style'] =
116
- reduced['style'] === undefined ? passthroughStyle : [reduced['style'], passthroughStyle];
123
+ reduced['style'] === undefined
124
+ ? passthroughStyle
125
+ : [reduced['style'], passthroughStyle];
117
126
  }
118
- reduced['style'] = [anchorHostStyle(this.elementRef), reduced['style']];
127
+ reduced['style'] = [this.anchorStyle(), reduced['style']];
119
128
  return reduced;
120
129
  }
121
130
 
131
+ // The anchor's class-derived style, polled rather than read live inside `reducedProps`.
132
+ //
133
+ // Two different writers land style on this component's anchor and NEITHER goes through
134
+ // ngOnChanges: `class=` / `[class.x]` / `[ngClass]` at the use site (via the renderer's
135
+ // addClass/removeClass -> commitClassStyle), and a `[style]` binding from a composing parent
136
+ // that Angular writes as a host property because `style` is not declared as an @Input here.
137
+ // Reading them live in the getter worked at CREATION only - nothing dirties THIS view when the
138
+ // anchor changes, so the getter was never re-read and the update was lost. Device-visible as a
139
+ // TouchableOpacity whose `class` toggled after mount never repainted.
140
+ //
141
+ // ngDoCheck runs during the PARENT's refresh even when this view is skipped, and the signal
142
+ // write is what then marks this view for refresh. `signal.set`'s own Object.is makes an
143
+ // unchanged poll a no-op, so a steady screen costs one property read per pass and nothing else.
144
+ private readonly anchorStyle = signal<unknown>(undefined);
145
+
146
+ ngDoCheck(): void {
147
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
148
+ }
149
+
122
150
  ngAfterViewInit(): void {
123
151
  this.viewReady = true;
124
152
  this.reconcile();
@@ -143,7 +171,8 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
143
171
  const hasPassthroughAnimatedValues =
144
172
  this.passthroughAnimatedPropExplicitValues !== null &&
145
173
  this.passthroughAnimatedPropExplicitValues !== undefined;
146
- const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
174
+ const wantsNative =
175
+ hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
147
176
  this.leafBinder.reconcile(props, wantsNative);
148
177
  }
149
178
 
@@ -152,7 +181,9 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
152
181
  // never disagree.
153
182
  private mergedProps(): Record<string, unknown> {
154
183
  const base = this.animatedProps ?? {};
155
- return this.style === undefined ? { ...base } : { ...base, style: this.style };
184
+ return this.style === undefined
185
+ ? { ...base }
186
+ : { ...base, style: this.style };
156
187
  }
157
188
 
158
189
  // The committed host node held by IDENTITY. resolveHostNode unwraps an imperative scroll
@@ -167,6 +198,9 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
167
198
  @Component({
168
199
  selector: 'AnimatedView, symbiote-animated-view',
169
200
  standalone: true,
201
+ hostDirectives: [
202
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
203
+ ],
170
204
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
171
205
  imports: [SymbioteHostPropsDirective, ViewHost],
172
206
  inputs: ANIMATED_INPUTS,
@@ -182,6 +216,9 @@ export class AnimatedView extends AnimatedComponentBase {}
182
216
  @Component({
183
217
  selector: 'AnimatedText, symbiote-animated-text',
184
218
  standalone: true,
219
+ hostDirectives: [
220
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
221
+ ],
185
222
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
186
223
  imports: [SymbioteHostPropsDirective, TextHost],
187
224
  inputs: ANIMATED_INPUTS,
@@ -197,6 +234,9 @@ export class AnimatedText extends AnimatedComponentBase {}
197
234
  @Component({
198
235
  selector: 'AnimatedImage, symbiote-animated-image',
199
236
  standalone: true,
237
+ hostDirectives: [
238
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
239
+ ],
200
240
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
201
241
  imports: [SymbioteHostPropsDirective, ImageHost],
202
242
  inputs: ANIMATED_IMAGE_INPUTS,
@@ -220,7 +260,10 @@ export class AnimatedText extends AnimatedComponentBase {}
220
260
  `,
221
261
  changeDetection: ChangeDetectionStrategy.OnPush,
222
262
  })
223
- export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges, OnDestroy {
263
+ export class AnimatedImage
264
+ extends ImageBase
265
+ implements AfterViewInit, OnChanges, OnDestroy
266
+ {
224
267
  animatedProps: Record<string, unknown> | undefined;
225
268
  passthroughAnimatedPropExplicitValues: unknown;
226
269
 
@@ -228,7 +271,8 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
228
271
  // comment on AnimatedComponentBase.elementRef.
229
272
  private readonly elementRef = inject(ElementRef);
230
273
 
231
- @ViewChild(SymbioteHostPropsDirective) private hostDirective?: SymbioteHostPropsDirective;
274
+ @ViewChild(SymbioteHostPropsDirective)
275
+ private hostDirective?: SymbioteHostPropsDirective;
232
276
 
233
277
  // Same Pure Fabrication AnimatedComponentBase holds — see its matching comment. AnimatedImage
234
278
  // can't extend AnimatedComponentBase (it must extend ImageBase, see the file header), so it
@@ -241,16 +285,39 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
241
285
 
242
286
  get animatedImageProps(): Record<string, unknown> {
243
287
  const reduced = reduceProps(this.mergedProps());
244
- const passthroughStyle = readPassthroughStyle(this.passthroughAnimatedPropExplicitValues);
288
+ const passthroughStyle = readPassthroughStyle(
289
+ this.passthroughAnimatedPropExplicitValues,
290
+ );
245
291
  if (passthroughStyle !== undefined) {
246
292
  reduced['style'] =
247
- reduced['style'] === undefined ? passthroughStyle : [reduced['style'], passthroughStyle];
293
+ reduced['style'] === undefined
294
+ ? passthroughStyle
295
+ : [reduced['style'], passthroughStyle];
248
296
  }
249
297
  const resolved = resolveImageProps(reduced);
250
298
  resolved['style'] = [anchorHostStyle(this.elementRef), resolved['style']];
251
299
  return resolved;
252
300
  }
253
301
 
302
+ // The anchor's class-derived style, polled rather than read live inside `reducedProps`.
303
+ //
304
+ // Two different writers land style on this component's anchor and NEITHER goes through
305
+ // ngOnChanges: `class=` / `[class.x]` / `[ngClass]` at the use site (via the renderer's
306
+ // addClass/removeClass -> commitClassStyle), and a `[style]` binding from a composing parent
307
+ // that Angular writes as a host property because `style` is not declared as an @Input here.
308
+ // Reading them live in the getter worked at CREATION only - nothing dirties THIS view when the
309
+ // anchor changes, so the getter was never re-read and the update was lost. Device-visible as a
310
+ // TouchableOpacity whose `class` toggled after mount never repainted.
311
+ //
312
+ // ngDoCheck runs during the PARENT's refresh even when this view is skipped, and the signal
313
+ // write is what then marks this view for refresh. `signal.set`'s own Object.is makes an
314
+ // unchanged poll a no-op, so a steady screen costs one property read per pass and nothing else.
315
+ private readonly anchorStyle = signal<unknown>(undefined);
316
+
317
+ ngDoCheck(): void {
318
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
319
+ }
320
+
254
321
  ngAfterViewInit(): void {
255
322
  this.viewReady = true;
256
323
  this.reconcile();
@@ -270,7 +337,8 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
270
337
  const hasPassthroughAnimatedValues =
271
338
  this.passthroughAnimatedPropExplicitValues !== null &&
272
339
  this.passthroughAnimatedPropExplicitValues !== undefined;
273
- const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
340
+ const wantsNative =
341
+ hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
274
342
  this.leafBinder.reconcile(props, wantsNative);
275
343
  }
276
344
 
@@ -291,6 +359,9 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
291
359
  @Component({
292
360
  selector: 'AnimatedScrollView, symbiote-animated-scroll-view',
293
361
  standalone: true,
362
+ hostDirectives: [
363
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
364
+ ],
294
365
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
295
366
  imports: [SymbioteHostPropsDirective, ScrollViewHost],
296
367
  inputs: ANIMATED_INPUTS,
@@ -305,20 +376,17 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
305
376
  })
306
377
  export class AnimatedScrollView extends AnimatedComponentBase {
307
378
  // RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`) — Android
308
- // needs it explicit or a ScrollView nested inside another scrollable container never
309
- // receives touch (swallowed by the outer scroll view; iOS doesn't gate on this). The real
310
- // ScrollView component already defaults this (scroll-view/shared.ts), but AnimatedScrollView
311
- // talks to the raw primitive directly and has no such defaulting, so it must apply it too.
379
+ // needs it explicit or a nested ScrollView never receives touch (swallowed by the outer one;
380
+ // iOS doesn't gate on this). The real ScrollView component already defaults this
381
+ // (scroll-view/shared.ts), but AnimatedScrollView talks to the raw primitive directly and
382
+ // must apply it too.
312
383
  //
313
- // scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the OTHER thing
314
- // the real ScrollView applies that this bespoke template used to skip entirely — without it,
315
- // iOS Fabric never clips the scroll view's content to its own frame (Android's native
316
- // ViewGroup clips regardless of the style prop, which is why this was invisible there). This
317
- // wrapper has no dedicated `horizontal` input (only the generic `animatedProps`/`style`
318
- // surface), and its template is hardcoded to the vertical intrinsics
319
- // (symbiote-scroll-view/symbiote-scroll-content, not the horizontal variants), so
320
- // selectScrollIntrinsics is always called with isHorizontal=false here — matching what the
321
- // template can actually render.
384
+ // scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the other thing the
385
+ // real ScrollView applies that this bespoke template used to skip — without it, iOS Fabric
386
+ // never clips content to the scroll view's frame (invisible on Android, whose native ViewGroup
387
+ // clips regardless of style). This wrapper has no `horizontal` input and its template is
388
+ // hardcoded to the vertical intrinsics, so selectScrollIntrinsics is always called with
389
+ // isHorizontal=false here, matching what the template can actually render.
322
390
  override get reducedProps(): Record<string, unknown> {
323
391
  const reduced = super.reducedProps;
324
392
  const { scrollViewBaseStyle } = selectScrollIntrinsics(false, undefined);
@@ -15,17 +15,17 @@ import {
15
15
  } from '@symbiote-native/engine';
16
16
  import { mount } from '../../render';
17
17
 
18
- // RN's IComponentProvider: a thunk returning the root component (lazy so the module
19
- // graph stays cheap until the app actually runs).
18
+ // RN's IComponentProvider. Lazy so the module graph stays cheap until the app actually runs.
20
19
  export type IComponentProvider = () => Type<unknown>;
21
20
 
22
- // RN's IWrapperComponentProvider: given the surface's parameters, returns a
23
- // component to wrap the app root in (e.g. a context provider). Optional. Angular has no
24
- // runtime template synthesis (no JIT under AOT/Metro — see create-animated-component.ts),
25
- // so the wrapper must be a pre-authored standalone @Component whose template renders
26
- // `<ng-content>` — the Angular idiom for "render my children" (mount() projects the root's
27
- // host node into it, the twin of React's createElement(Wrapper, null, rootElement)).
28
- export type IWrapperComponentProvider = (appParameters: IAppParameters) => Type<unknown>;
21
+ // RN's IWrapperComponentProvider (e.g. a context provider). Angular has no runtime template
22
+ // synthesis (no JIT under AOT/Metro — see create-animated-component.ts), so the wrapper must be
23
+ // a pre-authored standalone @Component whose template renders `<ng-content>` — the Angular idiom
24
+ // for "render my children" (mount() projects the root's host node into it, the twin of React's
25
+ // createElement(Wrapper, null, rootElement)).
26
+ export type IWrapperComponentProvider = (
27
+ appParameters: IAppParameters,
28
+ ) => Type<unknown>;
29
29
 
30
30
  function runnableFor(
31
31
  componentProvider: IComponentProvider,
@@ -5,7 +5,12 @@
5
5
  // ngOnChanges on mount + every prop change. Imperative statics are attached to the component
6
6
  // object the same way RN does.
7
7
 
8
- import { Component, Input, type OnChanges, type SimpleChanges } from '@angular/core';
8
+ import {
9
+ Component,
10
+ Input,
11
+ type OnChanges,
12
+ type SimpleChanges,
13
+ } from '@angular/core';
9
14
  import {
10
15
  applyStatusBarProps,
11
16
  statusBarImperative,
@@ -46,12 +51,16 @@ class StatusBarComponent implements OnChanges {
46
51
  }
47
52
  }
48
53
 
49
- const StatusBarWithStatics = Object.assign(StatusBarComponent, statusBarImperative);
54
+ const StatusBarWithStatics = Object.assign(
55
+ StatusBarComponent,
56
+ statusBarImperative,
57
+ );
50
58
 
51
59
  Object.defineProperty(StatusBarWithStatics, 'currentHeight', {
52
60
  get: statusBarCurrentHeight,
53
61
  enumerable: true,
54
62
  });
55
63
 
56
- export const StatusBar: typeof StatusBarWithStatics & { readonly currentHeight?: number } =
57
- StatusBarWithStatics;
64
+ export const StatusBar: typeof StatusBarWithStatics & {
65
+ readonly currentHeight?: number;
66
+ } = StatusBarWithStatics;
@@ -3,7 +3,16 @@
3
3
  // component imports them and renders them in its template. Declaring `style` as a real
4
4
  // Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp` arrays.
5
5
 
6
- import { Component } from '@angular/core';
6
+ import {
7
+ Component,
8
+ Input,
9
+ type OnInit,
10
+ type SimpleChanges,
11
+ } from '@angular/core';
12
+ import {
13
+ resolveTextProps,
14
+ type IEllipsizeMode,
15
+ } from '@symbiote-native/components';
7
16
  import { SymbiotePrimitiveHost } from './shared';
8
17
 
9
18
  export {
@@ -11,6 +20,7 @@ export {
11
20
  anchorStyleProp,
12
21
  stableAnchorStyle,
13
22
  SymbioteHostPropsDirective,
23
+ SymbioteStyleInputDirective,
14
24
  } from './shared';
15
25
 
16
26
  @Component({
@@ -20,12 +30,47 @@ export {
20
30
  })
21
31
  export class ViewHost extends SymbiotePrimitiveHost {}
22
32
 
33
+ /**
34
+ * Text carries the two defaults RN's Text.js applies unconditionally (`ellipsizeMode ?? 'tail'`,
35
+ * `allowFontScaling !== false`, Text.js:288-291). Without them native falls back to `clip` and a
36
+ * clamped Text cuts mid-word with no ellipsis — device-observed on the other adapters 2026-08-19.
37
+ *
38
+ * They are declared as real `@Input()`s, unlike the pass-through props the base's comment
39
+ * describes, for one reason: the default can only be applied by code that can SEE whether the
40
+ * caller supplied a value. A host-binding pass-through is invisible here, so blindly writing
41
+ * 'tail' would silently overwrite an explicit `ellipsizeMode="clip"`.
42
+ */
23
43
  @Component({
24
44
  selector: 'symbiote-text, Text',
25
45
  standalone: true,
26
46
  template: '<ng-content></ng-content>',
27
47
  })
28
- export class TextHost extends SymbiotePrimitiveHost {}
48
+ export class TextHost extends SymbiotePrimitiveHost implements OnInit {
49
+ @Input() ellipsizeMode?: IEllipsizeMode;
50
+ @Input() allowFontScaling?: boolean;
51
+
52
+ // ngOnChanges fires before ngOnInit when a binding exists, and not at all when none does —
53
+ // so both hooks are needed to guarantee the defaults land exactly once per settled value.
54
+ ngOnInit(): void {
55
+ this.applyTextDefaults();
56
+ }
57
+
58
+ override ngOnChanges(changes: SimpleChanges): void {
59
+ super.ngOnChanges(changes);
60
+ if ('ellipsizeMode' in changes || 'allowFontScaling' in changes) {
61
+ this.applyTextDefaults();
62
+ }
63
+ }
64
+
65
+ private applyTextDefaults(): void {
66
+ const resolved = resolveTextProps({
67
+ ellipsizeMode: this.ellipsizeMode,
68
+ allowFontScaling: this.allowFontScaling,
69
+ });
70
+ this.setHostProp('ellipsizeMode', resolved.ellipsizeMode);
71
+ this.setHostProp('allowFontScaling', resolved.allowFontScaling);
72
+ }
73
+ }
29
74
 
30
75
  @Component({
31
76
  selector: 'symbiote-image',