@symbiote-native/angular 3.3.0 → 3.4.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 (179) hide show
  1. package/babel-register-composed.cjs +1 -0
  2. package/build/angular/accessibility-inputs.d.ts +2 -1
  3. package/build/angular/accessibility-inputs.js +4 -1
  4. package/build/angular/accessibility-inputs.js.map +1 -1
  5. package/build/angular/callback-host.d.ts +1 -1
  6. package/build/angular/callback-host.js +57 -9
  7. package/build/angular/callback-host.js.map +1 -1
  8. package/build/angular/components/flat-list/flat-list-handle.d.ts +35 -0
  9. package/build/angular/components/flat-list/flat-list-handle.js +48 -0
  10. package/build/angular/components/flat-list/flat-list-handle.js.map +1 -0
  11. package/build/angular/components/flat-list/flat-list-template.d.ts +1 -1
  12. package/build/angular/components/flat-list/flat-list-template.js +16 -0
  13. package/build/angular/components/flat-list/flat-list-template.js.map +1 -1
  14. package/build/angular/components/flat-list/index.d.ts +6 -30
  15. package/build/angular/components/flat-list/index.js +6 -36
  16. package/build/angular/components/flat-list/index.js.map +1 -1
  17. package/build/angular/components/image-inputs.d.ts +4 -0
  18. package/build/angular/components/image-inputs.js +81 -0
  19. package/build/angular/components/image-inputs.js.map +1 -0
  20. package/build/angular/components/image-props-resolve.d.ts +3 -0
  21. package/build/angular/components/image-props-resolve.js +133 -0
  22. package/build/angular/components/image-props-resolve.js.map +1 -0
  23. package/build/angular/components/image-shared.d.ts +8 -6
  24. package/build/angular/components/image-shared.js +23 -212
  25. package/build/angular/components/image-shared.js.map +1 -1
  26. package/build/angular/components/input-accessory-view-props.d.ts +4 -4
  27. package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -6
  28. package/build/angular/components/keyboard-avoiding-view/index.js +29 -56
  29. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  30. package/build/angular/components/layout-conformance-props.d.ts +4 -0
  31. package/build/angular/components/layout-conformance-props.js +2 -0
  32. package/build/angular/components/layout-conformance-props.js.map +1 -0
  33. package/build/angular/components/modal/index.d.ts +6 -5
  34. package/build/angular/components/modal/index.js +17 -2
  35. package/build/angular/components/modal/index.js.map +1 -1
  36. package/build/angular/components/refresh-control-props.d.ts +7 -7
  37. package/build/angular/components/scroll-view-props.d.ts +8 -5
  38. package/build/angular/components/section-list/index.d.ts +17 -8
  39. package/build/angular/components/section-list/index.js +37 -28
  40. package/build/angular/components/section-list/index.js.map +1 -1
  41. package/build/angular/components/touchable-props.d.ts +2 -2
  42. package/build/angular/components/virtualized-list/cell-renderer-context.d.ts +12 -0
  43. package/build/angular/components/virtualized-list/cell-renderer-context.js +16 -0
  44. package/build/angular/components/virtualized-list/cell-renderer-context.js.map +1 -0
  45. package/build/angular/components/virtualized-list/directives.d.ts +26 -6
  46. package/build/angular/components/virtualized-list/directives.js +59 -5
  47. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  48. package/build/angular/components/virtualized-list/index.d.ts +19 -15
  49. package/build/angular/components/virtualized-list/index.js +91 -61
  50. package/build/angular/components/virtualized-list/index.js.map +1 -1
  51. package/build/angular/components/virtualized-list/list-bags.d.ts +5 -1
  52. package/build/angular/components/virtualized-list/list-bags.js +31 -19
  53. package/build/angular/components/virtualized-list/list-bags.js.map +1 -1
  54. package/build/angular/components/virtualized-list/list-effects.d.ts +2 -2
  55. package/build/angular/components/virtualized-list/list-effects.js +17 -27
  56. package/build/angular/components/virtualized-list/list-effects.js.map +1 -1
  57. package/build/angular/components/virtualized-list/list-handle.d.ts +4 -1
  58. package/build/angular/components/virtualized-list/list-handle.js +13 -3
  59. package/build/angular/components/virtualized-list/list-handle.js.map +1 -1
  60. package/build/angular/components/virtualized-list/list-inputs.d.ts +19 -5
  61. package/build/angular/components/virtualized-list/list-inputs.js +47 -4
  62. package/build/angular/components/virtualized-list/list-inputs.js.map +1 -1
  63. package/build/angular/components/virtualized-list/list-props.d.ts +9 -3
  64. package/build/angular/components/virtualized-list/list-rows.d.ts +25 -0
  65. package/build/angular/components/virtualized-list/list-rows.js +25 -0
  66. package/build/angular/components/virtualized-list/list-rows.js.map +1 -0
  67. package/build/angular/components/virtualized-list/list-styles.d.ts +4 -0
  68. package/build/angular/components/virtualized-list/list-styles.js +10 -2
  69. package/build/angular/components/virtualized-list/list-styles.js.map +1 -1
  70. package/build/angular/components/virtualized-list/list-template.d.ts +1 -1
  71. package/build/angular/components/virtualized-list/list-template.js +159 -73
  72. package/build/angular/components/virtualized-list/list-template.js.map +1 -1
  73. package/build/angular/components/virtualized-list/nested-scope.d.ts +6 -0
  74. package/build/angular/components/virtualized-list/nested-scope.js +17 -0
  75. package/build/angular/components/virtualized-list/nested-scope.js.map +1 -0
  76. package/build/angular/components/virtualized-section-list/directives.d.ts +21 -9
  77. package/build/angular/components/virtualized-section-list/directives.js +8 -28
  78. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  79. package/build/angular/components/virtualized-section-list/index.d.ts +39 -60
  80. package/build/angular/components/virtualized-section-list/index.js +67 -33
  81. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  82. package/build/angular/components/virtualized-section-list/section-cell.d.ts +29 -0
  83. package/build/angular/components/virtualized-section-list/section-cell.js +82 -0
  84. package/build/angular/components/virtualized-section-list/section-cell.js.map +1 -0
  85. package/build/angular/components/virtualized-section-list/section-list-props.d.ts +56 -0
  86. package/build/angular/components/virtualized-section-list/section-list-props.js +3 -0
  87. package/build/angular/components/virtualized-section-list/section-list-props.js.map +1 -0
  88. package/build/angular/components/virtualized-section-list/section-list-template.d.ts +1 -1
  89. package/build/angular/components/virtualized-section-list/section-list-template.js +34 -26
  90. package/build/angular/components/virtualized-section-list/section-list-template.js.map +1 -1
  91. package/build/angular/components.d.ts +7 -3
  92. package/build/angular/components.js +2 -1
  93. package/build/angular/components.js.map +1 -1
  94. package/build/angular/element-base.d.ts +17 -1
  95. package/build/angular/element-base.js +33 -1
  96. package/build/angular/element-base.js.map +1 -1
  97. package/build/angular/element-props.d.ts +7 -7
  98. package/build/angular/element-text-input-native.d.ts +27 -0
  99. package/build/angular/element-text-input-native.js +52 -0
  100. package/build/angular/element-text-input-native.js.map +1 -0
  101. package/build/angular/elements-controls.d.ts +8 -2
  102. package/build/angular/elements-controls.js +13 -1
  103. package/build/angular/elements-controls.js.map +1 -1
  104. package/build/angular/elements-layout.d.ts +20 -3
  105. package/build/angular/elements-layout.js +37 -3
  106. package/build/angular/elements-layout.js.map +1 -1
  107. package/build/angular/elements-press.d.ts +10 -3
  108. package/build/angular/elements-press.js +13 -1
  109. package/build/angular/elements-press.js.map +1 -1
  110. package/build/angular/elements.d.ts +3 -3
  111. package/build/angular/elements.js +3 -2
  112. package/build/angular/elements.js.map +1 -1
  113. package/build/angular/index.d.ts +14 -7
  114. package/build/angular/index.js +21 -25
  115. package/build/angular/index.js.map +1 -1
  116. package/build/angular/modules/animated/create-animated-component.d.ts +1 -1
  117. package/build/angular/modules/animated/create-animated-component.js +1 -1
  118. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  119. package/build/angular/modules/animated/index.d.ts +54 -41
  120. package/build/angular/modules/animated/index.js +2 -26
  121. package/build/angular/modules/animated/index.js.map +1 -1
  122. package/build/angular/modules/app-registry/index.d.ts +3 -3
  123. package/build/angular/modules/app-registry/index.js +2 -2
  124. package/build/angular/modules/app-registry/index.js.map +1 -1
  125. package/build/angular/modules/status-bar/index.d.ts +9 -5
  126. package/build/angular/modules/status-bar/index.js +21 -10
  127. package/build/angular/modules/status-bar/index.js.map +1 -1
  128. package/build/angular/primitives/index.d.ts +4 -0
  129. package/build/angular/primitives/index.js +12 -0
  130. package/build/angular/primitives/index.js.map +1 -1
  131. package/build/angular/services/color-scheme.service.d.ts +1 -1
  132. package/package.json +6 -6
  133. package/src/accessibility-inputs.ts +3 -0
  134. package/src/callback-host.ts +56 -8
  135. package/src/components/flat-list/flat-list-handle.ts +64 -0
  136. package/src/components/flat-list/flat-list-template.ts +16 -0
  137. package/src/components/flat-list/index.ts +6 -48
  138. package/src/components/image-inputs.ts +94 -0
  139. package/src/components/image-props-resolve.ts +172 -0
  140. package/src/components/image-shared.ts +26 -291
  141. package/src/components/input-accessory-view-props.ts +12 -8
  142. package/src/components/keyboard-avoiding-view/index.ts +39 -79
  143. package/src/components/layout-conformance-props.ts +8 -0
  144. package/src/components/modal/index.ts +42 -20
  145. package/src/components/refresh-control-props.ts +29 -25
  146. package/src/components/scroll-view-props.ts +84 -76
  147. package/src/components/section-list/index.ts +34 -37
  148. package/src/components/touchable-props.ts +6 -2
  149. package/src/components/virtualized-list/cell-renderer-context.ts +36 -0
  150. package/src/components/virtualized-list/directives.ts +84 -8
  151. package/src/components/virtualized-list/index.ts +118 -85
  152. package/src/components/virtualized-list/list-bags.ts +38 -19
  153. package/src/components/virtualized-list/list-effects.ts +20 -32
  154. package/src/components/virtualized-list/list-handle.ts +17 -4
  155. package/src/components/virtualized-list/list-inputs.ts +47 -0
  156. package/src/components/virtualized-list/list-props.ts +57 -50
  157. package/src/components/virtualized-list/list-rows.ts +66 -0
  158. package/src/components/virtualized-list/list-styles.ts +30 -1
  159. package/src/components/virtualized-list/list-template.ts +159 -73
  160. package/src/components/virtualized-list/nested-scope.ts +22 -0
  161. package/src/components/virtualized-section-list/directives.ts +41 -40
  162. package/src/components/virtualized-section-list/index.ts +114 -118
  163. package/src/components/virtualized-section-list/section-cell.ts +109 -0
  164. package/src/components/virtualized-section-list/section-list-props.ts +79 -0
  165. package/src/components/virtualized-section-list/section-list-template.ts +34 -26
  166. package/src/components.ts +8 -0
  167. package/src/element-base.ts +17 -0
  168. package/src/element-props.ts +35 -33
  169. package/src/element-text-input-native.ts +31 -0
  170. package/src/elements-controls.ts +9 -1
  171. package/src/elements-layout.ts +27 -0
  172. package/src/elements-press.ts +10 -2
  173. package/src/elements.ts +3 -0
  174. package/src/index.ts +72 -22
  175. package/src/modules/animated/index.ts +2 -47
  176. package/src/modules/app-registry/index.ts +3 -1
  177. package/src/modules/status-bar/index.ts +24 -11
  178. package/src/primitives/index.ts +7 -0
  179. package/src/services/color-scheme.service.ts +1 -1
@@ -12,17 +12,21 @@ import {
12
12
  ViewChild,
13
13
  inject,
14
14
  type DoCheck,
15
+ type TemplateRef,
15
16
  } from '@angular/core';
16
17
  import {
18
+ cellGapsFor,
19
+ createSeparatorBoard,
17
20
  flattenSections,
18
21
  resolveStickySectionHeaders,
19
- scrollLocationToFlatIndex,
22
+ resolveScrollLocation,
23
+ SECTION_ENTRY_KIND,
24
+ SEPARATOR_GAP_KIND,
25
+ SEPARATOR_SIDE,
20
26
  sectionEntryKey,
21
- unwrapEntryItem,
22
- type IAccessibilityProps,
23
- type IAriaProps,
24
- type ISection,
27
+ type IInnerViewRef,
25
28
  type ISectionEntry,
29
+ type ISeparatorGap,
26
30
  type ISeparators,
27
31
  type IScrollViewHandle,
28
32
  type IVirtualizedSectionListHandle,
@@ -37,12 +41,13 @@ import {
37
41
  } from '@symbiote-native/engine';
38
42
  import {
39
43
  VirtualizedList,
44
+ VListCellDirective,
40
45
  VListEmptyDirective,
41
46
  VListFooterDirective,
42
47
  VListHeaderDirective,
43
48
  VListItemDirective,
44
49
  VListSeparatorDirective,
45
- type IVListSeparatorContext,
50
+ type IVListCellContext,
46
51
  } from '../virtualized-list';
47
52
  import { VListOutletDirective } from '../virtualized-list/directives';
48
53
  import { ListEventsBase } from '../virtualized-list/list-events';
@@ -56,12 +61,18 @@ import {
56
61
  VSectionHeaderDirective,
57
62
  VSectionItemDirective,
58
63
  VSectionSeparatorDirective,
64
+ type ISection,
59
65
  type IVSectionContext,
60
66
  type IVSectionItemContext,
61
67
  } from './directives';
68
+ import { VSectionCellDirective, type ISeparatorSlot } from './section-cell';
69
+ import type {
70
+ IItemLayout,
71
+ IVirtualizedSectionListInputs,
72
+ IVirtualizedSectionListProps,
73
+ } from './section-list-props';
62
74
  import { VIRTUALIZED_SECTION_LIST_TEMPLATE } from './section-list-template';
63
75
 
64
- export type { ISection } from '@symbiote-native/components';
65
76
  export type { IVirtualizedSectionListHandle } from '@symbiote-native/components';
66
77
  export {
67
78
  VSectionFooterDirective,
@@ -69,66 +80,30 @@ export {
69
80
  VSectionItemDirective,
70
81
  VSectionSeparatorDirective,
71
82
  } from './directives';
72
- export type { IVSectionContext, IVSectionItemContext } from './directives';
73
-
74
- type IItemLayout = { length: number; offset: number; index: number };
75
-
76
- // The React and Vue surface minus element-returning props, which are `<ng-template>` here
77
- export interface IVirtualizedSectionListProps<ItemT>
78
- extends IAccessibilityProps, IAriaProps {
79
- sections: ReadonlyArray<ISection<ItemT>>;
80
- keyExtractor?: (item: ItemT, index: number) => string;
81
- // Flat like RN's: the sections array plus a flat entry index, where every section adds two rows
82
- // beyond its items (header, footer). Without it a fast scroll outruns measurement
83
- getItemLayout?: (
84
- data: ReadonlyArray<ISection<ItemT>> | null,
85
- index: number,
86
- ) => IItemLayout;
87
- // Defaults to `Platform.OS === 'ios'`, pass true or false to override
88
- stickySectionHeadersEnabled?: boolean;
89
- extraData?: unknown;
90
- onEndReached?: (info: { distanceFromEnd: number }) => void;
91
- onEndReachedThreshold?: number;
92
- onStartReached?: (info: { distanceFromStart: number }) => void;
93
- onStartReachedThreshold?: number;
94
- onRefresh?: () => void;
95
- refreshing?: boolean | null;
96
- progressViewOffset?: number;
97
- initialNumToRender?: number;
98
- initialScrollIndex?: number;
99
- maxToRenderPerBatch?: number;
100
- updateCellsBatchingPeriod?: number;
101
- windowSize?: number;
102
- inverted?: boolean;
103
- maintainVisibleContentPosition?: {
104
- minIndexForVisible: number;
105
- autoscrollToTopThreshold?: number;
106
- };
107
- onScroll?: (event: ISymbioteEvent) => void;
108
- onScrollBeginDrag?: (event: ISymbioteEvent) => void;
109
- onScrollEndDrag?: (event: ISymbioteEvent) => void;
110
- onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
111
- onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
112
- scrollEventThrottle?: number;
113
- keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
114
- keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
115
- removeClippedSubviews?: boolean;
116
- nestedScrollEnabled?: boolean;
117
- style?: IStyleProp<IViewStyle>;
118
- contentContainerStyle?: IStyleProp<IViewStyle>;
119
- }
83
+ export type {
84
+ ISection,
85
+ IVSectionContext,
86
+ IVSectionItemContext,
87
+ IVSectionSeparatorContext,
88
+ } from './directives';
89
+
90
+ type IEntry<ItemT> = ISectionEntry<ItemT, ISection<ItemT>>;
120
91
 
121
- // The plain inputs: the full surface minus the events exposed as real outputs
122
- export type IVirtualizedSectionListInputs<ItemT> = Omit<
123
- IVirtualizedSectionListProps<ItemT>,
124
- | 'onEndReached'
125
- | 'onStartReached'
126
- | 'onRefresh'
127
- | 'onAccessibilityAction'
128
- | 'onAccessibilityTap'
129
- | 'onMagicTap'
130
- | 'onAccessibilityEscape'
131
- >;
92
+ // Everything an item cell needs to paint itself, separators in painting order
93
+ export type ICellPlan<ItemT> = {
94
+ cellKey: string;
95
+ prevCellKey: string | undefined;
96
+ hasLeading: boolean;
97
+ hasTrailing: boolean;
98
+ itemTemplate: TemplateRef<IVSectionItemContext<ItemT>> | undefined;
99
+ first: ISeparatorSlot<ItemT>;
100
+ second: ISeparatorSlot<ItemT>;
101
+ };
102
+
103
+ export type {
104
+ IVirtualizedSectionListInputs,
105
+ IVirtualizedSectionListProps,
106
+ } from './section-list-props';
132
107
 
133
108
  @Component({
134
109
  selector: 'VirtualizedSectionList',
@@ -143,8 +118,8 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
143
118
  VListHeaderDirective,
144
119
  VListFooterDirective,
145
120
  VListEmptyDirective,
146
- VListSeparatorDirective,
147
121
  VListOutletDirective,
122
+ VSectionCellDirective,
148
123
  ],
149
124
  changeDetection: ChangeDetectionStrategy.OnPush,
150
125
  template: VIRTUALIZED_SECTION_LIST_TEMPLATE,
@@ -176,7 +151,9 @@ export class VirtualizedSectionList<ItemT = unknown>
176
151
  @Input() maxToRenderPerBatch?: number;
177
152
  @Input() updateCellsBatchingPeriod?: number;
178
153
  @Input() windowSize?: number;
154
+ @Input() disableVirtualization?: boolean;
179
155
  @Input() inverted?: boolean;
156
+ @Input() horizontal?: boolean;
180
157
  @Input() maintainVisibleContentPosition?: {
181
158
  minIndexForVisible: number;
182
159
  autoscrollToTopThreshold?: number;
@@ -186,13 +163,18 @@ export class VirtualizedSectionList<ItemT = unknown>
186
163
  @Input() onScrollEndDrag?: (event: ISymbioteEvent) => void;
187
164
  @Input() onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
188
165
  @Input() onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
166
+ @Input() onContentSizeChange?: (width: number, height: number) => void;
189
167
  @Input() scrollEventThrottle?: number;
190
168
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
191
169
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
192
170
  @Input() removeClippedSubviews?: boolean;
193
171
  @Input() nestedScrollEnabled?: boolean;
172
+ @Input() stickyHeaderHiddenOnScroll?: boolean;
173
+ @Input() innerViewRef?: IInnerViewRef;
194
174
  @Input() style?: IStyleProp<IViewStyle>;
195
175
  @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
176
+ @Input() listHeaderComponentStyle?: IStyleProp<IViewStyle>;
177
+ @Input() listFooterComponentStyle?: IStyleProp<IViewStyle>;
196
178
  @Input() testID?: string;
197
179
  @Input() nativeID?: string;
198
180
 
@@ -216,7 +198,7 @@ export class VirtualizedSectionList<ItemT = unknown>
216
198
  @ContentChild(VSectionFooterDirective)
217
199
  sectionFooterDir?: VSectionFooterDirective<ItemT>;
218
200
  @ContentChild(VSectionSeparatorDirective)
219
- sectionSeparatorDir?: VSectionSeparatorDirective;
201
+ sectionSeparatorDir?: VSectionSeparatorDirective<ItemT>;
220
202
 
221
203
  // List-level slots reuse the `VirtualizedList` directives and are forwarded to the inner list
222
204
  @ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
@@ -224,41 +206,38 @@ export class VirtualizedSectionList<ItemT = unknown>
224
206
  @ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
225
207
  @ContentChild(VListSeparatorDirective)
226
208
  itemSeparatorDir?: VListSeparatorDirective<ItemT>;
209
+ // The cell wrapper, from projection or handed in by `SectionList`, the item is the list's entry
210
+ @ContentChild(VListCellDirective) cellDir?: VListCellDirective;
211
+ @Input() cellRendererTemplate?: TemplateRef<IVListCellContext>;
212
+
213
+ get cellRendererTpl(): TemplateRef<IVListCellContext> | undefined {
214
+ return this.cellRendererTemplate ?? this.cellDir?.templateRef;
215
+ }
227
216
 
228
217
  // The inner list, whose instance is the scroll handle, reads lazily and no-ops before commit
229
- @ViewChild(VirtualizedList) private list?: VirtualizedList<
230
- ISectionEntry<ItemT>
231
- >;
218
+ @ViewChild(VirtualizedList) private list?: VirtualizedList<IEntry<ItemT>>;
232
219
 
233
220
  // This component's own host, the anchor `class="..."` resolves onto, not the inner list's
234
221
  private readonly elementRef = inject(ElementRef);
235
222
 
236
- flatEntries: ISectionEntry<ItemT>[] = [];
223
+ flatEntries: IEntry<ItemT>[] = [];
237
224
  stickyHeaderIndices: number[] | undefined = undefined;
225
+ readonly board = createSeparatorBoard<Record<string, unknown>>();
238
226
 
239
227
  // Flat positions of every section header, so `scrollToLocation` skips re-deriving the layout
240
228
  private headerIndices: number[] = [];
241
- // Memo guards, the inner list marks for check on every scroll tick and re-runs `ngDoCheck`
229
+ // Memo guard, the inner list marks for check on every scroll tick and re-runs `ngDoCheck`
242
230
  private lastSectionsRef: ReadonlyArray<ISection<ItemT>> | null = null;
243
- private lastHasSectionSeparator = false;
244
231
 
245
232
  private readonly cdr = inject(ChangeDetectorRef);
246
233
 
247
234
  ngDoCheck(): void {
248
- const hasSectionSeparator = this.sectionSeparatorDir !== undefined;
249
- if (
250
- this.sections === this.lastSectionsRef &&
251
- hasSectionSeparator === this.lastHasSectionSeparator
252
- ) {
253
- return;
254
- }
235
+ if (this.sections === this.lastSectionsRef) return;
255
236
  this.lastSectionsRef = this.sections;
256
- this.lastHasSectionSeparator = hasSectionSeparator;
257
237
 
258
238
  const sections = this.sections ?? [];
259
- const { entries, headerIndices } = flattenSections(
239
+ const { entries, headerIndices } = flattenSections<ItemT, ISection<ItemT>>(
260
240
  sections,
261
- hasSectionSeparator,
262
241
  );
263
242
  this.flatEntries = entries;
264
243
  this.headerIndices = headerIndices;
@@ -277,14 +256,13 @@ export class VirtualizedSectionList<ItemT = unknown>
277
256
  }
278
257
 
279
258
  // Stable arrow fields, so the bindings to the inner list keep a constant identity
280
- getEntry = (_source: unknown, index: number): ISectionEntry<ItemT> =>
259
+ getEntry = (_source: unknown, index: number): IEntry<ItemT> =>
281
260
  this.flatEntries[index];
282
261
  getEntryCount = (): number => this.flatEntries.length;
283
- entryKeyExtractor = (entry: ISectionEntry<ItemT>, index: number): string =>
284
- sectionEntryKey(entry, index, this.keyExtractor);
262
+ entryKeyExtractor = (entry: IEntry<ItemT>): string =>
263
+ sectionEntryKey(entry, this.keyExtractor);
285
264
 
286
- // RN hands the callback `sections`, not the flattened entries. The flat index matches RN's only
287
- // while no section separator template is set, which adds one row per boundary here
265
+ // RN hands the callback `sections`, not the flattened entries
288
266
  // Cached on the input's identity, the inner list folds it into its dedup array
289
267
  private cachedEntryItemLayout?: (data: unknown, index: number) => IItemLayout;
290
268
  private lastGetItemLayout?: IVirtualizedSectionListProps<ItemT>['getItemLayout'];
@@ -323,11 +301,11 @@ export class VirtualizedSectionList<ItemT = unknown>
323
301
  }
324
302
 
325
303
  // The inner cell context is typed `unknown` under strict templates, so entries are narrowed
326
- private isEntry(value: unknown): value is ISectionEntry<ItemT> {
304
+ private isEntry(value: unknown): value is IEntry<ItemT> {
327
305
  return typeof value === 'object' && value !== null && 'kind' in value;
328
306
  }
329
307
 
330
- entryKind(value: unknown): ISectionEntry<ItemT>['kind'] | 'unknown' {
308
+ entryKind(value: unknown): IEntry<ItemT>['kind'] | 'unknown' {
331
309
  return this.isEntry(value) ? value.kind : 'unknown';
332
310
  }
333
311
 
@@ -351,22 +329,37 @@ export class VirtualizedSectionList<ItemT = unknown>
351
329
  };
352
330
  }
353
331
 
354
- // The inner separator context carries flattened entries, unwrapped back to real items so the
355
- // app's separator, typed on the item, never sees an envelope
356
- itemSeparatorContextOf(
357
- highlighted: unknown,
358
- leadingItem: unknown,
359
- trailingItem: unknown,
360
- ): IVListSeparatorContext<ItemT> {
332
+ // The separator template for a gap: the section one, or the item one where a section's own
333
+ // template beats the list's
334
+ private slotFor(
335
+ gap: ISeparatorGap<ItemT, ISection<ItemT>>,
336
+ side: ISeparatorSlot<ItemT>['side'],
337
+ ): ISeparatorSlot<ItemT> {
338
+ if (gap.props === undefined) return { template: undefined, gap, side };
339
+ const template =
340
+ gap.kind === SEPARATOR_GAP_KIND.section
341
+ ? this.sectionSeparatorDir?.templateRef
342
+ : (gap.props.section.separator ?? this.itemSeparatorDir?.templateRef);
343
+ return { template, gap, side };
344
+ }
345
+
346
+ cellPlanOf(value: unknown, index: number): ICellPlan<ItemT> | undefined {
347
+ if (!this.isEntry(value) || value.kind !== SECTION_ENTRY_KIND.item)
348
+ return undefined;
349
+ const gaps = cellGapsFor(this.flatEntries, index);
350
+ const leading = this.slotFor(gaps.leading, SEPARATOR_SIDE.leading);
351
+ const trailing = this.slotFor(gaps.trailing, SEPARATOR_SIDE.trailing);
352
+ const previous = this.flatEntries[index - 1];
353
+ const isInverted = this.inverted === true;
361
354
  return {
362
- $implicit: highlighted === true,
363
- highlighted: highlighted === true,
364
- leadingItem: this.isEntry(leadingItem)
365
- ? unwrapEntryItem(leadingItem)
366
- : undefined,
367
- trailingItem: this.isEntry(trailingItem)
368
- ? unwrapEntryItem(trailingItem)
369
- : undefined,
355
+ cellKey: this.entryKeyExtractor(value),
356
+ prevCellKey:
357
+ previous === undefined ? undefined : this.entryKeyExtractor(previous),
358
+ hasLeading: leading.template !== undefined,
359
+ hasTrailing: trailing.template !== undefined,
360
+ itemTemplate: value.section.item ?? this.sectionItemDir?.templateRef,
361
+ first: isInverted ? trailing : leading,
362
+ second: isInverted ? leading : trailing,
370
363
  };
371
364
  }
372
365
 
@@ -377,12 +370,12 @@ export class VirtualizedSectionList<ItemT = unknown>
377
370
  viewPosition?: number;
378
371
  animated?: boolean;
379
372
  }): void {
380
- const flatIndex = scrollLocationToFlatIndex(
373
+ const target = resolveScrollLocation(
381
374
  this.headerIndices,
382
- params.sectionIndex,
383
- params.itemIndex,
375
+ this.stickyHeaderIndices,
376
+ params,
384
377
  );
385
- if (flatIndex === undefined) {
378
+ if (target === undefined) {
386
379
  dlog(
387
380
  `Angular VirtualizedSectionList scrollToLocation: section ${params.sectionIndex} out of range`,
388
381
  );
@@ -390,21 +383,16 @@ export class VirtualizedSectionList<ItemT = unknown>
390
383
  }
391
384
  dlog(
392
385
  `Angular VirtualizedSectionList scrollToLocation section=${params.sectionIndex} ` +
393
- `item=${params.itemIndex} -> flat ${flatIndex}`,
386
+ `item=${params.itemIndex} -> flat ${target.index}`,
394
387
  );
395
- this.list?.scrollToIndex({
396
- index: flatIndex,
397
- viewOffset: params.viewOffset,
398
- viewPosition: params.viewPosition,
399
- animated: params.animated,
400
- });
388
+ this.list?.scrollToIndex(target);
401
389
  }
402
390
 
403
391
  flashScrollIndicators(): void {
404
392
  this.list?.flashScrollIndicators();
405
393
  }
406
394
 
407
- getNativeScrollRef(): IScrollViewHandle | null {
395
+ getNativeScrollRef(): ISymbioteNode | null {
408
396
  return this.list?.getNativeScrollRef() ?? null;
409
397
  }
410
398
 
@@ -420,7 +408,15 @@ export class VirtualizedSectionList<ItemT = unknown>
420
408
  return this.list?.getScrollNode() ?? null;
421
409
  }
422
410
 
411
+ getScrollRef(): ISymbioteNode | null {
412
+ return this.list?.getScrollRef() ?? null;
413
+ }
414
+
423
415
  recordInteraction(): void {
424
416
  this.list?.recordInteraction();
425
417
  }
418
+
419
+ setNativeProps(props: Record<string, unknown>): void {
420
+ this.list?.setNativeProps(props);
421
+ }
426
422
  }
@@ -0,0 +1,109 @@
1
+ // An item cell with its separators around it, as in RN's `ItemWithSeparator`
2
+ // The state of those separators lives in a board shared by all cells, т.к. `highlight()` also
3
+ // lights the previous cell's one, this directive ties one cell to its key in that board
4
+
5
+ import {
6
+ ChangeDetectorRef,
7
+ Directive,
8
+ Input,
9
+ inject,
10
+ type OnChanges,
11
+ type OnDestroy,
12
+ type SimpleChanges,
13
+ type TemplateRef,
14
+ } from '@angular/core';
15
+ import {
16
+ createCellSeparators,
17
+ SEPARATOR_SIDE,
18
+ type ICellSeparatorState,
19
+ type ISeparatorBoard,
20
+ type ISeparatorGap,
21
+ type ISeparators,
22
+ type ISeparatorSide,
23
+ } from '@symbiote-native/components';
24
+ import type { IVListSeparatorContext } from '../virtualized-list';
25
+ import type { ISection } from './directives';
26
+
27
+ export type ISeparatorSlot<ItemT> = {
28
+ template: TemplateRef<IVListSeparatorContext<ItemT>> | undefined;
29
+ gap: ISeparatorGap<ItemT, ISection<ItemT>>;
30
+ side: ISeparatorSide;
31
+ };
32
+
33
+ // The separator props RN passes, the app's `updateProps` overrides laid over them
34
+ export function separatorContextOf<ItemT>(
35
+ slot: ISeparatorSlot<ItemT>,
36
+ state: ICellSeparatorState<Record<string, unknown>>,
37
+ ): IVListSeparatorContext<ItemT> | undefined {
38
+ if (slot.template === undefined || slot.gap.props === undefined)
39
+ return undefined;
40
+ const isLeading = slot.side === SEPARATOR_SIDE.leading;
41
+ const override = isLeading ? state.leadingOverride : state.trailingOverride;
42
+ const isHighlighted = isLeading
43
+ ? state.leadingHighlighted
44
+ : state.trailingHighlighted;
45
+ const bag: IVListSeparatorContext<ItemT> = {
46
+ $implicit: isHighlighted,
47
+ highlighted: isHighlighted,
48
+ ...slot.gap.props,
49
+ ...override,
50
+ };
51
+ return { ...bag, props: bag };
52
+ }
53
+
54
+ @Directive({
55
+ selector: '[vSectionCell]',
56
+ exportAs: 'vSectionCell',
57
+ standalone: true,
58
+ })
59
+ export class VSectionCellDirective implements OnChanges, OnDestroy {
60
+ @Input({ required: true }) board!: ISeparatorBoard<Record<string, unknown>>;
61
+ @Input({ required: true }) cellKey!: string;
62
+ @Input() prevCellKey?: string;
63
+ @Input() hasLeading = false;
64
+ @Input() hasTrailing = false;
65
+
66
+ private readonly cdr = inject(ChangeDetectorRef);
67
+ private unsubscribe: (() => void) | undefined;
68
+ private subscribedKey: string | undefined;
69
+
70
+ // Built on first read, once the inputs are set, and stable so a template holding it keeps working
71
+ private cachedSeparators: ISeparators | undefined;
72
+ get separators(): ISeparators {
73
+ this.cachedSeparators ??= createCellSeparators(this.board, () => ({
74
+ cellKey: this.cellKey,
75
+ prevCellKey: this.prevCellKey,
76
+ has: { leading: this.hasLeading, trailing: this.hasTrailing },
77
+ }));
78
+ return this.cachedSeparators;
79
+ }
80
+
81
+ contextOf<ItemT>(
82
+ slot: ISeparatorSlot<ItemT>,
83
+ ): IVListSeparatorContext<ItemT> | undefined {
84
+ return separatorContextOf(slot, this.board.read(this.cellKey));
85
+ }
86
+
87
+ ngOnChanges(changes: SimpleChanges): void {
88
+ if (changes['cellKey'] === undefined && changes['board'] === undefined)
89
+ return;
90
+ this.detach();
91
+ this.subscribedKey = this.cellKey;
92
+ this.unsubscribe = this.board.subscribe(this.cellKey, () =>
93
+ this.cdr.markForCheck(),
94
+ );
95
+ }
96
+
97
+ ngOnDestroy(): void {
98
+ this.detach();
99
+ }
100
+
101
+ // A recycled cell starts clean, as a remounted RN cell does
102
+ private detach(): void {
103
+ this.unsubscribe?.();
104
+ if (this.subscribedKey !== undefined)
105
+ this.board.release(this.subscribedKey);
106
+ this.unsubscribe = undefined;
107
+ this.subscribedKey = undefined;
108
+ }
109
+ }
@@ -0,0 +1,79 @@
1
+ // The public prop surface of the Angular VirtualizedSectionList, apart from the class
2
+
3
+ import type {
4
+ IAccessibilityProps,
5
+ IAriaProps,
6
+ IInnerViewRef,
7
+ } from '@symbiote-native/components';
8
+ import type {
9
+ IStyleProp,
10
+ ISymbioteEvent,
11
+ IViewStyle,
12
+ } from '@symbiote-native/engine';
13
+ import type { ISection } from './directives';
14
+
15
+ export type IItemLayout = { length: number; offset: number; index: number };
16
+
17
+ // The React and Vue surface minus element-returning props, which are `<ng-template>` here
18
+ export type IVirtualizedSectionListProps<ItemT> = IAccessibilityProps &
19
+ IAriaProps & {
20
+ sections: ReadonlyArray<ISection<ItemT>>;
21
+ keyExtractor?: (item: ItemT, index: number) => string;
22
+ // Flat like RN's: the sections array plus a flat entry index, where every section adds two rows
23
+ // beyond its items (header, footer). Without it a fast scroll outruns measurement
24
+ getItemLayout?: (
25
+ data: ReadonlyArray<ISection<ItemT>> | null,
26
+ index: number,
27
+ ) => IItemLayout;
28
+ // Defaults to `Platform.OS === 'ios'`, pass true or false to override
29
+ stickySectionHeadersEnabled?: boolean;
30
+ extraData?: unknown;
31
+ onEndReached?: (info: { distanceFromEnd: number }) => void;
32
+ onEndReachedThreshold?: number;
33
+ onStartReached?: (info: { distanceFromStart: number }) => void;
34
+ onStartReachedThreshold?: number;
35
+ onRefresh?: () => void;
36
+ refreshing?: boolean | null;
37
+ progressViewOffset?: number;
38
+ initialNumToRender?: number;
39
+ initialScrollIndex?: number;
40
+ maxToRenderPerBatch?: number;
41
+ updateCellsBatchingPeriod?: number;
42
+ windowSize?: number;
43
+ disableVirtualization?: boolean;
44
+ inverted?: boolean;
45
+ horizontal?: boolean;
46
+ maintainVisibleContentPosition?: {
47
+ minIndexForVisible: number;
48
+ autoscrollToTopThreshold?: number;
49
+ };
50
+ onScroll?: (event: ISymbioteEvent) => void;
51
+ onScrollBeginDrag?: (event: ISymbioteEvent) => void;
52
+ onScrollEndDrag?: (event: ISymbioteEvent) => void;
53
+ onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
54
+ onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
55
+ onContentSizeChange?: (width: number, height: number) => void;
56
+ scrollEventThrottle?: number;
57
+ keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
58
+ keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
59
+ removeClippedSubviews?: boolean;
60
+ nestedScrollEnabled?: boolean;
61
+ stickyHeaderHiddenOnScroll?: boolean;
62
+ innerViewRef?: IInnerViewRef;
63
+ style?: IStyleProp<IViewStyle>;
64
+ contentContainerStyle?: IStyleProp<IViewStyle>;
65
+ listHeaderComponentStyle?: IStyleProp<IViewStyle>;
66
+ listFooterComponentStyle?: IStyleProp<IViewStyle>;
67
+ };
68
+
69
+ // The plain inputs: the full surface minus the events exposed as real outputs
70
+ export type IVirtualizedSectionListInputs<ItemT> = Omit<
71
+ IVirtualizedSectionListProps<ItemT>,
72
+ | 'onEndReached'
73
+ | 'onStartReached'
74
+ | 'onRefresh'
75
+ | 'onAccessibilityAction'
76
+ | 'onAccessibilityTap'
77
+ | 'onMagicTap'
78
+ | 'onAccessibilityEscape'
79
+ >;