@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.
- package/babel-register-composed.cjs +1 -0
- package/build/angular/accessibility-inputs.d.ts +2 -1
- package/build/angular/accessibility-inputs.js +4 -1
- package/build/angular/accessibility-inputs.js.map +1 -1
- package/build/angular/callback-host.d.ts +1 -1
- package/build/angular/callback-host.js +57 -9
- package/build/angular/callback-host.js.map +1 -1
- package/build/angular/components/flat-list/flat-list-handle.d.ts +35 -0
- package/build/angular/components/flat-list/flat-list-handle.js +48 -0
- package/build/angular/components/flat-list/flat-list-handle.js.map +1 -0
- package/build/angular/components/flat-list/flat-list-template.d.ts +1 -1
- package/build/angular/components/flat-list/flat-list-template.js +16 -0
- package/build/angular/components/flat-list/flat-list-template.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +6 -30
- package/build/angular/components/flat-list/index.js +6 -36
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-inputs.d.ts +4 -0
- package/build/angular/components/image-inputs.js +81 -0
- package/build/angular/components/image-inputs.js.map +1 -0
- package/build/angular/components/image-props-resolve.d.ts +3 -0
- package/build/angular/components/image-props-resolve.js +133 -0
- package/build/angular/components/image-props-resolve.js.map +1 -0
- package/build/angular/components/image-shared.d.ts +8 -6
- package/build/angular/components/image-shared.js +23 -212
- package/build/angular/components/image-shared.js.map +1 -1
- package/build/angular/components/input-accessory-view-props.d.ts +4 -4
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -6
- package/build/angular/components/keyboard-avoiding-view/index.js +29 -56
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/layout-conformance-props.d.ts +4 -0
- package/build/angular/components/layout-conformance-props.js +2 -0
- package/build/angular/components/layout-conformance-props.js.map +1 -0
- package/build/angular/components/modal/index.d.ts +6 -5
- package/build/angular/components/modal/index.js +17 -2
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/refresh-control-props.d.ts +7 -7
- package/build/angular/components/scroll-view-props.d.ts +8 -5
- package/build/angular/components/section-list/index.d.ts +17 -8
- package/build/angular/components/section-list/index.js +37 -28
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/touchable-props.d.ts +2 -2
- package/build/angular/components/virtualized-list/cell-renderer-context.d.ts +12 -0
- package/build/angular/components/virtualized-list/cell-renderer-context.js +16 -0
- package/build/angular/components/virtualized-list/cell-renderer-context.js.map +1 -0
- package/build/angular/components/virtualized-list/directives.d.ts +26 -6
- package/build/angular/components/virtualized-list/directives.js +59 -5
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +19 -15
- package/build/angular/components/virtualized-list/index.js +91 -61
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-list/list-bags.d.ts +5 -1
- package/build/angular/components/virtualized-list/list-bags.js +31 -19
- package/build/angular/components/virtualized-list/list-bags.js.map +1 -1
- package/build/angular/components/virtualized-list/list-effects.d.ts +2 -2
- package/build/angular/components/virtualized-list/list-effects.js +17 -27
- package/build/angular/components/virtualized-list/list-effects.js.map +1 -1
- package/build/angular/components/virtualized-list/list-handle.d.ts +4 -1
- package/build/angular/components/virtualized-list/list-handle.js +13 -3
- package/build/angular/components/virtualized-list/list-handle.js.map +1 -1
- package/build/angular/components/virtualized-list/list-inputs.d.ts +19 -5
- package/build/angular/components/virtualized-list/list-inputs.js +47 -4
- package/build/angular/components/virtualized-list/list-inputs.js.map +1 -1
- package/build/angular/components/virtualized-list/list-props.d.ts +9 -3
- package/build/angular/components/virtualized-list/list-rows.d.ts +25 -0
- package/build/angular/components/virtualized-list/list-rows.js +25 -0
- package/build/angular/components/virtualized-list/list-rows.js.map +1 -0
- package/build/angular/components/virtualized-list/list-styles.d.ts +4 -0
- package/build/angular/components/virtualized-list/list-styles.js +10 -2
- package/build/angular/components/virtualized-list/list-styles.js.map +1 -1
- package/build/angular/components/virtualized-list/list-template.d.ts +1 -1
- package/build/angular/components/virtualized-list/list-template.js +159 -73
- package/build/angular/components/virtualized-list/list-template.js.map +1 -1
- package/build/angular/components/virtualized-list/nested-scope.d.ts +6 -0
- package/build/angular/components/virtualized-list/nested-scope.js +17 -0
- package/build/angular/components/virtualized-list/nested-scope.js.map +1 -0
- package/build/angular/components/virtualized-section-list/directives.d.ts +21 -9
- package/build/angular/components/virtualized-section-list/directives.js +8 -28
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +39 -60
- package/build/angular/components/virtualized-section-list/index.js +67 -33
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/section-cell.d.ts +29 -0
- package/build/angular/components/virtualized-section-list/section-cell.js +82 -0
- package/build/angular/components/virtualized-section-list/section-cell.js.map +1 -0
- package/build/angular/components/virtualized-section-list/section-list-props.d.ts +56 -0
- package/build/angular/components/virtualized-section-list/section-list-props.js +3 -0
- package/build/angular/components/virtualized-section-list/section-list-props.js.map +1 -0
- package/build/angular/components/virtualized-section-list/section-list-template.d.ts +1 -1
- package/build/angular/components/virtualized-section-list/section-list-template.js +34 -26
- package/build/angular/components/virtualized-section-list/section-list-template.js.map +1 -1
- package/build/angular/components.d.ts +7 -3
- package/build/angular/components.js +2 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/element-base.d.ts +17 -1
- package/build/angular/element-base.js +33 -1
- package/build/angular/element-base.js.map +1 -1
- package/build/angular/element-props.d.ts +7 -7
- package/build/angular/element-text-input-native.d.ts +27 -0
- package/build/angular/element-text-input-native.js +52 -0
- package/build/angular/element-text-input-native.js.map +1 -0
- package/build/angular/elements-controls.d.ts +8 -2
- package/build/angular/elements-controls.js +13 -1
- package/build/angular/elements-controls.js.map +1 -1
- package/build/angular/elements-layout.d.ts +20 -3
- package/build/angular/elements-layout.js +37 -3
- package/build/angular/elements-layout.js.map +1 -1
- package/build/angular/elements-press.d.ts +10 -3
- package/build/angular/elements-press.js +13 -1
- package/build/angular/elements-press.js.map +1 -1
- package/build/angular/elements.d.ts +3 -3
- package/build/angular/elements.js +3 -2
- package/build/angular/elements.js.map +1 -1
- package/build/angular/index.d.ts +15 -7
- package/build/angular/index.js +22 -25
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +1 -1
- package/build/angular/modules/animated/create-animated-component.js +1 -1
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.d.ts +54 -41
- package/build/angular/modules/animated/index.js +2 -26
- package/build/angular/modules/animated/index.js.map +1 -1
- package/build/angular/modules/app-registry/index.d.ts +3 -3
- package/build/angular/modules/app-registry/index.js +2 -2
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.d.ts +9 -5
- package/build/angular/modules/status-bar/index.js +21 -10
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +4 -0
- package/build/angular/primitives/index.js +12 -0
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/render/index.d.ts +2 -2
- package/build/angular/render/index.js +10 -29
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/root-tag.d.ts +4 -0
- package/build/angular/root-tag.js +10 -0
- package/build/angular/root-tag.js.map +1 -0
- package/build/angular/services/color-scheme.service.d.ts +1 -1
- package/package.json +6 -6
- package/src/accessibility-inputs.ts +3 -0
- package/src/callback-host.ts +56 -8
- package/src/components/flat-list/flat-list-handle.ts +64 -0
- package/src/components/flat-list/flat-list-template.ts +16 -0
- package/src/components/flat-list/index.ts +6 -48
- package/src/components/image-inputs.ts +94 -0
- package/src/components/image-props-resolve.ts +172 -0
- package/src/components/image-shared.ts +26 -291
- package/src/components/input-accessory-view-props.ts +12 -8
- package/src/components/keyboard-avoiding-view/index.ts +39 -79
- package/src/components/layout-conformance-props.ts +8 -0
- package/src/components/modal/index.ts +42 -20
- package/src/components/refresh-control-props.ts +29 -25
- package/src/components/scroll-view-props.ts +84 -76
- package/src/components/section-list/index.ts +34 -37
- package/src/components/touchable-props.ts +6 -2
- package/src/components/virtualized-list/cell-renderer-context.ts +36 -0
- package/src/components/virtualized-list/directives.ts +84 -8
- package/src/components/virtualized-list/index.ts +118 -85
- package/src/components/virtualized-list/list-bags.ts +38 -19
- package/src/components/virtualized-list/list-effects.ts +20 -32
- package/src/components/virtualized-list/list-handle.ts +17 -4
- package/src/components/virtualized-list/list-inputs.ts +47 -0
- package/src/components/virtualized-list/list-props.ts +57 -50
- package/src/components/virtualized-list/list-rows.ts +66 -0
- package/src/components/virtualized-list/list-styles.ts +30 -1
- package/src/components/virtualized-list/list-template.ts +159 -73
- package/src/components/virtualized-list/nested-scope.ts +22 -0
- package/src/components/virtualized-section-list/directives.ts +41 -40
- package/src/components/virtualized-section-list/index.ts +114 -118
- package/src/components/virtualized-section-list/section-cell.ts +109 -0
- package/src/components/virtualized-section-list/section-list-props.ts +79 -0
- package/src/components/virtualized-section-list/section-list-template.ts +34 -26
- package/src/components.ts +8 -0
- package/src/element-base.ts +17 -0
- package/src/element-props.ts +35 -33
- package/src/element-text-input-native.ts +31 -0
- package/src/elements-controls.ts +9 -1
- package/src/elements-layout.ts +27 -0
- package/src/elements-press.ts +10 -2
- package/src/elements.ts +3 -0
- package/src/index.ts +79 -22
- package/src/modules/animated/index.ts +2 -47
- package/src/modules/app-registry/index.ts +3 -1
- package/src/modules/status-bar/index.ts +24 -11
- package/src/primitives/index.ts +7 -0
- package/src/render/index.ts +23 -50
- package/src/root-tag.ts +14 -0
- package/src/services/color-scheme.service.ts +1 -1
|
@@ -9,25 +9,32 @@ import {
|
|
|
9
9
|
Component,
|
|
10
10
|
ElementRef,
|
|
11
11
|
ViewChild,
|
|
12
|
+
forwardRef,
|
|
12
13
|
inject,
|
|
13
14
|
type AfterViewChecked,
|
|
14
15
|
type DoCheck,
|
|
15
16
|
type OnDestroy,
|
|
17
|
+
type TemplateRef,
|
|
16
18
|
} from '@angular/core';
|
|
17
19
|
import {
|
|
18
20
|
EMPTY_OFFSET,
|
|
19
21
|
FIRST_INDEX,
|
|
20
22
|
LIST_ACTION_KIND,
|
|
21
|
-
|
|
23
|
+
LIST_SEGMENT_KIND,
|
|
22
24
|
buildScrollViewHandle,
|
|
23
25
|
buildViewabilityPairs,
|
|
24
|
-
computeWindow,
|
|
25
26
|
createInitialListState,
|
|
27
|
+
createListNesting,
|
|
28
|
+
deriveWindow,
|
|
26
29
|
listEffectSignature,
|
|
30
|
+
listHasMore,
|
|
31
|
+
planFromMetrics,
|
|
27
32
|
reduceList,
|
|
28
33
|
resolveItemKey,
|
|
29
34
|
type IListAction,
|
|
35
|
+
type IListNesting,
|
|
30
36
|
type IListReducerInputs,
|
|
37
|
+
type IListScope,
|
|
31
38
|
type IListState,
|
|
32
39
|
type IScrollViewHandle,
|
|
33
40
|
type IViewableItemsChangedInfo,
|
|
@@ -46,11 +53,14 @@ import {
|
|
|
46
53
|
SymbioteStyleInputDirective,
|
|
47
54
|
ViewHost,
|
|
48
55
|
} from '../../primitives';
|
|
49
|
-
import { CellRegistry
|
|
56
|
+
import { CellRegistry } from './cell-registry';
|
|
57
|
+
import { buildCellRendererContext } from './cell-renderer-context';
|
|
50
58
|
import { VListOutletDirective } from './directives';
|
|
51
59
|
import { ListEffectRunner } from './list-effects';
|
|
52
60
|
import { VirtualizedListBagsBase } from './list-bags';
|
|
53
61
|
import type { IVirtualizedListInputs } from './list-props';
|
|
62
|
+
import { injectVirtualizedListScope, ListScopeProvider } from './nested-scope';
|
|
63
|
+
import { stampPlanRows, type IListRow, type IRowsParams } from './list-rows';
|
|
54
64
|
import { resolveListStyles } from './list-styles';
|
|
55
65
|
import { VIRTUALIZED_LIST_TEMPLATE } from './list-template';
|
|
56
66
|
|
|
@@ -66,15 +76,19 @@ export type {
|
|
|
66
76
|
IVirtualizedListHandle,
|
|
67
77
|
} from '@symbiote-native/components';
|
|
68
78
|
export {
|
|
79
|
+
VListCellDirective,
|
|
69
80
|
VListEmptyDirective,
|
|
70
81
|
VListFooterDirective,
|
|
71
82
|
VListHeaderDirective,
|
|
72
83
|
VListItemDirective,
|
|
84
|
+
VListOutletDirective,
|
|
73
85
|
VListSeparatorDirective,
|
|
74
86
|
} from './directives';
|
|
75
|
-
export type {
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
export type {
|
|
88
|
+
IVListCellContext,
|
|
89
|
+
IVListItemContext,
|
|
90
|
+
IVListSeparatorContext,
|
|
91
|
+
} from './directives';
|
|
78
92
|
|
|
79
93
|
export type {
|
|
80
94
|
IVirtualizedListInputs,
|
|
@@ -89,17 +103,32 @@ export type {
|
|
|
89
103
|
],
|
|
90
104
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
91
105
|
imports: [SymbioteHostPropsDirective, VListOutletDirective, ViewHost],
|
|
106
|
+
providers: [
|
|
107
|
+
{
|
|
108
|
+
provide: ListScopeProvider,
|
|
109
|
+
useExisting: forwardRef(() => VirtualizedList),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
92
112
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
93
113
|
template: VIRTUALIZED_LIST_TEMPLATE,
|
|
94
114
|
})
|
|
95
115
|
export class VirtualizedList<ItemT = unknown>
|
|
96
116
|
extends VirtualizedListBagsBase<ItemT>
|
|
97
|
-
implements
|
|
117
|
+
implements
|
|
118
|
+
IVirtualizedListInputs<ItemT>,
|
|
119
|
+
ListScopeProvider,
|
|
120
|
+
DoCheck,
|
|
121
|
+
AfterViewChecked,
|
|
122
|
+
OnDestroy
|
|
98
123
|
{
|
|
99
124
|
// A template ref on a bare intrinsic hands back the host node directly
|
|
100
125
|
@ViewChild('scrollHost', { read: ElementRef })
|
|
101
126
|
private scrollHostRef?: ElementRef<unknown>;
|
|
102
127
|
|
|
128
|
+
// The template holding a cell's item and separator, sits at the root so it resolves up front
|
|
129
|
+
@ViewChild('cellBody', { static: true })
|
|
130
|
+
private cellBody?: TemplateRef<unknown>;
|
|
131
|
+
|
|
103
132
|
protected get scrollNode(): ISymbioteNode | null {
|
|
104
133
|
const node = this.scrollHostRef?.nativeElement;
|
|
105
134
|
return isSymbioteNode(node) ? node : null;
|
|
@@ -109,15 +138,16 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
109
138
|
() => this.scrollNode,
|
|
110
139
|
);
|
|
111
140
|
|
|
141
|
+
// The template tells a spacer row from a cell row by this, a bare literal would repeat per host
|
|
142
|
+
protected readonly spacerKind = LIST_SEGMENT_KIND.spacer;
|
|
143
|
+
|
|
112
144
|
// Template-bound view state, assembled by `recomputeView` in `ngDoCheck`
|
|
113
145
|
itemCount = EMPTY_OFFSET;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
forcedStickyCell: IWindowCell<ItemT> | null = null;
|
|
117
|
-
leadingSpacerStyle: IViewStyle | null = null;
|
|
118
|
-
gapSpacerStyle: IViewStyle | null = null;
|
|
119
|
-
trailingSpacerStyle: IViewStyle | null = null;
|
|
146
|
+
// The plan's spacers and cells in document order
|
|
147
|
+
rows: IListRow<ItemT>[] = [];
|
|
120
148
|
cellStyle: IViewStyle | undefined = undefined;
|
|
149
|
+
headerStyle: IViewStyle | undefined = undefined;
|
|
150
|
+
footerStyle: IViewStyle | undefined = undefined;
|
|
121
151
|
|
|
122
152
|
// `renderVersion` bumps on every transition that changes render state, for the recompute dedup
|
|
123
153
|
private readonly listState: IListState<ItemT> =
|
|
@@ -149,11 +179,34 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
149
179
|
// This component's own host, the anchor that `class="..."` at the use site resolves onto
|
|
150
180
|
private readonly elementRef = inject(ElementRef);
|
|
151
181
|
|
|
152
|
-
|
|
182
|
+
protected readonly keyFor = (index: number): string => {
|
|
153
183
|
const item = this.getItem(this.data, index);
|
|
154
184
|
return resolveItemKey(item, index, this.keyExtractor);
|
|
155
185
|
};
|
|
156
186
|
|
|
187
|
+
private readonly parentScope = injectVirtualizedListScope();
|
|
188
|
+
private nestingMemo: IListNesting<ItemT> | undefined = undefined;
|
|
189
|
+
|
|
190
|
+
// Built on first use, the inputs that pick its axis are set only after construction
|
|
191
|
+
protected get nesting(): IListNesting<ItemT> {
|
|
192
|
+
this.nestingMemo ??= createListNesting<ItemT>({
|
|
193
|
+
parent: this.parentScope,
|
|
194
|
+
horizontal: this.isHorizontal,
|
|
195
|
+
getState: () => this.listState,
|
|
196
|
+
dispatch: action => this.dispatch(action),
|
|
197
|
+
getContainerNode: () => this.scrollNode,
|
|
198
|
+
getHasMore: () => listHasMore(this.listState),
|
|
199
|
+
keyFor: this.keyFor,
|
|
200
|
+
handlers: () => this.listHandlers,
|
|
201
|
+
});
|
|
202
|
+
return this.nestingMemo;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The scope the lists in this list's cells inject
|
|
206
|
+
get listScope(): IListScope {
|
|
207
|
+
return this.nesting.scope;
|
|
208
|
+
}
|
|
209
|
+
|
|
157
210
|
// Edge and viewability listeners map to `.observed`, so the reducer emits only if listened to
|
|
158
211
|
private buildInputs(): IListReducerInputs<ItemT> {
|
|
159
212
|
return {
|
|
@@ -164,6 +217,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
164
217
|
getItemLayout: this.getItemLayout,
|
|
165
218
|
horizontal: this.isHorizontal,
|
|
166
219
|
windowSize: this.windowSizeValue,
|
|
220
|
+
disableVirtualization: this.disableVirtualization,
|
|
167
221
|
initialNumToRender: this.initialNumToRenderValue,
|
|
168
222
|
maxToRenderPerBatch: this.maxToRenderPerBatchValue,
|
|
169
223
|
updateCellsBatchingPeriod: this.updateCellsBatchingPeriodValue,
|
|
@@ -181,6 +235,8 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
181
235
|
),
|
|
182
236
|
maintainVisibleContentPosition: this.maintainVisibleContentPosition,
|
|
183
237
|
initialScrollIndex: this.initialScrollIndex,
|
|
238
|
+
findFirstChildWithMore: (first, last) =>
|
|
239
|
+
this.nesting.findFirstChildWithMore(first, last),
|
|
184
240
|
};
|
|
185
241
|
}
|
|
186
242
|
|
|
@@ -204,15 +260,12 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
204
260
|
const m = this.listState.metrics;
|
|
205
261
|
// Mid-fill the window climbs one batch step per render, only a render advances it
|
|
206
262
|
if (m.first !== m.target.first || m.last !== m.target.last) return false;
|
|
207
|
-
const next =
|
|
263
|
+
const next = deriveWindow(
|
|
264
|
+
this.listState,
|
|
265
|
+
this.buildInputs(),
|
|
208
266
|
m.count,
|
|
209
|
-
m
|
|
210
|
-
|
|
211
|
-
this.listState.scrollOffset,
|
|
212
|
-
this.listState.viewportLength,
|
|
213
|
-
this.windowSizeValue,
|
|
214
|
-
this.initialNumToRenderValue,
|
|
215
|
-
);
|
|
267
|
+
m,
|
|
268
|
+
).target;
|
|
216
269
|
const isSettled =
|
|
217
270
|
next.first === m.target.first && next.last === m.target.last;
|
|
218
271
|
if (!isSettled)
|
|
@@ -234,6 +287,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
234
287
|
this.isHorizontal,
|
|
235
288
|
this.isInverted,
|
|
236
289
|
this.windowSizeValue,
|
|
290
|
+
this.disableVirtualization,
|
|
237
291
|
this.initialNumToRenderValue,
|
|
238
292
|
this.maxToRenderPerBatchValue,
|
|
239
293
|
this.stickyHeaderIndices,
|
|
@@ -242,11 +296,14 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
242
296
|
// Arrives through `addClass` and `removeClass`, never as an input
|
|
243
297
|
anchorHostStyle(this.elementRef),
|
|
244
298
|
this.contentContainerStyle,
|
|
299
|
+
this.listHeaderComponentStyle,
|
|
300
|
+
this.listFooterComponentStyle,
|
|
245
301
|
this.renderVersion,
|
|
246
302
|
this.headerDir !== undefined,
|
|
247
303
|
this.footerDir !== undefined,
|
|
248
304
|
this.emptyDir !== undefined,
|
|
249
305
|
this.separatorTemplate !== undefined,
|
|
306
|
+
this.cellRendererTpl,
|
|
250
307
|
];
|
|
251
308
|
const previous = this.lastRecompute;
|
|
252
309
|
this.lastRecompute = recomputeInputs;
|
|
@@ -281,6 +338,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
281
338
|
|
|
282
339
|
ngOnDestroy(): void {
|
|
283
340
|
this.effects.dispose();
|
|
341
|
+
this.nestingMemo?.detach();
|
|
284
342
|
}
|
|
285
343
|
|
|
286
344
|
private recomputeView(): void {
|
|
@@ -297,29 +355,35 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
297
355
|
this.showEmpty();
|
|
298
356
|
return;
|
|
299
357
|
}
|
|
300
|
-
|
|
301
|
-
this.stickyHeaderIndices
|
|
302
|
-
|
|
303
|
-
: undefined;
|
|
304
|
-
const plan = buildListPlan({
|
|
358
|
+
this.rows = stampPlanRows({
|
|
359
|
+
windowPlan: planFromMetrics(m, this.keyFor, this.stickyHeaderIndices),
|
|
360
|
+
cells: this.cells,
|
|
305
361
|
count: m.count,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
lengths: m.lengths,
|
|
310
|
-
total: m.total,
|
|
311
|
-
keyFor: this.keyFor,
|
|
312
|
-
stickyIndices: stickySet,
|
|
313
|
-
hasHeader: this.headerDir !== undefined,
|
|
362
|
+
isHorizontal: this.isHorizontal,
|
|
363
|
+
hasSeparators: this.separatorTemplate !== undefined,
|
|
364
|
+
rendererFor: this.rendererFor(),
|
|
314
365
|
});
|
|
315
|
-
this.
|
|
316
|
-
this.buildCells(plan, stickySet);
|
|
366
|
+
this.cells.endPass();
|
|
317
367
|
dlog(
|
|
318
368
|
`Angular VirtualizedList window [${m.first}, ${m.last}] of ${m.count} ` +
|
|
319
|
-
`(offset=${this.listState.scrollOffset}, viewport=${this.listState.viewportLength},
|
|
369
|
+
`(offset=${this.listState.scrollOffset}, viewport=${this.listState.viewportLength}, rows=${this.rows.length})`,
|
|
320
370
|
);
|
|
321
371
|
}
|
|
322
372
|
|
|
373
|
+
// Only a list given a `vListCell` template builds the wrapper contexts
|
|
374
|
+
private rendererFor(): IRowsParams<ItemT>['rendererFor'] {
|
|
375
|
+
if (this.cellRendererTpl === undefined) return undefined;
|
|
376
|
+
return cell =>
|
|
377
|
+
buildCellRendererContext({
|
|
378
|
+
cell,
|
|
379
|
+
style: this.cellStyle,
|
|
380
|
+
body: this.cellBody,
|
|
381
|
+
layout: (measure, event, index) =>
|
|
382
|
+
this.handleCellLayout(measure, event, index),
|
|
383
|
+
focus: index => this.handleCellFocus(index),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
323
387
|
private applyStyles(total: number): void {
|
|
324
388
|
const styles = resolveListStyles({
|
|
325
389
|
isHorizontal: this.isHorizontal,
|
|
@@ -328,75 +392,44 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
328
392
|
hasHeader: this.headerDir !== undefined,
|
|
329
393
|
style: this.style,
|
|
330
394
|
contentContainerStyle: this.contentContainerStyle,
|
|
395
|
+
listHeaderComponentStyle: this.listHeaderComponentStyle,
|
|
396
|
+
listFooterComponentStyle: this.listFooterComponentStyle,
|
|
331
397
|
anchorStyle: anchorHostStyle(this.elementRef),
|
|
332
398
|
maintainVisibleContentPosition: this.maintainVisibleContentPosition,
|
|
333
399
|
});
|
|
334
400
|
this.resolvedStyle = styles.style;
|
|
335
401
|
this.resolvedContentContainerStyle = styles.contentContainerStyle;
|
|
336
402
|
this.cellStyle = styles.cellStyle;
|
|
403
|
+
this.headerStyle = styles.headerStyle;
|
|
404
|
+
this.footerStyle = styles.footerStyle;
|
|
337
405
|
this.resolvedMaintainVisibleContentPosition =
|
|
338
406
|
styles.maintainVisibleContentPosition;
|
|
339
407
|
}
|
|
340
408
|
|
|
341
409
|
private showEmpty(): void {
|
|
342
410
|
this.cells.clear();
|
|
343
|
-
this.
|
|
344
|
-
this.forcedStickyCell = null;
|
|
345
|
-
this.leadingSpacerStyle = null;
|
|
346
|
-
this.gapSpacerStyle = null;
|
|
347
|
-
this.trailingSpacerStyle = null;
|
|
411
|
+
this.rows = [];
|
|
348
412
|
dlog(
|
|
349
413
|
`Angular VirtualizedList empty (viewport=${this.listState.viewportLength})`,
|
|
350
414
|
);
|
|
351
415
|
}
|
|
352
416
|
|
|
353
|
-
private applySpacers(plan: IListPlan): void {
|
|
354
|
-
this.leadingSpacerStyle = this.spacerStyle(plan.leadingExtent);
|
|
355
|
-
this.gapSpacerStyle = this.spacerStyle(plan.gapExtent);
|
|
356
|
-
this.trailingSpacerStyle = this.spacerStyle(plan.trailingExtent);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
private spacerStyle(extent: number): IViewStyle | null {
|
|
360
|
-
if (extent <= EMPTY_OFFSET) return null;
|
|
361
|
-
return this.isHorizontal ? { width: extent } : { height: extent };
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private buildCells(
|
|
365
|
-
plan: IListPlan,
|
|
366
|
-
stickySet: Set<number> | undefined,
|
|
367
|
-
): void {
|
|
368
|
-
const count = this.listState.metrics.count;
|
|
369
|
-
const forced = plan.forcedStickyCell;
|
|
370
|
-
dlog(
|
|
371
|
-
`STICKY[list] stickySet=${stickySet === undefined ? 'undefined' : JSON.stringify([...stickySet])} ` +
|
|
372
|
-
`childPositions=${JSON.stringify(plan.stickyChildPositions)} ` +
|
|
373
|
-
`forcedStickyCell=${forced === undefined ? 'none' : forced.index}`,
|
|
374
|
-
);
|
|
375
|
-
// Sticky by construction: a forced cell exists only for an index in `stickySet`
|
|
376
|
-
this.forcedStickyCell =
|
|
377
|
-
forced !== undefined
|
|
378
|
-
? this.cells.windowCell(forced.index, forced.key, false, true)
|
|
379
|
-
: null;
|
|
380
|
-
const hasSeparators = this.separatorTemplate !== undefined;
|
|
381
|
-
// The separator gates on the last index of the data, not of the window, or a cell's height
|
|
382
|
-
// would shift as it slides past
|
|
383
|
-
this.windowCells = plan.cells.map(planned =>
|
|
384
|
-
this.cells.windowCell(
|
|
385
|
-
planned.index,
|
|
386
|
-
planned.key,
|
|
387
|
-
hasSeparators && planned.index < count - 1,
|
|
388
|
-
stickySet?.has(planned.index) === true,
|
|
389
|
-
),
|
|
390
|
-
);
|
|
391
|
-
this.cells.endPass();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
417
|
// Angular forbids an `[onLayout]` binding, so the event arrives untyped and is narrowed here
|
|
395
418
|
handleCellLayout(
|
|
396
419
|
measure: (event: ISymbioteEvent) => void,
|
|
397
420
|
event: unknown,
|
|
421
|
+
index: number,
|
|
398
422
|
): void {
|
|
399
|
-
if (this.isSymbioteEvent(event))
|
|
423
|
+
if (!this.isSymbioteEvent(event)) return;
|
|
424
|
+
measure(event);
|
|
425
|
+
const cellKey = this.keyFor(index);
|
|
426
|
+
this.nesting.scope.registerCellNode(event.currentTarget, cellKey);
|
|
427
|
+
this.nesting.remeasureCell(cellKey);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Focus inside a cell keeps a viewport of cells around it mounted, like RN's focus capture
|
|
431
|
+
handleCellFocus(index: number): void {
|
|
432
|
+
this.dispatch({ kind: LIST_ACTION_KIND.cellFocused, index });
|
|
400
433
|
}
|
|
401
434
|
|
|
402
435
|
private isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
import { Directive, computed, signal, type OnChanges } from '@angular/core';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
readLayoutLength,
|
|
8
|
-
readScrollOffset,
|
|
6
|
+
createListHandlers,
|
|
9
7
|
resolveAccessibilityProps,
|
|
10
8
|
type IAccessibilityProps,
|
|
11
9
|
type IAriaProps,
|
|
10
|
+
type IListHandlers,
|
|
11
|
+
type IListNesting,
|
|
12
12
|
} from '@symbiote-native/components';
|
|
13
13
|
import {
|
|
14
|
-
dlog,
|
|
15
14
|
type IStyleProp,
|
|
16
15
|
type ISymbioteEvent,
|
|
17
16
|
type IViewStyle,
|
|
@@ -32,6 +31,23 @@ export abstract class VirtualizedListBagsBase<ItemT>
|
|
|
32
31
|
// A fresh object per push, so the commit re-applies a repeated offset
|
|
33
32
|
commandedOffset: { x: number; y: number } | undefined = undefined;
|
|
34
33
|
|
|
34
|
+
protected abstract get nesting(): IListNesting<ItemT>;
|
|
35
|
+
|
|
36
|
+
protected abstract readonly keyFor: (index: number) => string;
|
|
37
|
+
|
|
38
|
+
// Reads of `this` are deferred to the event, so the inputs are set by the time they run
|
|
39
|
+
protected readonly listHandlers: IListHandlers = createListHandlers<ItemT>({
|
|
40
|
+
isHorizontal: () => this.isHorizontal,
|
|
41
|
+
user: () => this,
|
|
42
|
+
nesting: () => this.nesting,
|
|
43
|
+
dispatch: action => this.dispatch(action),
|
|
44
|
+
clearCommandedOffset: () => {
|
|
45
|
+
this.commandedOffset = undefined;
|
|
46
|
+
},
|
|
47
|
+
getNode: () => this.scrollNode,
|
|
48
|
+
keyFor: index => this.keyFor(index),
|
|
49
|
+
});
|
|
50
|
+
|
|
35
51
|
// Only inputs may feed this signal, window state changes skip `ngOnChanges`
|
|
36
52
|
private readonly inputsRevision = signal(0);
|
|
37
53
|
|
|
@@ -64,15 +80,18 @@ export abstract class VirtualizedListBagsBase<ItemT>
|
|
|
64
80
|
contentContainerStyle: this.resolvedContentContainerStyle,
|
|
65
81
|
onScroll: this.onScrollTick,
|
|
66
82
|
onLayout: this.onLayoutTick,
|
|
67
|
-
onScrollBeginDrag: this.onScrollBeginDrag,
|
|
68
|
-
onScrollEndDrag: this.onScrollEndDrag,
|
|
69
|
-
onMomentumScrollBegin: this.onMomentumScrollBegin,
|
|
70
|
-
onMomentumScrollEnd: this.onMomentumScrollEnd,
|
|
83
|
+
onScrollBeginDrag: this.listHandlers.onScrollBeginDrag,
|
|
84
|
+
onScrollEndDrag: this.listHandlers.onScrollEndDrag,
|
|
85
|
+
onMomentumScrollBegin: this.listHandlers.onMomentumScrollBegin,
|
|
86
|
+
onMomentumScrollEnd: this.listHandlers.onMomentumScrollEnd,
|
|
87
|
+
onContentSizeChange: this.listHandlers.onContentSizeChange,
|
|
71
88
|
scrollEventThrottle: this.scrollEventThrottle,
|
|
72
89
|
keyboardShouldPersistTaps: this.keyboardShouldPersistTaps,
|
|
73
90
|
keyboardDismissMode: this.keyboardDismissMode,
|
|
74
91
|
removeClippedSubviews: this.removeClippedSubviews,
|
|
75
92
|
nestedScrollEnabled: this.nestedScrollEnabled,
|
|
93
|
+
stickyHeaderHiddenOnScroll: this.stickyHeaderHiddenOnScroll,
|
|
94
|
+
innerViewRef: this.innerViewRef,
|
|
76
95
|
// Android moves the scrollbar back after the `scale: -1` flip
|
|
77
96
|
isInvertedVirtualizedList: this.isInverted ? true : undefined,
|
|
78
97
|
contentOffset: this.commandedOffset,
|
|
@@ -101,23 +120,23 @@ export abstract class VirtualizedListBagsBase<ItemT>
|
|
|
101
120
|
};
|
|
102
121
|
}
|
|
103
122
|
|
|
123
|
+
// The list above scrolls, so RN renders a `View` with no content container
|
|
124
|
+
nestedViewBag(): Record<string, unknown> {
|
|
125
|
+
return {
|
|
126
|
+
...this.foldedAccessibility(),
|
|
127
|
+
style: this.resolvedStyle,
|
|
128
|
+
onLayout: this.onLayoutTick,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
104
132
|
// `onScroll` stays a callback so an `Animated.event` target can flow through it
|
|
105
133
|
onScrollTick = (event: ISymbioteEvent): void => {
|
|
106
134
|
countAngular('scrollTicks');
|
|
107
|
-
|
|
108
|
-
if (offset === undefined) return;
|
|
109
|
-
dlog(`Angular VirtualizedList onScroll offset=${offset}`);
|
|
110
|
-
// A real native scroll supersedes any pending commanded offset
|
|
111
|
-
this.commandedOffset = undefined;
|
|
112
|
-
this.dispatch({ kind: LIST_ACTION_KIND.scroll, offset });
|
|
113
|
-
this.onScroll?.(event);
|
|
135
|
+
this.listHandlers.onScroll(event);
|
|
114
136
|
};
|
|
115
137
|
|
|
116
138
|
onLayoutTick = (event: ISymbioteEvent): void => {
|
|
117
|
-
|
|
118
|
-
if (length === undefined) return;
|
|
119
|
-
dlog(`Angular VirtualizedList onLayout viewport=${length}`);
|
|
120
|
-
this.dispatch({ kind: LIST_ACTION_KIND.layout, length });
|
|
139
|
+
this.listHandlers.onViewportLayout(event);
|
|
121
140
|
};
|
|
122
141
|
|
|
123
142
|
handleRefresh = (): void => {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// reducer only knows a delay for (the incremental fill and the viewability debounce)
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
EMPTY_OFFSET,
|
|
6
5
|
LIST_ACTION_KIND,
|
|
7
6
|
LIST_EFFECT_KIND,
|
|
8
7
|
type IListAction,
|
|
@@ -24,13 +23,14 @@ export type IEffectHost<ItemT> = {
|
|
|
24
23
|
}>;
|
|
25
24
|
};
|
|
26
25
|
|
|
27
|
-
type
|
|
28
|
-
IListEffect<
|
|
29
|
-
{ kind: typeof LIST_EFFECT_KIND.
|
|
26
|
+
type IScheduledViewable = Extract<
|
|
27
|
+
IListEffect<unknown>,
|
|
28
|
+
{ kind: typeof LIST_EFFECT_KIND.scheduleViewable }
|
|
30
29
|
>;
|
|
31
30
|
|
|
32
31
|
export class ListEffectRunner<ItemT> {
|
|
33
|
-
|
|
32
|
+
// RN never cancels a `minimumViewTime` timer, each one comes due and the reducer filters it
|
|
33
|
+
private readonly viewableTimers = new Set<ReturnType<typeof setTimeout>>();
|
|
34
34
|
private batchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
35
35
|
|
|
36
36
|
constructor(private readonly host: IEffectHost<ItemT>) {}
|
|
@@ -40,7 +40,8 @@ export class ListEffectRunner<ItemT> {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
dispose(): void {
|
|
43
|
-
|
|
43
|
+
for (const timer of this.viewableTimers) clearTimeout(timer);
|
|
44
|
+
this.viewableTimers.clear();
|
|
44
45
|
if (this.batchTimer !== null) clearTimeout(this.batchTimer);
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -71,7 +72,12 @@ export class ListEffectRunner<ItemT> {
|
|
|
71
72
|
this.scheduleRefill(effect.delay);
|
|
72
73
|
break;
|
|
73
74
|
case LIST_EFFECT_KIND.fireViewable:
|
|
74
|
-
|
|
75
|
+
inputs.viewabilityPairs[effect.pairIndex]?.onViewableItemsChanged(
|
|
76
|
+
effect.info,
|
|
77
|
+
);
|
|
78
|
+
break;
|
|
79
|
+
case LIST_EFFECT_KIND.scheduleViewable:
|
|
80
|
+
this.scheduleViewable(effect);
|
|
75
81
|
break;
|
|
76
82
|
}
|
|
77
83
|
}
|
|
@@ -84,33 +90,15 @@ export class ListEffectRunner<ItemT> {
|
|
|
84
90
|
}, delay);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
): void {
|
|
91
|
-
const fire = (): void => {
|
|
92
|
-
for (const pair of inputs.viewabilityPairs) {
|
|
93
|
-
pair.onViewableItemsChanged({
|
|
94
|
-
...effect.info,
|
|
95
|
-
viewabilityConfig: pair.viewabilityConfig,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
93
|
+
private scheduleViewable(effect: IScheduledViewable): void {
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
this.viewableTimers.delete(timer);
|
|
98
96
|
this.host.dispatch({
|
|
99
|
-
kind: LIST_ACTION_KIND.
|
|
100
|
-
|
|
97
|
+
kind: LIST_ACTION_KIND.viewableDue,
|
|
98
|
+
pairIndex: effect.pairIndex,
|
|
99
|
+
indices: effect.indices,
|
|
101
100
|
});
|
|
102
|
-
};
|
|
103
|
-
if (this.viewableTimer !== null) {
|
|
104
|
-
clearTimeout(this.viewableTimer);
|
|
105
|
-
this.viewableTimer = null;
|
|
106
|
-
}
|
|
107
|
-
if (effect.delay <= EMPTY_OFFSET) {
|
|
108
|
-
fire();
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
this.viewableTimer = setTimeout(() => {
|
|
112
|
-
this.viewableTimer = null;
|
|
113
|
-
fire();
|
|
114
101
|
}, effect.delay);
|
|
102
|
+
this.viewableTimers.add(timer);
|
|
115
103
|
}
|
|
116
104
|
}
|
|
@@ -70,23 +70,36 @@ export abstract class VirtualizedListHandleBase<ItemT>
|
|
|
70
70
|
this.scrollHandle.flashScrollIndicators();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// Null until the scroll tag commits, RN's "no scroll view yet" answer
|
|
74
|
+
private handleOrNull(): IScrollViewHandle | null {
|
|
75
|
+
return this.scrollNode === null ? null : this.scrollHandle;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getNativeScrollRef(): ISymbioteNode | null {
|
|
79
|
+
return this.scrollHandle.getScrollNode();
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
getScrollableNode(): IScrollViewHandle | null {
|
|
78
|
-
return this.
|
|
83
|
+
return this.handleOrNull();
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
getScrollResponder(): IScrollViewHandle | null {
|
|
82
|
-
return this.
|
|
87
|
+
return this.handleOrNull();
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
getScrollNode(): ISymbioteNode | null {
|
|
86
91
|
return this.scrollHandle.getScrollNode();
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
getScrollRef(): ISymbioteNode | null {
|
|
95
|
+
return this.scrollHandle.getScrollNode();
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
recordInteraction(): void {
|
|
90
99
|
this.dispatch({ kind: LIST_ACTION_KIND.recordInteraction });
|
|
91
100
|
}
|
|
101
|
+
|
|
102
|
+
setNativeProps(props: Record<string, unknown>): void {
|
|
103
|
+
this.scrollNode?.setNativeProps(props);
|
|
104
|
+
}
|
|
92
105
|
}
|