@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,37 +1,35 @@
1
1
  // FlatList, the Angular convenience surface over VirtualizedList. It takes a plain `data` array
2
2
  // and derives getItem/getItemCount; numColumns packs items into rows (each row a flex-row of N
3
- // columns), so the virtualized stream is rows, not items (RN's FlatList). All windowing /
4
- // viewability / batching / imperative scrolling are inherited from VirtualizedList; the data
5
- // shaping (chunkIntoRows / rowKeyExtractor / expandRowViewability / row-separator unwrap) is shared
6
- // verbatim from @symbiote-native/components, exactly as the React and Vue FlatLists reuse it. This is the
7
- // Angular twin of adapters/vue/src/components/flat-list/index.ts.
3
+ // columns), so the virtualized stream is rows, not items (RN's FlatList). Windowing / viewability /
4
+ // batching / imperative scrolling are inherited from VirtualizedList; the data shaping
5
+ // (chunkIntoRows / rowKeyExtractor / expandRowViewability / row-separator unwrap) is shared
6
+ // verbatim from @symbiote-native/components, same as React/Vue's FlatLists.
8
7
  //
9
- // TEMPLATE FORWARDING — both paths RE-STAMP; neither uses a bare `<ng-content>` passthrough.
8
+ // TEMPLATE FORWARDING — both column paths RE-STAMP; neither uses a bare `<ng-content>` passthrough.
10
9
  // Angular's @ContentChild does NOT resolve a directive across a SECOND `<ng-content>` re-projection
11
- // hop (it only sees what was projected directly onto the querying component's own tag) — a bare
12
- // `<VirtualizedList ...><ng-content></ng-content></VirtualizedList>` here left VirtualizedList's own
13
- // itemDir/headerDir/etc. undefined, so every cell rendered empty (a real, confirmed device bug see
14
- // flat-list.test.ts). Fixed for both column modes the same way:
10
+ // hop (only what's projected directly onto the querying component's own tag) — a bare
11
+ // `<VirtualizedList ...><ng-content></ng-content></VirtualizedList>` left VirtualizedList's own
12
+ // itemDir/headerDir/etc. undefined, rendering every cell empty (confirmed device bug, see
13
+ // flat-list.test.ts). Fixed for both column modes:
15
14
  // * Single column (numColumns <= 1): FlatList captures the app's `<ng-template vListItem>` (and
16
15
  // vListHeader/vListFooter/vListEmpty/vListSeparator) with its OWN @ContentChild — a single,
17
16
  // direct projection hop, which always resolves — then re-authors equivalent `<ng-template>`s on
18
- // `<VirtualizedList>`, each forwarding the captured templateRef + context through
17
+ // `<VirtualizedList>`, forwarding the captured templateRef + context through
19
18
  // VListOutletDirective. Item/index/separators pass through 1:1 (no row wrapping).
20
- // * Multi column (numColumns > 1): the same re-stamp, but the app's vListItem is typed for ItemT
21
- // while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work even if
22
- // the projection issue didn't exist. The row vListItem lays out the N columns side by side (each
23
- // cell stamps the app's item template via VListOutletDirective with the per-item context {item,
24
- // index: row.startIndex + column, separators}), the row vListSeparator unwraps the flanking rows
25
- // to their last/first item, and onViewableItemsChanged is wrapped with expandRowViewability so the
26
- // caller still sees per-item visibility. This mirrors exactly how Vue's FlatList intercepts
27
- // renderItem with renderRow.
19
+ // * Multi column (numColumns > 1): same re-stamp, but the app's vListItem is typed for ItemT
20
+ // while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work
21
+ // regardless. The row vListItem lays out the N columns side by side (each cell stamps the
22
+ // app's item template via VListOutletDirective with context {item, index: row.startIndex +
23
+ // column, separators}), the row vListSeparator unwraps the flanking rows to their last/first
24
+ // item, and onViewableItemsChanged is wrapped with expandRowViewability so the caller still
25
+ // sees per-item visibility mirrors how Vue's FlatList intercepts renderItem with renderRow.
28
26
  //
29
27
  // The imperative handle (scrollToIndex / scrollToOffset / scrollToEnd / scrollToItem /
30
28
  // recordInteraction / flashScrollIndicators / getScroll* / getNativeScrollRef) is RN's FlatList
31
- // surface; FlatList re-exposes it by delegating to the inner VirtualizedList (@ViewChild). The
32
- // element-returning props (renderItem / ItemSeparatorComponent / List{Header,Footer,Empty}Component)
33
- // are templates in Angular, so they are absent from IFlatListProps, per the per-adapter
34
- // children/render prop split; everything agnostic mirrors IVirtualizedListProps.
29
+ // surface, delegated straight to the inner VirtualizedList (@ViewChild). The element-returning
30
+ // props (renderItem / ItemSeparatorComponent / List{Header,Footer,Empty}Component) are templates
31
+ // in Angular, so they're absent from IFlatListProps per the per-adapter children/render split;
32
+ // everything else mirrors IVirtualizedListProps.
35
33
 
36
34
  import {
37
35
  CUSTOM_ELEMENTS_SCHEMA,
@@ -44,6 +42,7 @@ import {
44
42
  Output,
45
43
  ViewChild,
46
44
  inject,
45
+ type DoCheck,
47
46
  type OnChanges,
48
47
  type SimpleChanges,
49
48
  } from '@angular/core';
@@ -81,14 +80,20 @@ import {
81
80
  type IVListSeparatorContext,
82
81
  } from '../virtualized-list';
83
82
  import { VListOutletDirective } from '../virtualized-list/directives';
84
- import { stableAnchorStyle, ViewHost } from '../../primitives';
83
+ import {
84
+ stableAnchorStyle,
85
+ SymbioteStyleInputDirective,
86
+ ViewHost,
87
+ } from '../../primitives';
85
88
 
86
89
  function isRecord(value: unknown): value is Record<string, unknown> {
87
90
  return typeof value === 'object' && value !== null;
88
91
  }
89
92
  function isRow<ItemT>(value: unknown): value is IRow<ItemT> {
90
93
  return (
91
- isRecord(value) && Array.isArray(value['items']) && typeof value['startIndex'] === 'number'
94
+ isRecord(value) &&
95
+ Array.isArray(value['items']) &&
96
+ typeof value['startIndex'] === 'number'
92
97
  );
93
98
  }
94
99
  function isSeparators(value: unknown): value is ISeparators {
@@ -130,7 +135,10 @@ export {
130
135
  VListItemDirective,
131
136
  VListSeparatorDirective,
132
137
  } from '../virtualized-list';
133
- export type { IVListItemContext, IVListSeparatorContext } from '../virtualized-list';
138
+ export type {
139
+ IVListItemContext,
140
+ IVListSeparatorContext,
141
+ } from '../virtualized-list';
134
142
 
135
143
  // FlatList's imperative handle is exactly VirtualizedList's.
136
144
  export type IFlatListHandle = IVirtualizedListHandle;
@@ -165,6 +173,9 @@ export type IFlatListInputs<ItemT> = Omit<
165
173
  @Component({
166
174
  selector: 'FlatList',
167
175
  standalone: true,
176
+ hostDirectives: [
177
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
178
+ ],
168
179
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
169
180
  imports: [
170
181
  VirtualizedList,
@@ -254,7 +265,9 @@ export type IFlatListInputs<ItemT> = Omit<
254
265
  >
255
266
  <ng-container
256
267
  [vListOutlet]="separatorDir.templateRef"
257
- [vListOutletContext]="rowSeparatorContext(highlighted, leadingItem, trailingItem)"
268
+ [vListOutletContext]="
269
+ rowSeparatorContext(highlighted, leadingItem, trailingItem)
270
+ "
258
271
  ></ng-container>
259
272
  </ng-template>
260
273
  }
@@ -299,7 +312,12 @@ export type IFlatListInputs<ItemT> = Omit<
299
312
  [style]="resolvedStyle"
300
313
  [contentContainerStyle]="contentContainerStyle"
301
314
  >
302
- <ng-template vListItem let-item let-index="index" let-separators="separators">
315
+ <ng-template
316
+ vListItem
317
+ let-item
318
+ let-index="index"
319
+ let-separators="separators"
320
+ >
303
321
  <ng-container
304
322
  [vListOutlet]="itemDir?.templateRef"
305
323
  [vListOutletContext]="{ $implicit: item, index, separators }"
@@ -329,7 +347,9 @@ export type IFlatListInputs<ItemT> = Omit<
329
347
  >
330
348
  <ng-container
331
349
  [vListOutlet]="separatorDir.templateRef"
332
- [vListOutletContext]="itemSeparatorContext(highlighted, leadingItem, trailingItem)"
350
+ [vListOutletContext]="
351
+ itemSeparatorContext(highlighted, leadingItem, trailingItem)
352
+ "
333
353
  ></ng-container>
334
354
  </ng-template>
335
355
  }
@@ -338,15 +358,21 @@ export type IFlatListInputs<ItemT> = Omit<
338
358
  `,
339
359
  })
340
360
  export class FlatList<ItemT = unknown>
341
- implements IFlatListInputs<ItemT>, IVirtualizedListHandle, OnChanges
361
+ implements IFlatListInputs<ItemT>, IVirtualizedListHandle, DoCheck, OnChanges
342
362
  {
343
363
  // The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
344
364
  // `[onEndReached]="…"` — re-emitted straight from the inner VirtualizedList's own @Output()s (see
345
365
  // the template's `(endReached)="endReached.emit($event)"` style forwarding above).
346
- @Output() readonly endReached = new EventEmitter<{ distanceFromEnd: number }>();
347
- @Output() readonly startReached = new EventEmitter<{ distanceFromStart: number }>();
366
+ @Output() readonly endReached = new EventEmitter<{
367
+ distanceFromEnd: number;
368
+ }>();
369
+ @Output() readonly startReached = new EventEmitter<{
370
+ distanceFromStart: number;
371
+ }>();
348
372
  @Output() readonly refresh = new EventEmitter<void>();
349
- @Output() readonly viewableItemsChanged = new EventEmitter<IViewableItemsChangedInfo<ItemT>>();
373
+ @Output() readonly viewableItemsChanged = new EventEmitter<
374
+ IViewableItemsChangedInfo<ItemT>
375
+ >();
350
376
  @Output() readonly scrollToIndexFailed = new EventEmitter<{
351
377
  index: number;
352
378
  highestMeasuredFrameIndex: number;
@@ -367,8 +393,10 @@ export class FlatList<ItemT = unknown>
367
393
  @Input() onStartReachedThreshold?: number;
368
394
  @Input() refreshing?: boolean | null;
369
395
  @Input() progressViewOffset?: number;
370
- @Input() viewabilityConfig?: IVirtualizedListProps<ItemT>['viewabilityConfig'];
371
- @Input() viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
396
+ @Input()
397
+ viewabilityConfig?: IVirtualizedListProps<ItemT>['viewabilityConfig'];
398
+ @Input()
399
+ viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
372
400
  @Input() initialNumToRender?: number;
373
401
  @Input() initialScrollIndex?: number;
374
402
  @Input() maxToRenderPerBatch?: number;
@@ -380,10 +408,13 @@ export class FlatList<ItemT = unknown>
380
408
  autoscrollToTopThreshold?: number;
381
409
  };
382
410
  @Input() onScroll?: IVirtualizedListProps<ItemT>['onScroll'];
383
- @Input() onScrollBeginDrag?: IVirtualizedListProps<ItemT>['onScrollBeginDrag'];
411
+ @Input()
412
+ onScrollBeginDrag?: IVirtualizedListProps<ItemT>['onScrollBeginDrag'];
384
413
  @Input() onScrollEndDrag?: IVirtualizedListProps<ItemT>['onScrollEndDrag'];
385
- @Input() onMomentumScrollBegin?: IVirtualizedListProps<ItemT>['onMomentumScrollBegin'];
386
- @Input() onMomentumScrollEnd?: IVirtualizedListProps<ItemT>['onMomentumScrollEnd'];
414
+ @Input()
415
+ onMomentumScrollBegin?: IVirtualizedListProps<ItemT>['onMomentumScrollBegin'];
416
+ @Input()
417
+ onMomentumScrollEnd?: IVirtualizedListProps<ItemT>['onMomentumScrollEnd'];
387
418
  @Input() scrollEventThrottle?: number;
388
419
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
389
420
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
@@ -397,7 +428,8 @@ export class FlatList<ItemT = unknown>
397
428
  @ContentChild(VListHeaderDirective) headerDir?: VListHeaderDirective;
398
429
  @ContentChild(VListFooterDirective) footerDir?: VListFooterDirective;
399
430
  @ContentChild(VListEmptyDirective) emptyDir?: VListEmptyDirective;
400
- @ContentChild(VListSeparatorDirective) separatorDir?: VListSeparatorDirective<ItemT>;
431
+ @ContentChild(VListSeparatorDirective)
432
+ separatorDir?: VListSeparatorDirective<ItemT>;
401
433
 
402
434
  // The composed inner list (whichever numColumns branch rendered). Its instance IS the
403
435
  // IVirtualizedListHandle, so FlatList's handle delegates straight to it.
@@ -427,18 +459,20 @@ export class FlatList<ItemT = unknown>
427
459
  return this.columns > SINGLE_COLUMN;
428
460
  }
429
461
 
462
+ // ngDoCheck, NOT ngOnChanges: a bare `class=` never becomes an @Input, so ngOnChanges does not
463
+ // RUN for it and the merge froze at its creation value (a stranded tile on the ReactiveStyle
464
+ // canary). stableAnchorStyle, not a bare flattenStyle, keeps `resolvedStyle`'s reference stable
465
+ // across ticks that changed nothing - a fresh object every tick defeats VirtualizedList's own
466
+ // dedup gate and free-runs CD (see stableAnchorStyle's doc comment).
467
+ ngDoCheck(): void {
468
+ this.resolvedStyle = stableAnchorStyle(
469
+ this.elementRef,
470
+ this.style,
471
+ this.resolvedStyle,
472
+ );
473
+ }
474
+
430
475
  ngOnChanges(changes: SimpleChanges): void {
431
- // Recomputed on EVERY ngOnChanges call, not gated on `changes['style']`: a class= on the
432
- // FlatList use site (no matching [style] binding at all) never appears in `changes`, so a
433
- // guard here would permanently skip picking up anchorHostStyle for that (the actual bug-report
434
- // shape) — ngOnChanges still fires at least once at mount via the required `data` @Input(), and
435
- // by then the anchor's class-derived style is already resolved synchronously (routeProp runs
436
- // inline from Renderer2.addClass, before this component's own lifecycle hooks). stableAnchorStyle
437
- // (not a bare flattenStyle) keeps `resolvedStyle`'s REFERENCE stable across ticks where nothing
438
- // actually changed — `[style]="resolvedStyle"` binds onto VirtualizedList's own `@Input() style`,
439
- // which feeds its `ngDoCheck` dedup gate; a fresh object every tick would defeat that gate and
440
- // free-run change detection forever (see stableAnchorStyle's doc comment).
441
- this.resolvedStyle = stableAnchorStyle(this.elementRef, this.style, this.resolvedStyle);
442
476
  if (changes['columnWrapperStyle'] !== undefined) {
443
477
  // A class-name string resolves through the shared registry before flattenStyle, which
444
478
  // only understands style objects/arrays.
@@ -446,11 +480,18 @@ export class FlatList<ItemT = unknown>
446
480
  typeof this.columnWrapperStyle === 'string'
447
481
  ? resolveClassName(this.columnWrapperStyle)
448
482
  : this.columnWrapperStyle;
449
- this.rowStyle = flattenStyle([{ flexDirection: 'row' }, resolvedColumnWrapperStyle]);
483
+ this.rowStyle = flattenStyle([
484
+ { flexDirection: 'row' },
485
+ resolvedColumnWrapperStyle,
486
+ ]);
450
487
  }
451
488
  if (changes['data'] !== undefined || changes['numColumns'] !== undefined) {
452
- this.rows = this.isMultiColumn ? chunkIntoRows(this.data, this.columns) : [];
453
- dlog(`Angular FlatList over ${this.data?.length ?? 0} items, ${this.columns} column(s)`);
489
+ this.rows = this.isMultiColumn
490
+ ? chunkIntoRows(this.data, this.columns)
491
+ : [];
492
+ dlog(
493
+ `Angular FlatList over ${this.data?.length ?? 0} items, ${this.columns} column(s)`,
494
+ );
454
495
  }
455
496
  if (
456
497
  changes['viewabilityConfigCallbackPairs'] !== undefined ||
@@ -471,15 +512,24 @@ export class FlatList<ItemT = unknown>
471
512
 
472
513
  // Viewability over rows expands back to per-item tokens, so the caller sees item-level
473
514
  // visibility, not row-level (shared expandRowViewability), matching Vue.
474
- rowViewableItemsChanged = (info: IViewableItemsChangedInfo<IRow<ItemT>>): void => {
475
- this.viewableItemsChanged.emit(expandRowViewability(info, this.keyExtractor));
515
+ rowViewableItemsChanged = (
516
+ info: IViewableItemsChangedInfo<IRow<ItemT>>,
517
+ ): void => {
518
+ this.viewableItemsChanged.emit(
519
+ expandRowViewability(info, this.keyExtractor),
520
+ );
476
521
  };
477
522
 
478
- private buildRowViewabilityPairs(): IViewabilityConfigCallbackPair<IRow<ItemT>>[] | undefined {
523
+ private buildRowViewabilityPairs():
524
+ IViewabilityConfigCallbackPair<IRow<ItemT>>[] | undefined {
479
525
  return this.viewabilityConfigCallbackPairs?.map(pair => ({
480
526
  viewabilityConfig: pair.viewabilityConfig,
481
- onViewableItemsChanged: (rowInfo: IViewableItemsChangedInfo<IRow<ItemT>>): void => {
482
- pair.onViewableItemsChanged?.(expandRowViewability(rowInfo, this.keyExtractor));
527
+ onViewableItemsChanged: (
528
+ rowInfo: IViewableItemsChangedInfo<IRow<ItemT>>,
529
+ ): void => {
530
+ pair.onViewableItemsChanged?.(
531
+ expandRowViewability(rowInfo, this.keyExtractor),
532
+ );
483
533
  },
484
534
  }));
485
535
  }
@@ -496,7 +546,9 @@ export class FlatList<ItemT = unknown>
496
546
  const handle = isSeparators(separators) ? separators : NOOP_SEPARATORS;
497
547
  return row.items.map((item, column) => {
498
548
  const index = row.startIndex + column;
499
- const key = this.keyExtractor ? this.keyExtractor(item, index) : String(index);
549
+ const key = this.keyExtractor
550
+ ? this.keyExtractor(item, index)
551
+ : String(index);
500
552
  return { key, context: { $implicit: item, index, separators: handle } };
501
553
  });
502
554
  }
@@ -513,8 +565,12 @@ export class FlatList<ItemT = unknown>
513
565
  return {
514
566
  $implicit: isHighlighted,
515
567
  highlighted: isHighlighted,
516
- leadingItem: isRow<ItemT>(leadingRow) ? lastItemOfRow(leadingRow) : undefined,
517
- trailingItem: isRow<ItemT>(trailingRow) ? firstItemOfRow(trailingRow) : undefined,
568
+ leadingItem: isRow<ItemT>(leadingRow)
569
+ ? lastItemOfRow(leadingRow)
570
+ : undefined,
571
+ trailingItem: isRow<ItemT>(trailingRow)
572
+ ? firstItemOfRow(trailingRow)
573
+ : undefined,
518
574
  };
519
575
  }
520
576
 
@@ -547,7 +603,11 @@ export class FlatList<ItemT = unknown>
547
603
  }): void {
548
604
  this.listRef?.scrollToIndex(params);
549
605
  }
550
- scrollToItem(params: { item: unknown; animated?: boolean; viewPosition?: number }): void {
606
+ scrollToItem(params: {
607
+ item: unknown;
608
+ animated?: boolean;
609
+ viewPosition?: number;
610
+ }): void {
551
611
  this.listRef?.scrollToItem(params);
552
612
  }
553
613
  scrollToEnd(params?: { animated?: boolean }): void {
@@ -1,6 +1,24 @@
1
- import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, inject } from '@angular/core';
2
- import { anchorHostStyle, ImageHost, SymbioteHostPropsDirective } from '../../primitives';
3
- import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps } from './shared';
1
+ import {
2
+ CUSTOM_ELEMENTS_SCHEMA,
3
+ Component,
4
+ ElementRef,
5
+ inject,
6
+ signal,
7
+ type DoCheck,
8
+ type OnChanges,
9
+ } from '@angular/core';
10
+ import {
11
+ anchorHostStyle,
12
+ ImageHost,
13
+ SymbioteHostPropsDirective,
14
+ SymbioteStyleInputDirective,
15
+ } from '../../primitives';
16
+ import {
17
+ IMAGE_INPUTS,
18
+ IMAGE_OUTPUTS,
19
+ ImageBase,
20
+ resolveImageProps,
21
+ } from './shared';
4
22
  export { setImageSourceResolver } from './shared';
5
23
  export type {
6
24
  IImageProps,
@@ -14,13 +32,16 @@ export type {
14
32
  @Component({
15
33
  selector: 'Image',
16
34
  standalone: true,
35
+ hostDirectives: [
36
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
37
+ ],
17
38
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
18
39
  imports: [ImageHost, SymbioteHostPropsDirective],
19
40
  inputs: IMAGE_INPUTS,
20
41
  outputs: IMAGE_OUTPUTS,
21
42
  template: `
22
43
  <symbiote-image
23
- [symbioteHostProps]="imageProps"
44
+ [symbioteHostProps]="imageProps()"
24
45
  (accessibilityAction)="handleAccessibilityAction($event)"
25
46
  (accessibilityTap)="handleAccessibilityTap($event)"
26
47
  (magicTap)="handleMagicTap($event)"
@@ -34,15 +55,45 @@ export type {
34
55
  />
35
56
  `,
36
57
  })
37
- export class Image extends ImageBase {
58
+ export class Image extends ImageBase implements OnChanges, DoCheck {
38
59
  // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
39
60
  // onto (see anchorHostStyle's doc comment) — NOT the inner `symbiote-image` one level down.
40
61
  private readonly elementRef = inject(ElementRef);
41
62
 
42
- override get imageProps(): Record<string, unknown> {
63
+ // The anchor's class-derived style is written by the renderer's addClass/removeClass and never
64
+ // reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
65
+ // anchorHostStyle's doc comment), so ImageBase's inputsRevision cannot see it. Hence its own
66
+ // signal: a plain field read inside a computed() would be untracked and the bag would go stale
67
+ // the moment a `[class.x]`/`[ngClass]` toggled.
68
+ //
69
+ // ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
70
+ // next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
71
+ // template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
72
+ // refreshes — the exact window a class poll needs. callHooks also runs under
73
+ // setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
74
+ // commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
75
+ // own Object.is check makes an unchanged poll a no-op that dirties nothing.
76
+ //
77
+ // Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
78
+ // TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
79
+ // node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
80
+ // one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
81
+ // template's styling instructions and so are always current here.
82
+ private readonly anchorStyle = signal<unknown>(undefined);
83
+
84
+ // ImageBase is undecorated, so the input bump is declared here rather than inherited.
85
+ ngOnChanges(): void {
86
+ this.bumpInputsRevision();
87
+ }
88
+
89
+ ngDoCheck(): void {
90
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
91
+ }
92
+
93
+ protected override buildImageProps(): Record<string, unknown> {
43
94
  return resolveImageProps({
44
95
  ...this.imageInputProps,
45
- style: [anchorHostStyle(this.elementRef), this.style],
96
+ style: [this.anchorStyle(), this.style],
46
97
  });
47
98
  }
48
99
  }
@@ -1,6 +1,24 @@
1
- import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, inject } from '@angular/core';
2
- import { anchorHostStyle, ImageHost, SymbioteHostPropsDirective } from '../../primitives';
3
- import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps } from './shared';
1
+ import {
2
+ CUSTOM_ELEMENTS_SCHEMA,
3
+ Component,
4
+ ElementRef,
5
+ inject,
6
+ signal,
7
+ type DoCheck,
8
+ type OnChanges,
9
+ } from '@angular/core';
10
+ import {
11
+ anchorHostStyle,
12
+ ImageHost,
13
+ SymbioteHostPropsDirective,
14
+ SymbioteStyleInputDirective,
15
+ } from '../../primitives';
16
+ import {
17
+ IMAGE_INPUTS,
18
+ IMAGE_OUTPUTS,
19
+ ImageBase,
20
+ resolveImageProps,
21
+ } from './shared';
4
22
  export { setImageSourceResolver } from './shared';
5
23
  export type {
6
24
  IImageProps,
@@ -14,13 +32,16 @@ export type {
14
32
  @Component({
15
33
  selector: 'Image',
16
34
  standalone: true,
35
+ hostDirectives: [
36
+ { directive: SymbioteStyleInputDirective, inputs: ['style'] },
37
+ ],
17
38
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
18
39
  imports: [ImageHost, SymbioteHostPropsDirective],
19
40
  inputs: IMAGE_INPUTS,
20
41
  outputs: IMAGE_OUTPUTS,
21
42
  template: `
22
43
  <symbiote-image
23
- [symbioteHostProps]="imageProps"
44
+ [symbioteHostProps]="imageProps()"
24
45
  (accessibilityAction)="handleAccessibilityAction($event)"
25
46
  (accessibilityTap)="handleAccessibilityTap($event)"
26
47
  (magicTap)="handleMagicTap($event)"
@@ -34,15 +55,45 @@ export type {
34
55
  />
35
56
  `,
36
57
  })
37
- export class Image extends ImageBase {
58
+ export class Image extends ImageBase implements OnChanges, DoCheck {
38
59
  // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
39
60
  // onto (see anchorHostStyle's doc comment) — NOT the inner `symbiote-image` one level down.
40
61
  private readonly elementRef = inject(ElementRef);
41
62
 
42
- override get imageProps(): Record<string, unknown> {
63
+ // The anchor's class-derived style is written by the renderer's addClass/removeClass and never
64
+ // reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
65
+ // anchorHostStyle's doc comment), so ImageBase's inputsRevision cannot see it. Hence its own
66
+ // signal: a plain field read inside a computed() would be untracked and the bag would go stale
67
+ // the moment a `[class.x]`/`[ngClass]` toggled.
68
+ //
69
+ // ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
70
+ // next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
71
+ // template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
72
+ // refreshes — the exact window a class poll needs. callHooks also runs under
73
+ // setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
74
+ // commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
75
+ // own Object.is check makes an unchanged poll a no-op that dirties nothing.
76
+ //
77
+ // Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
78
+ // TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
79
+ // node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
80
+ // one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
81
+ // template's styling instructions and so are always current here.
82
+ private readonly anchorStyle = signal<unknown>(undefined);
83
+
84
+ // ImageBase is undecorated, so the input bump is declared here rather than inherited.
85
+ ngOnChanges(): void {
86
+ this.bumpInputsRevision();
87
+ }
88
+
89
+ ngDoCheck(): void {
90
+ this.anchorStyle.set(anchorHostStyle(this.elementRef));
91
+ }
92
+
93
+ protected override buildImageProps(): Record<string, unknown> {
43
94
  return resolveImageProps({
44
95
  ...this.imageInputProps,
45
- style: [anchorHostStyle(this.elementRef), this.style],
96
+ style: [this.anchorStyle(), this.style],
46
97
  });
47
98
  }
48
99
  }