@symbiote-native/angular 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) 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 +15 -7
  114. package/build/angular/index.js +22 -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/render/index.d.ts +2 -2
  132. package/build/angular/render/index.js +10 -29
  133. package/build/angular/render/index.js.map +1 -1
  134. package/build/angular/root-tag.d.ts +4 -0
  135. package/build/angular/root-tag.js +10 -0
  136. package/build/angular/root-tag.js.map +1 -0
  137. package/build/angular/services/color-scheme.service.d.ts +1 -1
  138. package/package.json +6 -6
  139. package/src/accessibility-inputs.ts +3 -0
  140. package/src/callback-host.ts +56 -8
  141. package/src/components/flat-list/flat-list-handle.ts +64 -0
  142. package/src/components/flat-list/flat-list-template.ts +16 -0
  143. package/src/components/flat-list/index.ts +6 -48
  144. package/src/components/image-inputs.ts +94 -0
  145. package/src/components/image-props-resolve.ts +172 -0
  146. package/src/components/image-shared.ts +26 -291
  147. package/src/components/input-accessory-view-props.ts +12 -8
  148. package/src/components/keyboard-avoiding-view/index.ts +39 -79
  149. package/src/components/layout-conformance-props.ts +8 -0
  150. package/src/components/modal/index.ts +42 -20
  151. package/src/components/refresh-control-props.ts +29 -25
  152. package/src/components/scroll-view-props.ts +84 -76
  153. package/src/components/section-list/index.ts +34 -37
  154. package/src/components/touchable-props.ts +6 -2
  155. package/src/components/virtualized-list/cell-renderer-context.ts +36 -0
  156. package/src/components/virtualized-list/directives.ts +84 -8
  157. package/src/components/virtualized-list/index.ts +118 -85
  158. package/src/components/virtualized-list/list-bags.ts +38 -19
  159. package/src/components/virtualized-list/list-effects.ts +20 -32
  160. package/src/components/virtualized-list/list-handle.ts +17 -4
  161. package/src/components/virtualized-list/list-inputs.ts +47 -0
  162. package/src/components/virtualized-list/list-props.ts +57 -50
  163. package/src/components/virtualized-list/list-rows.ts +66 -0
  164. package/src/components/virtualized-list/list-styles.ts +30 -1
  165. package/src/components/virtualized-list/list-template.ts +159 -73
  166. package/src/components/virtualized-list/nested-scope.ts +22 -0
  167. package/src/components/virtualized-section-list/directives.ts +41 -40
  168. package/src/components/virtualized-section-list/index.ts +114 -118
  169. package/src/components/virtualized-section-list/section-cell.ts +109 -0
  170. package/src/components/virtualized-section-list/section-list-props.ts +79 -0
  171. package/src/components/virtualized-section-list/section-list-template.ts +34 -26
  172. package/src/components.ts +8 -0
  173. package/src/element-base.ts +17 -0
  174. package/src/element-props.ts +35 -33
  175. package/src/element-text-input-native.ts +31 -0
  176. package/src/elements-controls.ts +9 -1
  177. package/src/elements-layout.ts +27 -0
  178. package/src/elements-press.ts +10 -2
  179. package/src/elements.ts +3 -0
  180. package/src/index.ts +79 -22
  181. package/src/modules/animated/index.ts +2 -47
  182. package/src/modules/app-registry/index.ts +3 -1
  183. package/src/modules/status-bar/index.ts +24 -11
  184. package/src/primitives/index.ts +7 -0
  185. package/src/render/index.ts +23 -50
  186. package/src/root-tag.ts +14 -0
  187. package/src/services/color-scheme.service.ts +1 -1
@@ -12,8 +12,8 @@ import {
12
12
  inject,
13
13
  } from '@angular/core';
14
14
  import type {
15
+ IInnerViewRef,
15
16
  IScrollViewHandle,
16
- ISection,
17
17
  IVirtualizedSectionListHandle,
18
18
  } from '@symbiote-native/components';
19
19
  import {
@@ -23,11 +23,11 @@ import {
23
23
  type IViewStyle,
24
24
  } from '@symbiote-native/engine';
25
25
  import {
26
+ VListCellDirective,
26
27
  VListEmptyDirective,
27
28
  VListFooterDirective,
28
29
  VListHeaderDirective,
29
30
  VListSeparatorDirective,
30
- type IVListSeparatorContext,
31
31
  } from '../virtualized-list';
32
32
  import { VListOutletDirective } from '../virtualized-list/directives';
33
33
  import { LIST_ACCESSIBILITY_FORWARD } from '../virtualized-list/list-a11y-forward';
@@ -43,16 +43,11 @@ import {
43
43
  VSectionItemDirective,
44
44
  VSectionSeparatorDirective,
45
45
  VirtualizedSectionList,
46
+ type ISection,
46
47
  type IVirtualizedSectionListProps,
47
48
  } from '../virtualized-section-list';
48
49
 
49
- // The separator context arrives typed `unknown`, the directive is matched structurally in the
50
- // template with no type argument to pin the item type. The narrowest cast for that gap
51
- function asItem<ItemT>(value: unknown): ItemT | undefined {
52
- return value as ItemT | undefined;
53
- }
54
-
55
- export type { ISection } from '@symbiote-native/components';
50
+ export type { ISection } from '../virtualized-section-list';
56
51
  export type ISectionListHandle = IVirtualizedSectionListHandle;
57
52
 
58
53
  // Re-exported so app code gets the whole `vSection*` and `vList*` authoring surface from here
@@ -124,20 +119,28 @@ export type ISectionListInputs<ItemT> = Omit<
124
119
  [maxToRenderPerBatch]="maxToRenderPerBatch"
125
120
  [updateCellsBatchingPeriod]="updateCellsBatchingPeriod"
126
121
  [windowSize]="windowSize"
122
+ [disableVirtualization]="disableVirtualization"
127
123
  [inverted]="inverted"
124
+ [horizontal]="horizontal"
128
125
  [maintainVisibleContentPosition]="maintainVisibleContentPosition"
129
126
  [onScroll]="onScroll"
130
127
  [onScrollBeginDrag]="onScrollBeginDrag"
131
128
  [onScrollEndDrag]="onScrollEndDrag"
132
129
  [onMomentumScrollBegin]="onMomentumScrollBegin"
133
130
  [onMomentumScrollEnd]="onMomentumScrollEnd"
131
+ [onContentSizeChange]="onContentSizeChange"
134
132
  [scrollEventThrottle]="scrollEventThrottle"
135
133
  [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
136
134
  [keyboardDismissMode]="keyboardDismissMode"
137
135
  [removeClippedSubviews]="removeClippedSubviews"
138
136
  [nestedScrollEnabled]="nestedScrollEnabled"
137
+ [stickyHeaderHiddenOnScroll]="stickyHeaderHiddenOnScroll"
138
+ [innerViewRef]="innerViewRef"
139
139
  [style]="resolvedStyle"
140
140
  [contentContainerStyle]="contentContainerStyle"
141
+ [listHeaderComponentStyle]="listHeaderComponentStyle"
142
+ [listFooterComponentStyle]="listFooterComponentStyle"
143
+ [cellRendererTemplate]="cellDir?.templateRef"
141
144
  [testID]="testID"
142
145
  [nativeID]="nativeID"
143
146
  ${LIST_ACCESSIBILITY_FORWARD}
@@ -177,9 +180,10 @@ export type ISectionListInputs<ItemT> = Omit<
177
180
  </ng-template>
178
181
  }
179
182
  @if (sectionSeparatorDir !== undefined) {
180
- <ng-template vSectionSeparator>
183
+ <ng-template vSectionSeparator let-props="props">
181
184
  <ng-container
182
185
  [vListOutlet]="sectionSeparatorDir.templateRef"
186
+ [vListOutletContext]="props"
183
187
  ></ng-container>
184
188
  </ng-template>
185
189
  }
@@ -203,17 +207,10 @@ export type ISectionListInputs<ItemT> = Omit<
203
207
  </ng-template>
204
208
  }
205
209
  @if (itemSeparatorDir !== undefined) {
206
- <ng-template
207
- vListSeparator
208
- let-highlighted="highlighted"
209
- let-leadingItem="leadingItem"
210
- let-trailingItem="trailingItem"
211
- >
210
+ <ng-template vListSeparator let-props="props">
212
211
  <ng-container
213
212
  [vListOutlet]="itemSeparatorDir.templateRef"
214
- [vListOutletContext]="
215
- itemSeparatorContext(highlighted, leadingItem, trailingItem)
216
- "
213
+ [vListOutletContext]="props"
217
214
  ></ng-container>
218
215
  </ng-template>
219
216
  }
@@ -242,7 +239,9 @@ export class SectionList<ItemT = unknown>
242
239
  @Input() maxToRenderPerBatch?: number;
243
240
  @Input() updateCellsBatchingPeriod?: number;
244
241
  @Input() windowSize?: number;
242
+ @Input() disableVirtualization?: boolean;
245
243
  @Input() inverted?: boolean;
244
+ @Input() horizontal?: boolean;
246
245
  @Input() maintainVisibleContentPosition?: {
247
246
  minIndexForVisible: number;
248
247
  autoscrollToTopThreshold?: number;
@@ -252,13 +251,18 @@ export class SectionList<ItemT = unknown>
252
251
  @Input() onScrollEndDrag?: (event: ISymbioteEvent) => void;
253
252
  @Input() onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
254
253
  @Input() onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
254
+ @Input() onContentSizeChange?: (width: number, height: number) => void;
255
255
  @Input() scrollEventThrottle?: number;
256
256
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
257
257
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
258
258
  @Input() removeClippedSubviews?: boolean;
259
259
  @Input() nestedScrollEnabled?: boolean;
260
+ @Input() stickyHeaderHiddenOnScroll?: boolean;
261
+ @Input() innerViewRef?: IInnerViewRef;
260
262
  @Input() style?: IStyleProp<IViewStyle>;
261
263
  @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
264
+ @Input() listHeaderComponentStyle?: IStyleProp<IViewStyle>;
265
+ @Input() listFooterComponentStyle?: IStyleProp<IViewStyle>;
262
266
  @Input() testID?: string;
263
267
  @Input() nativeID?: string;
264
268
 
@@ -282,12 +286,13 @@ export class SectionList<ItemT = unknown>
282
286
  @ContentChild(VSectionFooterDirective)
283
287
  sectionFooterDir?: VSectionFooterDirective<ItemT>;
284
288
  @ContentChild(VSectionSeparatorDirective)
285
- sectionSeparatorDir?: VSectionSeparatorDirective;
289
+ sectionSeparatorDir?: VSectionSeparatorDirective<ItemT>;
286
290
  @ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
287
291
  @ContentChild(VListFooterDirective) listFooterDir?: VListFooterDirective;
288
292
  @ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
289
293
  @ContentChild(VListSeparatorDirective)
290
294
  itemSeparatorDir?: VListSeparatorDirective<ItemT>;
295
+ @ContentChild(VListCellDirective) cellDir?: VListCellDirective;
291
296
 
292
297
  // The inner list, whose instance is the scroll handle, reads lazily
293
298
  @ViewChild(VirtualizedSectionList)
@@ -296,22 +301,6 @@ export class SectionList<ItemT = unknown>
296
301
  // This component's own host, the anchor `class="..."` resolves onto, not the inner list's
297
302
  private readonly elementRef = inject(ElementRef);
298
303
 
299
- // The leading and trailing items arrive `unknown` from the template's `let` bindings, already
300
- // unwrapped to real items
301
- itemSeparatorContext(
302
- highlighted: unknown,
303
- leadingItem: unknown,
304
- trailingItem: unknown,
305
- ): IVListSeparatorContext<ItemT> {
306
- const isHighlighted = highlighted === true;
307
- return {
308
- $implicit: isHighlighted,
309
- highlighted: isHighlighted,
310
- leadingItem: asItem<ItemT>(leadingItem),
311
- trailingItem: asItem<ItemT>(trailingItem),
312
- };
313
- }
314
-
315
304
  scrollToLocation(params: {
316
305
  sectionIndex: number;
317
306
  itemIndex: number;
@@ -326,7 +315,7 @@ export class SectionList<ItemT = unknown>
326
315
  this.list?.flashScrollIndicators();
327
316
  }
328
317
 
329
- getNativeScrollRef(): IScrollViewHandle | null {
318
+ getNativeScrollRef(): ISymbioteNode | null {
330
319
  return this.list?.getNativeScrollRef() ?? null;
331
320
  }
332
321
 
@@ -342,7 +331,15 @@ export class SectionList<ItemT = unknown>
342
331
  return this.list?.getScrollNode() ?? null;
343
332
  }
344
333
 
334
+ getScrollRef(): ISymbioteNode | null {
335
+ return this.list?.getScrollRef() ?? null;
336
+ }
337
+
345
338
  recordInteraction(): void {
346
339
  this.list?.recordInteraction();
347
340
  }
341
+
342
+ setNativeProps(props: Record<string, unknown>): void {
343
+ this.list?.setNativeProps(props);
344
+ }
348
345
  }
@@ -9,7 +9,11 @@
9
9
  // <prop_types_split_agnostic_vs_per_adapter> describes. Mirrors React/Vue's ITouchableBaseProps.
10
10
 
11
11
  import type { IPressTimingProps } from '@symbiote-native/components';
12
- import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
12
+ import type {
13
+ IColorValue,
14
+ IStyleProp,
15
+ IViewStyle,
16
+ } from '@symbiote-native/engine';
13
17
 
14
18
  import type { IAngularPressableInputs } from './pressable-props';
15
19
 
@@ -24,7 +28,7 @@ export type IAngularTouchableOpacityProps = IAngularTouchableBaseProps & {
24
28
 
25
29
  export type IAngularTouchableHighlightProps = IAngularTouchableBaseProps & {
26
30
  activeOpacity?: number;
27
- underlayColor?: string;
31
+ underlayColor?: IColorValue;
28
32
  // RN's snapshot affordance (`Pressable.js:151`, `TouchableHighlight.js:61`): render the control in
29
33
  // its pressed state with no gesture, so a test can capture it. Consumed by the engine and stripped
30
34
  // before the payload — no ViewConfig declares it.
@@ -0,0 +1,36 @@
1
+ // The context a `vListCell` template reads for one cell: RN's `CellRendererComponent` props plus
2
+ // the template that stamps the item and its separator inside the wrapper the app draws
3
+
4
+ import type { TemplateRef } from '@angular/core';
5
+ import type { IViewStyle } from '@symbiote-native/engine';
6
+ import type { IWindowCell } from './cell-registry';
7
+ import type { IVListCellContext } from './directives';
8
+
9
+ export type ICellRendererArgs<ItemT> = {
10
+ cell: IWindowCell<ItemT>;
11
+ style: IViewStyle | undefined;
12
+ // The list's own template holding the cell's item and separator outlets
13
+ body: TemplateRef<unknown> | undefined;
14
+ layout: (
15
+ measure: IWindowCell<ItemT>['measure'],
16
+ event: unknown,
17
+ index: number,
18
+ ) => void;
19
+ focus: (index: number) => void;
20
+ };
21
+
22
+ export function buildCellRendererContext<ItemT>(
23
+ args: ICellRendererArgs<ItemT>,
24
+ ): IVListCellContext {
25
+ const { cell, style, body, layout, focus } = args;
26
+ return {
27
+ $implicit: cell.context.$implicit,
28
+ cellKey: cell.key,
29
+ index: cell.index,
30
+ style,
31
+ layout: event => layout(cell.measure, event, cell.index),
32
+ focus: () => focus(cell.index),
33
+ content: body,
34
+ contentContext: { $implicit: cell },
35
+ };
36
+ }
@@ -30,36 +30,60 @@ import {
30
30
  TemplateRef,
31
31
  ViewContainerRef,
32
32
  inject,
33
+ reflectComponentType,
34
+ type ComponentRef,
33
35
  type EmbeddedViewRef,
34
36
  type OnChanges,
35
37
  type OnDestroy,
36
38
  type SimpleChanges,
39
+ type Type,
37
40
  } from '@angular/core';
38
- import { dlog, isDebug } from '@symbiote-native/engine';
41
+ import { dlog, isDebug, type IViewStyle } from '@symbiote-native/engine';
39
42
  import { countAngular } from '../../diagnostics';
40
43
  import type { ISeparators } from '@symbiote-native/components';
41
44
 
42
45
  let vListOutletInstanceCounter = 0;
43
46
 
47
+ function nextOutletInstanceId(): number {
48
+ vListOutletInstanceCounter += 1;
49
+ return vListOutletInstanceCounter;
50
+ }
51
+
44
52
  // The context a `vListItem` template receives, mirroring RN's renderItem info arg
45
53
  // ({ item, index, separators }). `$implicit` is the item, so `let-item` (no key) binds it.
46
- export interface IVListItemContext<ItemT> {
54
+ export type IVListItemContext<ItemT> = {
47
55
  $implicit: ItemT;
48
56
  index: number;
49
57
  separators: ISeparators;
50
- }
58
+ };
51
59
 
52
60
  // The context a `vListSeparator` template receives, mirroring RN's ItemSeparatorComponent props
53
61
  // (the highlight flag the cell toggles + the items flanking the gap). `$implicit` is the highlight
54
62
  // flag, so `let-highlighted` binds it.
55
- export interface IVListSeparatorContext<ItemT> {
63
+ export type IVListSeparatorContext<ItemT> = {
56
64
  $implicit: boolean;
57
65
  highlighted: boolean;
58
66
  leadingItem?: ItemT;
59
67
  trailingItem?: ItemT;
60
68
  // RN lets a row drive arbitrary separator props via separators.updateProps; they ride here.
61
69
  [key: string]: unknown;
62
- }
70
+ };
71
+
72
+ // The context a `vListCell` template receives, RN's `CellRendererComponent` props
73
+ // The template draws the wrapper and stamps the item and its separator through `content`
74
+ // The item is `unknown`, a wrapping list hands it an entry or a row, not its own item type
75
+ export type IVListCellContext = {
76
+ $implicit: unknown;
77
+ cellKey: string;
78
+ index: number;
79
+ style: IViewStyle | undefined;
80
+ // Wire both onto the wrapper: `(layout)="cell.layout($event)"`, `(focus)="cell.focus()"`
81
+ layout: (event: unknown) => void;
82
+ focus: () => void;
83
+ // Stamp it with `[vListOutlet]="cell.content" [vListOutletContext]="cell.contentContext"`
84
+ content: TemplateRef<unknown> | undefined;
85
+ contentContext: unknown;
86
+ };
63
87
 
64
88
  function isRecord(value: unknown): value is Record<string, unknown> {
65
89
  return typeof value === 'object' && value !== null;
@@ -114,6 +138,20 @@ export class VListSeparatorDirective<ItemT = unknown> {
114
138
  }
115
139
  }
116
140
 
141
+ // `<ng-template vListCell>` replaces the view around each cell (RN CellRendererComponent)
142
+ // It must wire `layout` and `focus` itself, as RN's custom cell must wire `onLayout`
143
+ @Directive({ selector: '[vListCell]', standalone: true })
144
+ export class VListCellDirective {
145
+ readonly templateRef = inject<TemplateRef<IVListCellContext>>(TemplateRef);
146
+
147
+ static ngTemplateContextGuard(
148
+ _dir: VListCellDirective,
149
+ _ctx: unknown,
150
+ ): _ctx is IVListCellContext {
151
+ return true;
152
+ }
153
+ }
154
+
117
155
  // Core-only NgTemplateOutlet twin. The adapter cannot import @angular/common's NgTemplateOutlet
118
156
  // (not a dependency), so the list stamps templates through this: it creates the embedded view when
119
157
  // the template changes and updates the live view's context IN PLACE on a context change, so a
@@ -122,19 +160,32 @@ export class VListSeparatorDirective<ItemT = unknown> {
122
160
  export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
123
161
  @Input({ alias: 'vListOutlet' }) templateRef?: TemplateRef<C>;
124
162
  @Input({ alias: 'vListOutletContext' }) context?: C;
163
+ // RN's `ListItemComponent`: stamped instead of the template, its inputs are the item context
164
+ @Input({ alias: 'vListOutletComponent' }) component?: Type<unknown>;
125
165
 
126
166
  private viewRef: EmbeddedViewRef<C> | null = null;
167
+ private componentRef: ComponentRef<unknown> | null = null;
127
168
  private readonly viewContainer = inject(ViewContainerRef);
128
- private readonly instanceId = (vListOutletInstanceCounter += 1);
169
+ private readonly instanceId = nextOutletInstanceId();
129
170
 
130
171
  ngOnChanges(changes: SimpleChanges): void {
131
- if (changes['templateRef'] !== undefined) {
172
+ if (
173
+ changes['templateRef'] !== undefined ||
174
+ changes['component'] !== undefined
175
+ ) {
132
176
  if (isDebug())
133
177
  dlog(
134
- `Angular VListOutlet#${this.instanceId} templateRef CHANGED (was=${changes['templateRef'].previousValue !== undefined} now=${this.templateRef !== undefined}) -> clear + recreate`,
178
+ `Angular VListOutlet#${this.instanceId} templateRef CHANGED (was=${changes['templateRef']?.previousValue !== undefined} now=${this.templateRef !== undefined}) -> clear + recreate`,
135
179
  );
136
180
  this.viewContainer.clear();
137
181
  countAngular('outletCreates');
182
+ this.componentRef = null;
183
+ this.viewRef = null;
184
+ if (this.component !== undefined) {
185
+ this.componentRef = this.viewContainer.createComponent(this.component);
186
+ this.writeComponentInputs(this.componentRef);
187
+ return;
188
+ }
138
189
  this.viewRef =
139
190
  this.templateRef === undefined
140
191
  ? null
@@ -144,6 +195,11 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
144
195
  );
145
196
  return;
146
197
  }
198
+ if (this.componentRef !== null) {
199
+ countAngular('outletUpdates');
200
+ this.writeComponentInputs(this.componentRef);
201
+ return;
202
+ }
147
203
  if (this.viewRef !== null && this.context !== undefined) {
148
204
  countAngular('outletUpdates');
149
205
  if (isDebug())
@@ -161,6 +217,26 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
161
217
  this.viewContainer.clear();
162
218
  }
163
219
 
220
+ // The item context becomes the component's inputs, only those it declares: RN hands a
221
+ // `ListItemComponent` item, index and separators, and one may not read all three
222
+ private writeComponentInputs(ref: ComponentRef<unknown>): void {
223
+ const { context } = this;
224
+ if (!isRecord(context)) return;
225
+ const declared = new Set(
226
+ reflectComponentType(ref.componentType)?.inputs.map(
227
+ input => input.propName,
228
+ ),
229
+ );
230
+ const inputs = {
231
+ item: context['$implicit'],
232
+ index: context['index'],
233
+ separators: context['separators'],
234
+ };
235
+ for (const [name, value] of Object.entries(inputs)) {
236
+ if (declared.has(name)) ref.setInput(name, value);
237
+ }
238
+ }
239
+
164
240
  // Copy the new context's fields onto the live embedded view's context object (whose identity the
165
241
  // consumer's `let-` bindings already read), dropping keys that disappeared. The generic `C` can't
166
242
  // be index-WRITTEN even once narrowed (TS2862), so the copy runs in a non-generic helper the