@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,20 +1,15 @@
1
- // Sticky headers: the Angular twin of adapters/{react,vue}/src/components/scroll-view/
2
- // sticky-header.{tsx,ts}, the JS layer RN implements in ScrollView.js / ScrollViewStickyHeader.js.
3
- //
4
- // Source-based: RN does stickiness PURELY IN JS. A single `_scrollAnimatedValue` an Animated.event
5
- // drives from `onScroll` feeds each flagged header's translateY through an interpolation that pins
6
- // it to the top (or bottom, inverted) until the next header collides with it. The native Fabric
7
- // scroll view does NOT honor `stickyHeaderIndices` on its own. The load-bearing top/inverted
8
- // interpolation math (computeStickyInterpolation) lives framework-agnostic in @symbiote-native/components
9
- // this file holds the Angular component shell, the layout state, and the interpolation
10
- // build, sharing the math verbatim with React/Vue. Angular supplies only the lifecycle (inputs +
11
- // manual change detection instead of useState/useEffect or refs/watch).
1
+ // Sticky headers: RN does stickiness PURELY IN JS (ScrollView.js / ScrollViewStickyHeader.js).
2
+ // A single `_scrollAnimatedValue` an Animated.event drives from `onScroll` feeds each flagged
3
+ // header's translateY through an interpolation that pins it to the top (or bottom, inverted)
4
+ // until the next header collides with it the native Fabric scroll view does NOT honor
5
+ // `stickyHeaderIndices` on its own. The interpolation math (computeStickyInterpolation) lives
6
+ // framework-agnostic in @symbiote-native/components, shared verbatim with React/Vue; this file
7
+ // holds the Angular component shell, layout state, and interpolation build.
12
8
  //
13
9
  // This is the public sticky-header WRAPPER for explicit composition. ScrollView also auto-wraps
14
- // projected direct children named by stickyHeaderIndices through its projection bridge (see
15
- // projection.ts), because Angular templates cannot map <ng-content> children directly; that auto
16
- // path intentionally uses this built-in wrapper rather than dynamically instantiating arbitrary
17
- // custom component classes.
10
+ // projected children named by stickyHeaderIndices through its projection bridge (projection.ts),
11
+ // because Angular templates cannot map <ng-content> children directly; that auto path
12
+ // intentionally reuses this built-in wrapper rather than instantiating arbitrary component types.
18
13
 
19
14
  import {
20
15
  ChangeDetectionStrategy,
@@ -101,7 +96,9 @@ export class ScrollViewStickyHeader
101
96
  // The animated node driving the transform (RN's animatedTranslateY), replaced by the
102
97
  // rebuild-interpolation effect. When the scroll value is native, this interpolation runs on the UI
103
98
  // thread: the smooth pin.
104
- private animatedTranslateY: AnimatedInterpolation = new AnimatedValue(0).interpolate({
99
+ private animatedTranslateY: AnimatedInterpolation = new AnimatedValue(
100
+ 0,
101
+ ).interpolate({
105
102
  inputRange: [-1, 0],
106
103
  outputRange: [0, 0],
107
104
  });
@@ -140,13 +137,17 @@ export class ScrollViewStickyHeader
140
137
  // anchor style first so the fixed transform/zIndex below still wins on conflict.
141
138
  return [
142
139
  anchorHostStyle(this.elementRef),
143
- { transform: [{ translateY: this.animatedTranslateY }], zIndex: STICKY_HEADER_Z_INDEX },
140
+ {
141
+ transform: [{ translateY: this.animatedTranslateY }],
142
+ zIndex: STICKY_HEADER_Z_INDEX,
143
+ },
144
144
  ];
145
145
  }
146
146
 
147
147
  // Stable reference (bound once) so AnimatedView's directive does not re-wire the layout listener
148
148
  // every change-detection pass.
149
- private readonly onHostLayout = (event: ISymbioteEvent): void => this.handleLayout(event);
149
+ private readonly onHostLayout = (event: ISymbioteEvent): void =>
150
+ this.handleLayout(event);
150
151
 
151
152
  // Also a stable reference, not a getter: jsonEqual (commit.ts) can't structurally compare the
152
153
  // embedded onLayout function, so a fresh object here would read as "props changed" on every
@@ -161,7 +162,9 @@ export class ScrollViewStickyHeader
161
162
 
162
163
  // The EXPLICIT debounced translateY overrides the committed transform for hit-testing while
163
164
  // animatedTranslateY does the smooth (native-driven) pin (RN ScrollViewStickyHeader.js).
164
- get passthrough(): { style: { transform: Array<{ translateY: number }> } } | null {
165
+ get passthrough(): {
166
+ style: { transform: Array<{ translateY: number }> };
167
+ } | null {
165
168
  return this.state.translateY !== null
166
169
  ? { style: { transform: [{ translateY: this.state.translateY }] } }
167
170
  : null;
@@ -186,7 +189,10 @@ export class ScrollViewStickyHeader
186
189
  case 'rebuild-interpolation': {
187
190
  // Detach the old listener, build a fresh interpolation onto the shared scroll value, and
188
191
  // wire the settled-value listener (symbiote is always Fabric; RN attaches it only there).
189
- if (this.interpolation !== undefined && this.listenerId !== undefined) {
192
+ if (
193
+ this.interpolation !== undefined &&
194
+ this.listenerId !== undefined
195
+ ) {
190
196
  this.interpolation.removeListener(this.listenerId);
191
197
  this.listenerId = undefined;
192
198
  }
@@ -198,14 +204,15 @@ export class ScrollViewStickyHeader
198
204
  this.interpolation = next;
199
205
  this.animatedTranslateY = next;
200
206
  dlog(
201
- `Angular ScrollViewStickyHeader interpolation measured=${this.state.measured} y=${this.state.layoutY}`,
207
+ `STICKY[header] interpolation measured=${this.state.measured} y=${this.state.layoutY}`,
202
208
  );
203
209
  break;
204
210
  }
205
211
  case 'schedule-debounce':
206
212
  // The animated value updates several times per frame; debounce the settled value into the
207
213
  // committed transform so hit detection stays current (a Fabric issue, worse on Android).
208
- if (this.debounceTimer !== undefined) clearTimeout(this.debounceTimer);
214
+ if (this.debounceTimer !== undefined)
215
+ clearTimeout(this.debounceTimer);
209
216
  this.debounceTimer = setTimeout(() => {
210
217
  this.debounceTimer = undefined;
211
218
  this.dispatch({ kind: 'debounce-fired', value: effect.value });
@@ -238,8 +245,13 @@ export class ScrollViewStickyHeader
238
245
  this.changeDetector.markForCheck();
239
246
  }
240
247
 
241
- private readonly animatedValueListener = ({ value }: { value: number | string }): void => {
242
- if (typeof value === 'number') this.dispatch({ kind: 'animated-tick', value });
248
+ private readonly animatedValueListener = ({
249
+ value,
250
+ }: {
251
+ value: number | string;
252
+ }): void => {
253
+ if (typeof value === 'number')
254
+ this.dispatch({ kind: 'animated-tick', value });
243
255
  };
244
256
 
245
257
  private teardown(): void {
@@ -1,33 +1,27 @@
1
1
  // SectionList, the Angular public list-of-sections component. A thin convenience surface over
2
2
  // VirtualizedSectionList, mirroring RN's layering (SectionList -> VirtualizedSectionList ->
3
3
  // VirtualizedList). All section-flattening / windowing / sticky-header / imperative-scroll logic
4
- // lives below; this layer re-exposes the same surface under the SectionList name and re-exposes the
5
- // handle (delegating to the inner VirtualizedSectionList). The Angular twin of
6
- // adapters/vue/src/components/section-list/index.ts.
4
+ // lives below; this layer re-exposes the same surface under the SectionList name and delegates
5
+ // the handle to the inner VirtualizedSectionList.
7
6
  //
8
- // As in the Vue twin, SectionList is a PURE FORWARDER: its public prop surface IS
9
- // VirtualizedSectionList's (RN layers them one-for-one), and the SectionList-flavour defaults
10
- // (stickySectionHeadersEnabled per Platform.OS, the section entry keyExtractor) already live INSIDE
11
- // VirtualizedSectionList it applies `stickySectionHeadersEnabled ?? (Platform.OS === 'ios')` in
12
- // its ngDoCheck and routes keyExtractor through the shared sectionEntryKey. So SectionList forwards
13
- // each prop unchanged (undefined included) and the default lands one layer down; double-applying
14
- // here would fight VSL. Vue spreads $attrs to do this in one line — Angular has no attrs-spread, so
15
- // every prop is forwarded as an explicit @Input binding, except the seven list-lifecycle events
7
+ // SectionList is a PURE FORWARDER: its public prop surface IS VirtualizedSectionList's (RN layers
8
+ // them one-for-one), and the SectionList-flavour defaults (stickySectionHeadersEnabled per
9
+ // Platform.OS, the section entry keyExtractor) already live INSIDE VirtualizedSectionList — double-
10
+ // applying them here would fight it. Angular has no attrs-spread (unlike Vue's $attrs), so every
11
+ // prop is forwarded as an explicit @Input binding, except the seven list-lifecycle events
16
12
  // (endReached/startReached/refresh/accessibilityAction/accessibilityTap/magicTap/
17
13
  // accessibilityEscape), which are real @Output() EventEmitters re-emitted via a listener binding.
18
14
  //
19
- // TEMPLATE FORWARDING — RE-STAMP, the same pattern FlatList's single-column branch was fixed to use
20
- // (see flat-list/index.ts). A bare `<ng-content></ng-content>` passthrough does NOT let
21
- // VirtualizedSectionList's own @ContentChild resolve directives across the SECOND projection hop
22
- // (SectionList's own `<ng-content>` re-projecting content that was actually authored on `<SectionList>`
23
- // by the app, one level further out) — Angular's content queries resolve against what was projected
24
- // directly onto the querying component's OWN tag, not transitively through a nested `<ng-content>`
25
- // relay. So SectionList captures the app's `<ng-template vSectionItem>` / vSectionHeader /
26
- // vSectionFooter / vSectionSeparator / vListHeader / vListFooter / vListEmpty / vListSeparator with
27
- // its OWN @ContentChild (a single, direct projection hop this always resolves) and re-authors
28
- // equivalent `<ng-template>`s on `<VirtualizedSectionList>`, each forwarding the captured
29
- // templateRef + context through VListOutletDirective, exactly mirroring how VirtualizedSectionList
30
- // itself re-stamps its own captured directives onto its inner VirtualizedList.
15
+ // TEMPLATE FORWARDING — RE-STAMP, the same pattern FlatList's single-column branch uses (see
16
+ // flat-list/index.ts). A bare `<ng-content></ng-content>` passthrough does NOT let
17
+ // VirtualizedSectionList's own @ContentChild resolve directives across a SECOND projection hop
18
+ // Angular's content queries resolve only against what was projected directly onto the querying
19
+ // component's OWN tag. So SectionList captures the app's `<ng-template vSectionItem>` /
20
+ // vSectionHeader / vSectionFooter / vSectionSeparator / vListHeader / vListFooter / vListEmpty /
21
+ // vListSeparator with its OWN @ContentChild (a single, direct hop always resolves) and
22
+ // re-authors equivalent `<ng-template>`s on `<VirtualizedSectionList>`, forwarding the captured
23
+ // templateRef + context through VListOutletDirectivemirroring how VirtualizedSectionList itself
24
+ // re-stamps its own captured directives onto its inner VirtualizedList.
31
25
 
32
26
  import {
33
27
  ChangeDetectionStrategy,
@@ -62,7 +56,10 @@ import {
62
56
  type IVListSeparatorContext,
63
57
  } from '../virtualized-list';
64
58
  import { VListOutletDirective } from '../virtualized-list/directives';
65
- import { stableAnchorStyle } from '../../primitives';
59
+ import {
60
+ stableAnchorStyle,
61
+ SymbioteStyleInputDirective,
62
+ } from '../../primitives';
66
63
  import {
67
64
  VSectionFooterDirective,
68
65
  VSectionHeaderDirective,
@@ -126,6 +123,9 @@ export type ISectionListInputs<ItemT> = Omit<
126
123
  @Component({
127
124
  selector: 'SectionList',
128
125
  standalone: true,
126
+ hostDirectives: [
127
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
128
+ ],
129
129
  imports: [
130
130
  VirtualizedSectionList,
131
131
  VSectionItemDirective,
@@ -143,6 +143,7 @@ export type ISectionListInputs<ItemT> = Omit<
143
143
  <VirtualizedSectionList
144
144
  [sections]="sections"
145
145
  [keyExtractor]="keyExtractor"
146
+ [getItemLayout]="getItemLayout"
146
147
  [stickySectionHeadersEnabled]="stickySectionHeadersEnabled"
147
148
  [extraData]="extraData"
148
149
  (endReached)="endReached.emit($event)"
@@ -187,8 +188,12 @@ export type ISectionListInputs<ItemT> = Omit<
187
188
  [accessibilityElementsHidden]="accessibilityElementsHidden"
188
189
  [accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
189
190
  [accessibilityLanguage]="accessibilityLanguage"
190
- [accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"
191
- [accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"
191
+ [accessibilityRespondsToUserInteraction]="
192
+ accessibilityRespondsToUserInteraction
193
+ "
194
+ [accessibilityShowsLargeContentViewer]="
195
+ accessibilityShowsLargeContentViewer
196
+ "
192
197
  [accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
193
198
  (accessibilityAction)="accessibilityAction.emit($event)"
194
199
  (accessibilityTap)="accessibilityTap.emit($event)"
@@ -219,7 +224,13 @@ export type ISectionListInputs<ItemT> = Omit<
219
224
  >
220
225
  <ng-container
221
226
  [vListOutlet]="sectionItemDir?.templateRef"
222
- [vListOutletContext]="{ $implicit: item, item, index, section, separators }"
227
+ [vListOutletContext]="{
228
+ $implicit: item,
229
+ item,
230
+ index,
231
+ section,
232
+ separators,
233
+ }"
223
234
  ></ng-container>
224
235
  </ng-template>
225
236
  @if (sectionHeaderDir !== undefined) {
@@ -240,17 +251,23 @@ export type ISectionListInputs<ItemT> = Omit<
240
251
  }
241
252
  @if (sectionSeparatorDir !== undefined) {
242
253
  <ng-template vSectionSeparator>
243
- <ng-container [vListOutlet]="sectionSeparatorDir.templateRef"></ng-container>
254
+ <ng-container
255
+ [vListOutlet]="sectionSeparatorDir.templateRef"
256
+ ></ng-container>
244
257
  </ng-template>
245
258
  }
246
259
  @if (listHeaderDir !== undefined) {
247
260
  <ng-template vListHeader>
248
- <ng-container [vListOutlet]="listHeaderDir.templateRef"></ng-container>
261
+ <ng-container
262
+ [vListOutlet]="listHeaderDir.templateRef"
263
+ ></ng-container>
249
264
  </ng-template>
250
265
  }
251
266
  @if (listFooterDir !== undefined) {
252
267
  <ng-template vListFooter>
253
- <ng-container [vListOutlet]="listFooterDir.templateRef"></ng-container>
268
+ <ng-container
269
+ [vListOutlet]="listFooterDir.templateRef"
270
+ ></ng-container>
254
271
  </ng-template>
255
272
  }
256
273
  @if (listEmptyDir !== undefined) {
@@ -267,7 +284,9 @@ export type ISectionListInputs<ItemT> = Omit<
267
284
  >
268
285
  <ng-container
269
286
  [vListOutlet]="itemSeparatorDir.templateRef"
270
- [vListOutletContext]="itemSeparatorContext(highlighted, leadingItem, trailingItem)"
287
+ [vListOutletContext]="
288
+ itemSeparatorContext(highlighted, leadingItem, trailingItem)
289
+ "
271
290
  ></ng-container>
272
291
  </ng-template>
273
292
  }
@@ -279,11 +298,21 @@ export class SectionList<ItemT = unknown>
279
298
  {
280
299
  @Input({ required: true }) sections!: ReadonlyArray<ISection<ItemT>>;
281
300
  @Input() keyExtractor?: (item: ItemT, index: number) => string;
301
+ // Passed through FLAT (sections array + flat entry index); VirtualizedSectionList owns the
302
+ // sections-vs-entries wrapper - see its entryItemLayout getter.
303
+ @Input() getItemLayout?: (
304
+ data: ReadonlyArray<ISection<ItemT>> | null,
305
+ index: number,
306
+ ) => { length: number; offset: number; index: number };
282
307
  @Input() stickySectionHeadersEnabled?: boolean;
283
308
  @Input() extraData?: unknown;
284
- @Output() readonly endReached = new EventEmitter<{ distanceFromEnd: number }>();
309
+ @Output() readonly endReached = new EventEmitter<{
310
+ distanceFromEnd: number;
311
+ }>();
285
312
  @Input() onEndReachedThreshold?: number;
286
- @Output() readonly startReached = new EventEmitter<{ distanceFromStart: number }>();
313
+ @Output() readonly startReached = new EventEmitter<{
314
+ distanceFromStart: number;
315
+ }>();
287
316
  @Input() onStartReachedThreshold?: number;
288
317
  @Output() readonly refresh = new EventEmitter<void>();
289
318
  @Input() refreshing?: boolean | null;
@@ -311,18 +340,14 @@ export class SectionList<ItemT = unknown>
311
340
  @Input() testID?: string;
312
341
  @Input() nativeID?: string;
313
342
 
314
- // Bound to the template's `[style]="resolvedStyle"`, which Angular compiles to the built-in
315
- // ɵɵstyleMap instruction — it only understands a flat object, never an array (RN's own
316
- // `style={[a, b]}` composition idiom crashes deep inside Angular's styling engine), so this
317
- // flattens `style` via the engine's own flattenStyle before it ever reaches that binding.
318
- // anchorHostStyle merges in this component's OWN anchor's class-derived style (see its doc
319
- // comment) `elementRef` below is SectionList's OWN host, not `list`'s inner VirtualizedSectionList.
320
- // A plain `flattenStyle([...])` here would allocate a FRESH object on every getter read (every CD
321
- // check), which bound onto the nested VirtualizedSectionList/VirtualizedList's own `@Input()
322
- // style` — defeats VirtualizedList's `ngDoCheck` dedup gate and free-runs change detection
323
- // forever (the exact bug `stableAnchorStyle` exists to prevent, see its doc comment and the
324
- // `flat-list-array-style.test.ts` regression it fixes). `cachedResolvedStyle` is the getter's own
325
- // persisted "previous" value across reads, since a getter has no natural field to compare against.
343
+ // Bound to `[style]="resolvedStyle"`, which Angular compiles to ɵɵstyleMap — it only
344
+ // understands a flat object, never an array (RN's `style={[a, b]}` idiom crashes deep inside
345
+ // Angular's styling engine), so this flattens `style` first, merging in this component's OWN
346
+ // anchor's class-derived style via anchorHostStyle (`elementRef` here is SectionList's own
347
+ // host, not `list`'s inner VirtualizedSectionList). A plain `flattenStyle([...])` would
348
+ // allocate a fresh object on every CD check, defeating VirtualizedList's `ngDoCheck` dedup gate
349
+ // and free-running change detection forever the bug `stableAnchorStyle` exists to prevent
350
+ // (see its doc comment). `cachedResolvedStyle` is the getter's own persisted "previous" value.
326
351
  private cachedResolvedStyle: Record<string, unknown> | undefined;
327
352
  get resolvedStyle(): IViewStyle {
328
353
  this.cachedResolvedStyle = stableAnchorStyle(
@@ -340,8 +365,10 @@ export class SectionList<ItemT = unknown>
340
365
  @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
341
366
  @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
342
367
  @Input() accessibilityLabelledBy?: string | string[];
343
- @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
344
- @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
368
+ @Input()
369
+ importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
370
+ @Input()
371
+ accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
345
372
  @Input() screenReaderFocusable?: boolean;
346
373
  @Input() accessibilityViewIsModal?: boolean;
347
374
  @Input() accessibilityElementsHidden?: boolean;
@@ -372,18 +399,24 @@ export class SectionList<ItemT = unknown>
372
399
 
373
400
  // The app's section/list authoring templates, captured from the app's DIRECT projected content
374
401
  // (single hop — always resolves) and re-stamped onto the inner VirtualizedSectionList above.
375
- @ContentChild(VSectionItemDirective) sectionItemDir?: VSectionItemDirective<ItemT>;
376
- @ContentChild(VSectionHeaderDirective) sectionHeaderDir?: VSectionHeaderDirective<ItemT>;
377
- @ContentChild(VSectionFooterDirective) sectionFooterDir?: VSectionFooterDirective<ItemT>;
378
- @ContentChild(VSectionSeparatorDirective) sectionSeparatorDir?: VSectionSeparatorDirective;
402
+ @ContentChild(VSectionItemDirective)
403
+ sectionItemDir?: VSectionItemDirective<ItemT>;
404
+ @ContentChild(VSectionHeaderDirective)
405
+ sectionHeaderDir?: VSectionHeaderDirective<ItemT>;
406
+ @ContentChild(VSectionFooterDirective)
407
+ sectionFooterDir?: VSectionFooterDirective<ItemT>;
408
+ @ContentChild(VSectionSeparatorDirective)
409
+ sectionSeparatorDir?: VSectionSeparatorDirective;
379
410
  @ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
380
411
  @ContentChild(VListFooterDirective) listFooterDir?: VListFooterDirective;
381
412
  @ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
382
- @ContentChild(VListSeparatorDirective) itemSeparatorDir?: VListSeparatorDirective<ItemT>;
413
+ @ContentChild(VListSeparatorDirective)
414
+ itemSeparatorDir?: VListSeparatorDirective<ItemT>;
383
415
 
384
416
  // The composed inner VirtualizedSectionList. Its instance IS an IVirtualizedSectionListHandle, so
385
417
  // SectionList's handle delegates straight to it. Available from ngAfterViewInit; reads lazily.
386
- @ViewChild(VirtualizedSectionList) private list?: VirtualizedSectionList<ItemT>;
418
+ @ViewChild(VirtualizedSectionList)
419
+ private list?: VirtualizedSectionList<ItemT>;
387
420
 
388
421
  // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
389
422
  // onto (see anchorHostStyle's doc comment) — NOT `list` above, which targets the real inner
@@ -1,13 +1,37 @@
1
- import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, inject } from '@angular/core';
2
- import { anchorHostStyle, SwitchHost, SymbioteHostPropsDirective } from '../../primitives';
1
+ import {
2
+ CUSTOM_ELEMENTS_SCHEMA,
3
+ Component,
4
+ ElementRef,
5
+ forwardRef,
6
+ inject,
7
+ signal,
8
+ type DoCheck,
9
+ } from '@angular/core';
10
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
11
+ import {
12
+ anchorHostStyle,
13
+ SwitchHost,
14
+ SymbioteHostPropsDirective,
15
+ SymbioteStyleInputDirective,
16
+ } from '../../primitives';
3
17
  import { SwitchBase } from './shared';
4
18
  export type { ISwitchProps, ISwitchTrackColor } from './shared';
5
19
 
6
20
  @Component({
7
21
  selector: 'Switch',
8
22
  standalone: true,
23
+ hostDirectives: [
24
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
25
+ ],
9
26
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
10
27
  imports: [SwitchHost, SymbioteHostPropsDirective],
28
+ providers: [
29
+ {
30
+ provide: NG_VALUE_ACCESSOR,
31
+ useExisting: forwardRef(() => Switch),
32
+ multi: true,
33
+ },
34
+ ],
11
35
  inputs: [
12
36
  'value',
13
37
  'disabled',
@@ -32,30 +56,58 @@ export type { ISwitchProps, ISwitchTrackColor } from './shared';
32
56
  template: `
33
57
  <symbiote-switch
34
58
  #nativeSwitch="symbioteHost"
35
- [symbioteHostProps]="hostProps"
59
+ [symbioteHostProps]="hostProps()"
36
60
  (change)="handleChange($event, nativeSwitch)"
37
61
  />
38
62
  `,
39
63
  })
40
- export class Switch extends SwitchBase {
64
+ export class Switch extends SwitchBase implements DoCheck {
41
65
  // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
42
66
  // onto (see anchorHostStyle's doc comment) — NOT the <symbiote-switch> leaf one level down.
43
67
  private readonly elementRef = inject(ElementRef);
44
68
 
45
69
  protected readonly platform = {
46
70
  snapBackCommand: 'setNativeValue',
47
- trackColorProps: (value: boolean, trackColor?: { false?: string; true?: string }) => ({
71
+ trackColorProps: (
72
+ value: boolean,
73
+ trackColor?: { false?: string; true?: string },
74
+ ) => ({
48
75
  trackColorForFalse: trackColor?.false,
49
76
  trackColorForTrue: trackColor?.true,
50
77
  trackTintColor: value ? trackColor?.true : trackColor?.false,
51
78
  }),
52
79
  };
53
80
 
81
+ // The anchor's class-derived style is written by the renderer's addClass/removeClass and never
82
+ // reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
83
+ // anchorHostStyle's doc comment), so SwitchBase's inputsRevision cannot see it. Hence its own
84
+ // signal: a plain field read inside a computed() would be untracked and the bag would go stale
85
+ // the moment a `[class.x]`/`[ngClass]` toggled.
86
+ //
87
+ // ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
88
+ // next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
89
+ // template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
90
+ // refreshes — the exact window a class poll needs. callHooks also runs under
91
+ // setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
92
+ // commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
93
+ // own Object.is check makes an unchanged poll a no-op that dirties nothing.
94
+ //
95
+ // Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
96
+ // TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
97
+ // node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
98
+ // one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
99
+ // template's styling instructions and so are always current here.
100
+ private readonly anchorStyle = signal<unknown>(undefined);
101
+
102
+ ngDoCheck(): void {
103
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
104
+ }
105
+
54
106
  // The anchor's class-derived style goes first, then whatever SwitchBase already resolved
55
107
  // (its own explicit `style` @Input plus the ios_backgroundColor fold), so an explicit [style]
56
108
  // still beats the ambient class.
57
- override get hostProps(): Record<string, unknown> {
58
- const props = super.hostProps;
59
- return { ...props, style: [anchorHostStyle(this.elementRef), props['style']] };
109
+ protected override buildHostProps(): Record<string, unknown> {
110
+ const props = super.buildHostProps();
111
+ return { ...props, style: [this.anchorStyle(), props['style']] };
60
112
  }
61
113
  }
@@ -1,13 +1,37 @@
1
- import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, inject } from '@angular/core';
2
- import { anchorHostStyle, SwitchHost, SymbioteHostPropsDirective } from '../../primitives';
1
+ import {
2
+ CUSTOM_ELEMENTS_SCHEMA,
3
+ Component,
4
+ ElementRef,
5
+ forwardRef,
6
+ inject,
7
+ signal,
8
+ type DoCheck,
9
+ } from '@angular/core';
10
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
11
+ import {
12
+ anchorHostStyle,
13
+ SwitchHost,
14
+ SymbioteHostPropsDirective,
15
+ SymbioteStyleInputDirective,
16
+ } from '../../primitives';
3
17
  import { SwitchBase } from './shared';
4
18
  export type { ISwitchProps, ISwitchTrackColor } from './shared';
5
19
 
6
20
  @Component({
7
21
  selector: 'Switch',
8
22
  standalone: true,
23
+ hostDirectives: [
24
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
25
+ ],
9
26
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
10
27
  imports: [SwitchHost, SymbioteHostPropsDirective],
28
+ providers: [
29
+ {
30
+ provide: NG_VALUE_ACCESSOR,
31
+ useExisting: forwardRef(() => Switch),
32
+ multi: true,
33
+ },
34
+ ],
11
35
  inputs: [
12
36
  'value',
13
37
  'disabled',
@@ -32,29 +56,57 @@ export type { ISwitchProps, ISwitchTrackColor } from './shared';
32
56
  template: `
33
57
  <symbiote-switch
34
58
  #nativeSwitch="symbioteHost"
35
- [symbioteHostProps]="hostProps"
59
+ [symbioteHostProps]="hostProps()"
36
60
  (change)="handleChange($event, nativeSwitch)"
37
61
  />
38
62
  `,
39
63
  })
40
- export class Switch extends SwitchBase {
64
+ export class Switch extends SwitchBase implements DoCheck {
41
65
  // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
42
66
  // onto (see anchorHostStyle's doc comment) — NOT the <symbiote-switch> leaf one level down.
43
67
  private readonly elementRef = inject(ElementRef);
44
68
 
45
69
  protected readonly platform = {
46
70
  snapBackCommand: 'setValue',
47
- trackColorProps: (_value: boolean, trackColor?: { false?: string; true?: string }) => ({
71
+ trackColorProps: (
72
+ _value: boolean,
73
+ trackColor?: { false?: string; true?: string },
74
+ ) => ({
48
75
  onTintColor: trackColor?.true,
49
76
  tintColor: trackColor?.false,
50
77
  }),
51
78
  };
52
79
 
80
+ // The anchor's class-derived style is written by the renderer's addClass/removeClass and never
81
+ // reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
82
+ // anchorHostStyle's doc comment), so SwitchBase's inputsRevision cannot see it. Hence its own
83
+ // signal: a plain field read inside a computed() would be untracked and the bag would go stale
84
+ // the moment a `[class.x]`/`[ngClass]` toggled.
85
+ //
86
+ // ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
87
+ // next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
88
+ // template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
89
+ // refreshes — the exact window a class poll needs. callHooks also runs under
90
+ // setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
91
+ // commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
92
+ // own Object.is check makes an unchanged poll a no-op that dirties nothing.
93
+ //
94
+ // Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
95
+ // TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
96
+ // node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
97
+ // one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
98
+ // template's styling instructions and so are always current here.
99
+ private readonly anchorStyle = signal<unknown>(undefined);
100
+
101
+ ngDoCheck(): void {
102
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
103
+ }
104
+
53
105
  // The anchor's class-derived style goes first, then whatever SwitchBase already resolved
54
106
  // (its own explicit `style` @Input plus the ios_backgroundColor fold), so an explicit [style]
55
107
  // still beats the ambient class.
56
- override get hostProps(): Record<string, unknown> {
57
- const props = super.hostProps;
58
- return { ...props, style: [anchorHostStyle(this.elementRef), props['style']] };
108
+ protected override buildHostProps(): Record<string, unknown> {
109
+ const props = super.buildHostProps();
110
+ return { ...props, style: [this.anchorStyle(), props['style']] };
59
111
  }
60
112
  }