@symbiote-native/angular 3.0.1 → 3.1.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.
- package/build/angular/callback-host.d.ts +2 -5
- package/build/angular/callback-host.js +11 -36
- package/build/angular/callback-host.js.map +1 -1
- package/build/angular/change-detection-flush.d.ts +1 -0
- package/build/angular/change-detection-flush.js +58 -14
- package/build/angular/change-detection-flush.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +4 -2
- package/build/angular/components/flat-list/index.js +28 -78
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-shared.d.ts +8 -2
- package/build/angular/components/modal/index.d.ts +1 -0
- package/build/angular/components/modal/index.js +23 -16
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +3 -1
- package/build/angular/components/section-list/index.js +10 -2
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +7 -4
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +10 -2
- package/build/angular/components/virtualized-list/index.js +45 -25
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +5 -1
- package/build/angular/components/virtualized-section-list/index.js +10 -2
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +6 -3
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/element-props.d.ts +16 -5
- package/build/angular/elements.d.ts +5 -21
- package/build/angular/elements.js +18 -26
- package/build/angular/elements.js.map +1 -1
- package/build/angular/index.d.ts +0 -1
- package/build/angular/index.js +5 -7
- package/build/angular/index.js.map +1 -1
- package/build/angular/primitives/shared.js +5 -1
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/renderer/index.d.ts +11 -0
- package/build/angular/renderer/index.js +97 -12
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/runtime-matching.d.ts +0 -1
- package/build/angular/runtime-matching.js +6 -29
- package/build/angular/runtime-matching.js.map +1 -1
- package/package.json +6 -6
- package/src/callback-host.ts +10 -43
- package/src/change-detection-flush.ts +67 -14
- package/src/components/flat-list/index.ts +22 -54
- package/src/components/modal/index.ts +22 -16
- package/src/components/section-list/index.ts +4 -0
- package/src/components/virtualized-list/directives.ts +10 -8
- package/src/components/virtualized-list/index.ts +38 -13
- package/src/components/virtualized-section-list/index.ts +6 -0
- package/src/descriptor-to-angular/index.ts +6 -3
- package/src/element-props.ts +22 -13
- package/src/elements.ts +21 -56
- package/src/index.ts +5 -7
- package/src/primitives/shared.ts +5 -1
- package/src/renderer/index.ts +107 -11
- package/src/runtime-matching.ts +6 -30
- package/build/angular/style-host.d.ts +0 -11
- package/build/angular/style-host.js +0 -86
- package/build/angular/style-host.js.map +0 -1
- package/src/style-host.ts +0 -94
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
// flat-list.test.ts). Fixed for both column modes:
|
|
14
14
|
// * Single column (numColumns <= 1): FlatList captures the app's `<ng-template vListItem>` (and
|
|
15
15
|
// vListHeader/vListFooter/vListEmpty/vListSeparator) with its OWN @ContentChild — a single,
|
|
16
|
-
// direct projection hop, which always resolves
|
|
17
|
-
//
|
|
18
|
-
//
|
|
16
|
+
// direct projection hop, which always resolves. The per-cell item and separator templates go
|
|
17
|
+
// straight in through VirtualizedList's `[itemTemplate]`/`[itemSeparatorTemplate]`, so a cell
|
|
18
|
+
// is ONE outlet deep (a re-stamp doubled every cell's embedded views); the once-per-list
|
|
19
|
+
// header/footer/empty slots are still re-stamped.
|
|
19
20
|
// * Multi column (numColumns > 1): same re-stamp, but the app's vListItem is typed for ItemT
|
|
20
21
|
// while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work
|
|
21
22
|
// regardless. The row vListItem lays out the N columns side by side (each cell stamps the
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
expandRowViewability,
|
|
53
54
|
firstItemOfRow,
|
|
54
55
|
lastItemOfRow,
|
|
56
|
+
removeClippedSubviewsOrDefault,
|
|
55
57
|
rowKeyExtractor,
|
|
56
58
|
type IRow,
|
|
57
59
|
type IScrollViewHandle,
|
|
@@ -61,6 +63,7 @@ import {
|
|
|
61
63
|
type IVirtualizedListHandle,
|
|
62
64
|
} from '@symbiote-native/components';
|
|
63
65
|
import {
|
|
66
|
+
Platform,
|
|
64
67
|
dlog,
|
|
65
68
|
flattenStyle,
|
|
66
69
|
resolveClassName,
|
|
@@ -111,15 +114,6 @@ const NOOP_SEPARATORS: ISeparators = {
|
|
|
111
114
|
updateProps: (): void => undefined,
|
|
112
115
|
};
|
|
113
116
|
|
|
114
|
-
// Angular cannot preserve VListSeparatorDirective<ItemT>'s type parameter across this re-stamp
|
|
115
|
-
// reuse (the directive is matched structurally in FlatList's own template with no explicit type
|
|
116
|
-
// argument to pin ItemT to, so `let-leadingItem`/`let-trailingItem` arrive typed `unknown`) — even
|
|
117
|
-
// though VirtualizedList's own buildSeparatorContext always supplies a real ItemT value. The
|
|
118
|
-
// narrowest possible I/O-boundary cast for that gap; not a general-purpose unknown-to-T narrowing.
|
|
119
|
-
function asItem<ItemT>(value: unknown): ItemT | undefined {
|
|
120
|
-
return value as ItemT | undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
117
|
// Re-export the shared list types + the authoring directives so the app can import the cell/slot
|
|
124
118
|
// directives alongside FlatList (mirrors how virtualized-list/index re-exports them).
|
|
125
119
|
export type {
|
|
@@ -226,6 +220,8 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
226
220
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
227
221
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
228
222
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
223
|
+
[removeClippedSubviews]="resolvedRemoveClippedSubviews"
|
|
224
|
+
[nestedScrollEnabled]="nestedScrollEnabled"
|
|
229
225
|
[testID]="testID"
|
|
230
226
|
[style]="resolvedStyle"
|
|
231
227
|
[contentContainerStyle]="contentContainerStyle"
|
|
@@ -310,21 +306,14 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
310
306
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
311
307
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
312
308
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
309
|
+
[removeClippedSubviews]="resolvedRemoveClippedSubviews"
|
|
310
|
+
[nestedScrollEnabled]="nestedScrollEnabled"
|
|
313
311
|
[testID]="testID"
|
|
314
312
|
[style]="resolvedStyle"
|
|
315
313
|
[contentContainerStyle]="contentContainerStyle"
|
|
314
|
+
[itemTemplate]="itemDir?.templateRef"
|
|
315
|
+
[itemSeparatorTemplate]="separatorDir?.templateRef"
|
|
316
316
|
>
|
|
317
|
-
<ng-template
|
|
318
|
-
vListItem
|
|
319
|
-
let-item
|
|
320
|
-
let-index="index"
|
|
321
|
-
let-separators="separators"
|
|
322
|
-
>
|
|
323
|
-
<ng-container
|
|
324
|
-
[vListOutlet]="itemDir?.templateRef"
|
|
325
|
-
[vListOutletContext]="{ $implicit: item, index, separators }"
|
|
326
|
-
></ng-container>
|
|
327
|
-
</ng-template>
|
|
328
317
|
@if (headerDir !== undefined) {
|
|
329
318
|
<ng-template vListHeader>
|
|
330
319
|
<ng-container [vListOutlet]="headerDir.templateRef"></ng-container>
|
|
@@ -340,21 +329,6 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
340
329
|
<ng-container [vListOutlet]="emptyDir.templateRef"></ng-container>
|
|
341
330
|
</ng-template>
|
|
342
331
|
}
|
|
343
|
-
@if (separatorDir !== undefined) {
|
|
344
|
-
<ng-template
|
|
345
|
-
vListSeparator
|
|
346
|
-
let-highlighted="highlighted"
|
|
347
|
-
let-leadingItem="leadingItem"
|
|
348
|
-
let-trailingItem="trailingItem"
|
|
349
|
-
>
|
|
350
|
-
<ng-container
|
|
351
|
-
[vListOutlet]="separatorDir.templateRef"
|
|
352
|
-
[vListOutletContext]="
|
|
353
|
-
itemSeparatorContext(highlighted, leadingItem, trailingItem)
|
|
354
|
-
"
|
|
355
|
-
></ng-container>
|
|
356
|
-
</ng-template>
|
|
357
|
-
}
|
|
358
332
|
</VirtualizedList>
|
|
359
333
|
}
|
|
360
334
|
`,
|
|
@@ -426,9 +400,19 @@ export class FlatList<ItemT = unknown>
|
|
|
426
400
|
@Input() scrollEventThrottle?: number;
|
|
427
401
|
@Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
428
402
|
@Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
403
|
+
@Input() removeClippedSubviews?: boolean;
|
|
404
|
+
@Input() nestedScrollEnabled?: boolean;
|
|
429
405
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
430
406
|
@Input() contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
431
407
|
|
|
408
|
+
// FlatList.js always sends it, defaulted per platform.
|
|
409
|
+
get resolvedRemoveClippedSubviews(): boolean {
|
|
410
|
+
return removeClippedSubviewsOrDefault(
|
|
411
|
+
this.removeClippedSubviews,
|
|
412
|
+
Platform.OS,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
432
416
|
// The app's cell + slot templates, captured for the multi-column re-stamp path. In the
|
|
433
417
|
// single-column path they are ALSO projected through <ng-content> to the inner list and these
|
|
434
418
|
// captures stay unused (the inner list's own @ContentChild resolves them across the projection).
|
|
@@ -583,22 +567,6 @@ export class FlatList<ItemT = unknown>
|
|
|
583
567
|
};
|
|
584
568
|
}
|
|
585
569
|
|
|
586
|
-
// Single-column separator context: leadingItem/trailingItem are already real items (no row
|
|
587
|
-
// wrapper to narrow through — see the asItem boundary comment above).
|
|
588
|
-
itemSeparatorContext(
|
|
589
|
-
highlighted: unknown,
|
|
590
|
-
leadingItem: unknown,
|
|
591
|
-
trailingItem: unknown,
|
|
592
|
-
): IVListSeparatorContext<ItemT> {
|
|
593
|
-
const isHighlighted = highlighted === true;
|
|
594
|
-
return {
|
|
595
|
-
$implicit: isHighlighted,
|
|
596
|
-
highlighted: isHighlighted,
|
|
597
|
-
leadingItem: asItem<ItemT>(leadingItem),
|
|
598
|
-
trailingItem: asItem<ItemT>(trailingItem),
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
|
|
602
570
|
// ---- imperative handle (RN FlatList surface) — delegates to the inner VirtualizedList ----
|
|
603
571
|
|
|
604
572
|
scrollToOffset(params: { offset: number; animated?: boolean }): void {
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
// through the same childSet as the rest of the tree (no second JS surface). The style math (the
|
|
3
3
|
// backdrop override, the container/host styles, the presentationStyle default), the visible gate,
|
|
4
4
|
// and the iOS keep-alive reducer all live framework-agnostic in @symbiote-native/components and are shared
|
|
5
|
-
// verbatim with React/Vue; here Angular supplies only the lifecycle: the keep-alive state
|
|
6
|
-
//
|
|
7
|
-
// that used the OLD state, the Angular twin of React's useEffect / Vue's post-flush watch — one
|
|
8
|
-
// keep-alive frame survives the visible→hidden transition), reusing renderModal's resolved props.
|
|
5
|
+
// verbatim with React/Vue; here Angular supplies only the lifecycle: the keep-alive state, armed on
|
|
6
|
+
// show (ngOnChanges) and dropped by the native dismiss on iOS, reusing renderModal's resolved props.
|
|
9
7
|
// The user children nest UNDER the container View via <ng-content>.
|
|
10
8
|
|
|
11
9
|
import {
|
|
@@ -27,7 +25,9 @@ import {
|
|
|
27
25
|
} from '@angular/core';
|
|
28
26
|
import {
|
|
29
27
|
createInitialModalState,
|
|
28
|
+
isModalVisible,
|
|
30
29
|
modalReducer,
|
|
30
|
+
modalVisibilityAction,
|
|
31
31
|
renderModal,
|
|
32
32
|
resolveAccessibilityProps,
|
|
33
33
|
shouldRenderModal,
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import {
|
|
43
43
|
dlog,
|
|
44
44
|
isSymbioteEvent,
|
|
45
|
+
Platform,
|
|
45
46
|
type IStyleProp,
|
|
46
47
|
type ISymbioteEvent,
|
|
47
48
|
type IViewStyle,
|
|
@@ -112,7 +113,7 @@ export type IAngularModalInputs = Omit<
|
|
|
112
113
|
<modal
|
|
113
114
|
[symbioteHostProps]="hostProps()"
|
|
114
115
|
(show)="show.emit()"
|
|
115
|
-
(dismiss)="
|
|
116
|
+
(dismiss)="handleDismiss()"
|
|
116
117
|
(requestClose)="requestClose.emit()"
|
|
117
118
|
(orientationChange)="emit(orientationChange, $event)"
|
|
118
119
|
>
|
|
@@ -180,8 +181,8 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
180
181
|
@Input('aria-valuenow') ariaValueNow?: number;
|
|
181
182
|
@Input('aria-valuetext') ariaValueText?: string;
|
|
182
183
|
|
|
183
|
-
// The iOS keep-alive (state/modal.ts):
|
|
184
|
-
// (
|
|
184
|
+
// The iOS keep-alive (state/modal.ts): after visible→hidden the node stays until the native
|
|
185
|
+
// onDismiss (`handleDismiss`) drops it.
|
|
185
186
|
private state: IModalState = createInitialModalState(false);
|
|
186
187
|
|
|
187
188
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
@@ -192,7 +193,7 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
192
193
|
private readonly elementRef = inject(ElementRef);
|
|
193
194
|
|
|
194
195
|
ngOnInit(): void {
|
|
195
|
-
this.state = createInitialModalState(this.visible
|
|
196
|
+
this.state = createInitialModalState(isModalVisible(this.visible));
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// Bridges the non-reactive @Input fields `hostProps` reads into the reactive graph, so it can
|
|
@@ -212,20 +213,25 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
212
213
|
const visibleChange = changes.visible;
|
|
213
214
|
// First change is reflected by the ngOnInit seed; only later toggles drive the keep-alive.
|
|
214
215
|
if (visibleChange === undefined || visibleChange.firstChange) return;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
// Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts).
|
|
217
|
+
const action = modalVisibilityAction(isModalVisible(this.visible));
|
|
218
|
+
if (action === undefined) return;
|
|
218
219
|
queueMicrotask(() => {
|
|
219
|
-
this.state = modalReducer(
|
|
220
|
-
this.state,
|
|
221
|
-
isVisible ? { type: 'show' } : { type: 'hide' },
|
|
222
|
-
);
|
|
220
|
+
this.state = modalReducer(this.state, action);
|
|
223
221
|
this.changeDetector.markForCheck();
|
|
224
222
|
});
|
|
225
223
|
}
|
|
226
224
|
|
|
225
|
+
// Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
|
|
226
|
+
handleDismiss(): void {
|
|
227
|
+
if (Platform.OS !== 'ios') return;
|
|
228
|
+
this.state = modalReducer(this.state, { type: 'hide' });
|
|
229
|
+
this.changeDetector.markForCheck();
|
|
230
|
+
this.dismiss.emit();
|
|
231
|
+
}
|
|
232
|
+
|
|
227
233
|
get shouldRender(): boolean {
|
|
228
|
-
const render = shouldRenderModal(this.visible
|
|
234
|
+
const render = shouldRenderModal(isModalVisible(this.visible), this.state);
|
|
229
235
|
if (!render) dlog('Modal hidden -> no node committed');
|
|
230
236
|
return render;
|
|
231
237
|
}
|
|
@@ -177,6 +177,8 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
177
177
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
178
178
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
179
179
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
180
|
+
[removeClippedSubviews]="removeClippedSubviews"
|
|
181
|
+
[nestedScrollEnabled]="nestedScrollEnabled"
|
|
180
182
|
[style]="resolvedStyle"
|
|
181
183
|
[contentContainerStyle]="contentContainerStyle"
|
|
182
184
|
[testID]="testID"
|
|
@@ -343,6 +345,8 @@ export class SectionList<ItemT = unknown>
|
|
|
343
345
|
@Input() scrollEventThrottle?: number;
|
|
344
346
|
@Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
345
347
|
@Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
348
|
+
@Input() removeClippedSubviews?: boolean;
|
|
349
|
+
@Input() nestedScrollEnabled?: boolean;
|
|
346
350
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
347
351
|
@Input() contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
348
352
|
@Input() testID?: string;
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
type OnDestroy,
|
|
36
36
|
type SimpleChanges,
|
|
37
37
|
} from '@angular/core';
|
|
38
|
-
import { dlog } from '@symbiote-native/engine';
|
|
38
|
+
import { dlog, isDebug } from '@symbiote-native/engine';
|
|
39
39
|
import { countAngular } from '../../diagnostics';
|
|
40
40
|
import type { ISeparators } from '@symbiote-native/components';
|
|
41
41
|
|
|
@@ -129,9 +129,10 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
|
|
|
129
129
|
|
|
130
130
|
ngOnChanges(changes: SimpleChanges): void {
|
|
131
131
|
if (changes['templateRef'] !== undefined) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
if (isDebug())
|
|
133
|
+
dlog(
|
|
134
|
+
`Angular VListOutlet#${this.instanceId} templateRef CHANGED (was=${changes['templateRef'].previousValue !== undefined} now=${this.templateRef !== undefined}) -> clear + recreate`,
|
|
135
|
+
);
|
|
135
136
|
this.viewContainer.clear();
|
|
136
137
|
countAngular('outletCreates');
|
|
137
138
|
this.viewRef =
|
|
@@ -145,9 +146,10 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
|
|
|
145
146
|
}
|
|
146
147
|
if (this.viewRef !== null && this.context !== undefined) {
|
|
147
148
|
countAngular('outletUpdates');
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (isDebug())
|
|
150
|
+
dlog(
|
|
151
|
+
`Angular VListOutlet#${this.instanceId} context updated, markForCheck`,
|
|
152
|
+
);
|
|
151
153
|
this.updateContext(this.viewRef.context, this.context);
|
|
152
154
|
this.viewRef.markForCheck();
|
|
153
155
|
}
|
|
@@ -155,7 +157,7 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
|
|
|
155
157
|
|
|
156
158
|
ngOnDestroy(): void {
|
|
157
159
|
countAngular('outletDestroys');
|
|
158
|
-
dlog(`Angular VListOutlet#${this.instanceId} destroyed`);
|
|
160
|
+
if (isDebug()) dlog(`Angular VListOutlet#${this.instanceId} destroyed`);
|
|
159
161
|
this.viewContainer.clear();
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
EventEmitter,
|
|
35
35
|
Input,
|
|
36
36
|
Output,
|
|
37
|
+
TemplateRef,
|
|
37
38
|
ViewChild,
|
|
38
39
|
computed,
|
|
39
40
|
inject,
|
|
@@ -183,6 +184,8 @@ export interface IVirtualizedListProps<ItemT>
|
|
|
183
184
|
scrollEventThrottle?: number;
|
|
184
185
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
185
186
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
187
|
+
removeClippedSubviews?: boolean;
|
|
188
|
+
nestedScrollEnabled?: boolean;
|
|
186
189
|
style?: IStyleProp<IViewStyle>;
|
|
187
190
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
188
191
|
}
|
|
@@ -267,7 +270,7 @@ interface IWindowCell<ItemT> {
|
|
|
267
270
|
[style]="cellStyle"
|
|
268
271
|
>
|
|
269
272
|
<ng-container
|
|
270
|
-
[vListOutlet]="
|
|
273
|
+
[vListOutlet]="cellTemplate"
|
|
271
274
|
[vListOutletContext]="forcedStickyCell.context"
|
|
272
275
|
></ng-container>
|
|
273
276
|
</sticky-header>
|
|
@@ -282,12 +285,12 @@ interface IWindowCell<ItemT> {
|
|
|
282
285
|
[style]="cellStyle"
|
|
283
286
|
>
|
|
284
287
|
<ng-container
|
|
285
|
-
[vListOutlet]="
|
|
288
|
+
[vListOutlet]="cellTemplate"
|
|
286
289
|
[vListOutletContext]="cell.context"
|
|
287
290
|
></ng-container>
|
|
288
291
|
@if (cell.separatorContext !== undefined) {
|
|
289
292
|
<ng-container
|
|
290
|
-
[vListOutlet]="
|
|
293
|
+
[vListOutlet]="separatorTemplate"
|
|
291
294
|
[vListOutletContext]="cell.separatorContext"
|
|
292
295
|
></ng-container>
|
|
293
296
|
}
|
|
@@ -298,12 +301,12 @@ interface IWindowCell<ItemT> {
|
|
|
298
301
|
[style]="cellStyle"
|
|
299
302
|
>
|
|
300
303
|
<ng-container
|
|
301
|
-
[vListOutlet]="
|
|
304
|
+
[vListOutlet]="cellTemplate"
|
|
302
305
|
[vListOutletContext]="cell.context"
|
|
303
306
|
></ng-container>
|
|
304
307
|
@if (cell.separatorContext !== undefined) {
|
|
305
308
|
<ng-container
|
|
306
|
-
[vListOutlet]="
|
|
309
|
+
[vListOutlet]="separatorTemplate"
|
|
307
310
|
[vListOutletContext]="cell.separatorContext"
|
|
308
311
|
></ng-container>
|
|
309
312
|
}
|
|
@@ -351,7 +354,7 @@ interface IWindowCell<ItemT> {
|
|
|
351
354
|
[style]="cellStyle"
|
|
352
355
|
>
|
|
353
356
|
<ng-container
|
|
354
|
-
[vListOutlet]="
|
|
357
|
+
[vListOutlet]="cellTemplate"
|
|
355
358
|
[vListOutletContext]="forcedStickyCell.context"
|
|
356
359
|
></ng-container>
|
|
357
360
|
</sticky-header>
|
|
@@ -373,12 +376,12 @@ interface IWindowCell<ItemT> {
|
|
|
373
376
|
[style]="cellStyle"
|
|
374
377
|
>
|
|
375
378
|
<ng-container
|
|
376
|
-
[vListOutlet]="
|
|
379
|
+
[vListOutlet]="cellTemplate"
|
|
377
380
|
[vListOutletContext]="cell.context"
|
|
378
381
|
></ng-container>
|
|
379
382
|
@if (cell.separatorContext !== undefined) {
|
|
380
383
|
<ng-container
|
|
381
|
-
[vListOutlet]="
|
|
384
|
+
[vListOutlet]="separatorTemplate"
|
|
382
385
|
[vListOutletContext]="cell.separatorContext"
|
|
383
386
|
></ng-container>
|
|
384
387
|
}
|
|
@@ -389,12 +392,12 @@ interface IWindowCell<ItemT> {
|
|
|
389
392
|
[style]="cellStyle"
|
|
390
393
|
>
|
|
391
394
|
<ng-container
|
|
392
|
-
[vListOutlet]="
|
|
395
|
+
[vListOutlet]="cellTemplate"
|
|
393
396
|
[vListOutletContext]="cell.context"
|
|
394
397
|
></ng-container>
|
|
395
398
|
@if (cell.separatorContext !== undefined) {
|
|
396
399
|
<ng-container
|
|
397
|
-
[vListOutlet]="
|
|
400
|
+
[vListOutlet]="separatorTemplate"
|
|
398
401
|
[vListOutletContext]="cell.separatorContext"
|
|
399
402
|
></ng-container>
|
|
400
403
|
}
|
|
@@ -500,6 +503,8 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
500
503
|
@Input() scrollEventThrottle?: number;
|
|
501
504
|
@Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
502
505
|
@Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
506
|
+
@Input() removeClippedSubviews?: boolean;
|
|
507
|
+
@Input() nestedScrollEnabled?: boolean;
|
|
503
508
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
504
509
|
@Input() contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
505
510
|
@Input() testID?: string;
|
|
@@ -550,6 +555,20 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
550
555
|
@ContentChild(VListSeparatorDirective)
|
|
551
556
|
separatorDir?: VListSeparatorDirective<ItemT>;
|
|
552
557
|
|
|
558
|
+
// A wrapping list (FlatList) hands its app's cell/separator templates straight in, so a cell is
|
|
559
|
+
// one outlet deep instead of a wrapper outlet around the app's own; each wins over projection.
|
|
560
|
+
@Input() itemTemplate?: TemplateRef<IVListItemContext<ItemT>>;
|
|
561
|
+
@Input() itemSeparatorTemplate?: TemplateRef<IVListSeparatorContext<ItemT>>;
|
|
562
|
+
|
|
563
|
+
get cellTemplate(): TemplateRef<IVListItemContext<ItemT>> | undefined {
|
|
564
|
+
return this.itemTemplate ?? this.itemDir?.templateRef;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
get separatorTemplate():
|
|
568
|
+
TemplateRef<IVListSeparatorContext<ItemT>> | undefined {
|
|
569
|
+
return this.itemSeparatorTemplate ?? this.separatorDir?.templateRef;
|
|
570
|
+
}
|
|
571
|
+
|
|
553
572
|
// The inner scroll TAG's own engine node — a template ref on a bare intrinsic hands back the
|
|
554
573
|
// host node directly (`isSymbioteNode(elementRef.nativeElement)`, matching `anchorHostStyle`'s
|
|
555
574
|
// own reader below), so there is no wrapper component left to read a handle off. Present from
|
|
@@ -739,6 +758,11 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
739
758
|
scrollEventThrottle: this.scrollEventThrottle,
|
|
740
759
|
keyboardShouldPersistTaps: this.keyboardShouldPersistTaps,
|
|
741
760
|
keyboardDismissMode: this.keyboardDismissMode,
|
|
761
|
+
// RN's VirtualizedList spreads its props onto the ScrollView, this one included.
|
|
762
|
+
removeClippedSubviews: this.removeClippedSubviews,
|
|
763
|
+
nestedScrollEnabled: this.nestedScrollEnabled,
|
|
764
|
+
// VirtualizedList.js:1111 — Android moves the scrollbar back after the `scale: -1` flip.
|
|
765
|
+
isInvertedVirtualizedList: this.isInverted ? true : undefined,
|
|
742
766
|
contentOffset: this.commandedOffset,
|
|
743
767
|
// `stickyHeaderIndices` is deliberately NOT forwarded — it numbers the scroll view's PAINT
|
|
744
768
|
// children, and a windowed list paints a header, a spacer and a slice, so the positions move
|
|
@@ -1041,7 +1065,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1041
1065
|
this.headerDir !== undefined,
|
|
1042
1066
|
this.footerDir !== undefined,
|
|
1043
1067
|
this.emptyDir !== undefined,
|
|
1044
|
-
this.
|
|
1068
|
+
this.separatorTemplate !== undefined,
|
|
1045
1069
|
];
|
|
1046
1070
|
const previous = this.lastRecompute;
|
|
1047
1071
|
this.lastRecompute = recomputeInputs;
|
|
@@ -1076,7 +1100,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1076
1100
|
const m = this.listState.metrics;
|
|
1077
1101
|
this.itemCount = m.count;
|
|
1078
1102
|
const hasHeader = this.headerDir !== undefined;
|
|
1079
|
-
const hasSeparators = this.
|
|
1103
|
+
const hasSeparators = this.separatorTemplate !== undefined;
|
|
1080
1104
|
const stickySet =
|
|
1081
1105
|
this.stickyHeaderIndices !== undefined
|
|
1082
1106
|
? new Set(this.stickyHeaderIndices)
|
|
@@ -1087,8 +1111,9 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1087
1111
|
: this.contentContainerStyle;
|
|
1088
1112
|
this.resolvedStyle = flattenStyle([
|
|
1089
1113
|
anchorHostStyle(this.elementRef),
|
|
1114
|
+
// VirtualizedList.js: `[inversionStyle, style]` — the app's style can override the flip.
|
|
1090
1115
|
this.isInverted
|
|
1091
|
-
? [this.
|
|
1116
|
+
? [this.isHorizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE, this.style]
|
|
1092
1117
|
: this.style,
|
|
1093
1118
|
]);
|
|
1094
1119
|
this.cellStyle = this.isInverted
|
|
@@ -146,6 +146,8 @@ export interface IVirtualizedSectionListProps<ItemT>
|
|
|
146
146
|
scrollEventThrottle?: number;
|
|
147
147
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
148
148
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
149
|
+
removeClippedSubviews?: boolean;
|
|
150
|
+
nestedScrollEnabled?: boolean;
|
|
149
151
|
style?: IStyleProp<IViewStyle>;
|
|
150
152
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
151
153
|
}
|
|
@@ -213,6 +215,8 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
213
215
|
[scrollEventThrottle]="scrollEventThrottle"
|
|
214
216
|
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
215
217
|
[keyboardDismissMode]="keyboardDismissMode"
|
|
218
|
+
[removeClippedSubviews]="removeClippedSubviews"
|
|
219
|
+
[nestedScrollEnabled]="nestedScrollEnabled"
|
|
216
220
|
[style]="resolvedStyle"
|
|
217
221
|
[contentContainerStyle]="contentContainerStyle"
|
|
218
222
|
[testID]="testID"
|
|
@@ -375,6 +379,8 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
375
379
|
@Input() scrollEventThrottle?: number;
|
|
376
380
|
@Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
377
381
|
@Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
382
|
+
@Input() removeClippedSubviews?: boolean;
|
|
383
|
+
@Input() nestedScrollEnabled?: boolean;
|
|
378
384
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
379
385
|
@Input() contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
380
386
|
@Input() testID?: string;
|
|
@@ -86,8 +86,10 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
86
86
|
private createElement(descriptor: IDescriptor): IRenderedElement {
|
|
87
87
|
const node = this.renderer.createElement(descriptor.type);
|
|
88
88
|
const props = { ...descriptor.props };
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// `Object.keys` rather than `Object.entries` — `entries` allocates a pair array per key up front,
|
|
90
|
+
// and this runs once per rendered descriptor element. See `object-iteration-cost.itest.ts`.
|
|
91
|
+
for (const key of Object.keys(props)) {
|
|
92
|
+
this.renderer.setProperty(node, key, props[key]);
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
const children = descriptor.children.map(child => this.createChild(child));
|
|
@@ -155,7 +157,8 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
155
157
|
this.renderer.setProperty(rendered.node, key, undefined);
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
|
-
for (const
|
|
160
|
+
for (const key of Object.keys(nextProps)) {
|
|
161
|
+
const value = nextProps[key];
|
|
159
162
|
if (!Object.is(prevProps[key], value)) {
|
|
160
163
|
this.renderer.setProperty(rendered.node, key, value);
|
|
161
164
|
}
|
package/src/element-props.ts
CHANGED
|
@@ -8,13 +8,9 @@
|
|
|
8
8
|
// differ, since a bare tag takes children from the template and a handle from `#ref`, so neither
|
|
9
9
|
// `children` nor `ref` appears here.
|
|
10
10
|
//
|
|
11
|
-
// `style`
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// aborting the enclosing template update — which is what an app writes, and what every deleted
|
|
15
|
-
// wrapper component accepted. The claim that a directive can reclaim it "solely by executing a
|
|
16
|
-
// linked AOT artifact" was answered rather than assumed: `elements.test.ts` compiles the binding
|
|
17
|
-
// through real ngtsc and runs the LINKED output. See `SymbioteElement.style`.
|
|
11
|
+
// `style` and `styleProp` are TWO bindings. `[style]` is Angular's styling binding: an object or CSS
|
|
12
|
+
// string, as on a DOM element, unclaimed because a claim is a directive instance per element. An RN
|
|
13
|
+
// array or press-state callback throws in that engine, so it travels as `[styleProp]`.
|
|
18
14
|
import type {
|
|
19
15
|
IAccessibilityProps,
|
|
20
16
|
IAriaProps,
|
|
@@ -31,6 +27,17 @@ import type {
|
|
|
31
27
|
|
|
32
28
|
type IEventHandler = (event: ISymbioteEvent) => void;
|
|
33
29
|
|
|
30
|
+
/**
|
|
31
|
+
* What `[style]` carries on a tag: Angular's own styling binding, decomposed key by key into
|
|
32
|
+
* `Renderer2.setStyle` exactly as on a DOM element. A style OBJECT or a CSS string - never an array
|
|
33
|
+
* or a function, which that engine cannot represent. Those are `[styleProp]`.
|
|
34
|
+
*/
|
|
35
|
+
export type IAngularStyleBinding<TStyle> = TStyle | string | null | undefined;
|
|
36
|
+
|
|
37
|
+
/** RN's full StyleProp, press-state callback included: the `[styleProp]` binding. */
|
|
38
|
+
export type IElementStyleProp =
|
|
39
|
+
IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
|
|
40
|
+
|
|
34
41
|
export interface IElementProps
|
|
35
42
|
extends IAccessibilityProps, IAriaProps, IResponderProps {
|
|
36
43
|
/** RN's modern W3C-named alias for `nativeID`; the renderer folds it (`PROP_ALIASES`). */
|
|
@@ -43,13 +50,14 @@ export interface IElementProps
|
|
|
43
50
|
renderToHardwareTextureAndroid?: boolean;
|
|
44
51
|
shouldRasterizeIOS?: boolean;
|
|
45
52
|
needsOffscreenAlphaCompositing?: boolean;
|
|
53
|
+
style?: IAngularStyleBinding<IViewStyle>;
|
|
46
54
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
55
|
+
* An RN StyleProp as ONE property binding: arrays, falsy entries, and the press-state callback.
|
|
56
|
+
* The callback is in the union because a subclass cannot widen an inherited property and
|
|
57
|
+
* `<pressable>`/`<touchable-*>` are the tags that take one; on every other tag the engine resolves
|
|
58
|
+
* it at `pressed: false`.
|
|
50
59
|
*/
|
|
51
|
-
|
|
52
|
-
IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
|
|
60
|
+
styleProp?: IElementStyleProp;
|
|
53
61
|
onPress?: IEventHandler;
|
|
54
62
|
onPressIn?: IEventHandler;
|
|
55
63
|
onPressOut?: IEventHandler;
|
|
@@ -63,7 +71,8 @@ export interface IElementProps
|
|
|
63
71
|
/** Text's own surface on top of the shared one. `onTextLayout` is an event, so it is not here. */
|
|
64
72
|
export interface ITextElementProps {
|
|
65
73
|
/** Narrows the shared `style` so `fontSize` / `fontWeight` type-check on a `<text>`. */
|
|
66
|
-
style?:
|
|
74
|
+
style?: IAngularStyleBinding<ITextStyle>;
|
|
75
|
+
styleProp?: IStyleProp<ITextStyle>;
|
|
67
76
|
numberOfLines?: number;
|
|
68
77
|
ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
|
|
69
78
|
selectable?: boolean;
|