@ng-native/components 0.2.0 → 0.3.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/dist/content-container.d.ts +25 -0
- package/dist/content-container.js +35 -0
- package/dist/content-container.js.map +1 -0
- package/dist/gestures-web.d.ts +12 -0
- package/dist/gestures-web.js +32 -0
- package/dist/gestures-web.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keyboard-avoiding-view.d.ts +6 -1
- package/dist/keyboard-avoiding-view.js +23 -6
- package/dist/keyboard-avoiding-view.js.map +1 -1
- package/dist/pressable.d.ts +33 -4
- package/dist/pressable.js +55 -3
- package/dist/pressable.js.map +1 -1
- package/dist/reanimated-web.d.ts +26 -0
- package/dist/reanimated-web.js +59 -0
- package/dist/reanimated-web.js.map +1 -0
- package/dist/refresh-control.d.ts +4 -1
- package/dist/refresh-control.js +4 -1
- package/dist/refresh-control.js.map +1 -1
- package/dist/scroll-view.d.ts +17 -1
- package/dist/scroll-view.js +59 -6
- package/dist/scroll-view.js.map +1 -1
- package/dist/section-list.d.ts +14 -2
- package/dist/section-list.js +20 -4
- package/dist/section-list.js.map +1 -1
- package/dist/sticky-headers.d.ts +6 -0
- package/dist/sticky-headers.js +7 -2
- package/dist/sticky-headers.js.map +1 -1
- package/dist/switch.d.ts +11 -1
- package/dist/switch.js +16 -3
- package/dist/switch.js.map +1 -1
- package/dist/text.d.ts +10 -1
- package/dist/text.js +15 -1
- package/dist/text.js.map +1 -1
- package/dist/touchable-opacity.d.ts +2 -1
- package/dist/touchable-opacity.js +3 -0
- package/dist/touchable-opacity.js.map +1 -1
- package/dist/view-base.d.ts +28 -18
- package/dist/view-base.js +37 -22
- package/dist/view-base.js.map +1 -1
- package/dist/virtual-list.d.ts +49 -5
- package/dist/virtual-list.js +129 -42
- package/dist/virtual-list.js.map +1 -1
- package/dist/zero-size-warning.d.ts +30 -0
- package/dist/zero-size-warning.js +58 -0
- package/dist/zero-size-warning.js.map +1 -0
- package/package.json +7 -4
package/dist/virtual-list.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ export interface VirtualRow<T> {
|
|
|
28
28
|
*/
|
|
29
29
|
readonly parked?: true;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Space around the rows, inside the scrolling content: one number for every side, or a side each,
|
|
33
|
+
* as FlatList's `contentContainerStyle` padding.
|
|
34
|
+
*/
|
|
35
|
+
export type VirtualListPadding = number | {
|
|
36
|
+
readonly top?: number;
|
|
37
|
+
readonly right?: number;
|
|
38
|
+
readonly bottom?: number;
|
|
39
|
+
readonly left?: number;
|
|
40
|
+
};
|
|
31
41
|
/** A row height: one for every row, or one per item. */
|
|
32
42
|
export type VirtualItemHeight<T> = number | ((item: T, index: number) => number);
|
|
33
43
|
/**
|
|
@@ -143,6 +153,14 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
143
153
|
* honest the scroll bar is and the less a jump to an unmeasured row has to correct.
|
|
144
154
|
*/
|
|
145
155
|
readonly estimatedItemHeight: import("@angular/core").InputSignal<VirtualItemHeight<T>>;
|
|
156
|
+
/**
|
|
157
|
+
* Space around the rows, inside the scrolling content. The leading side comes before the first
|
|
158
|
+
* row and the trailing side after the last; the sides across the axis inset every row. The
|
|
159
|
+
* header and footer sit outside it, as FlatList's do outside `contentContainerStyle`.
|
|
160
|
+
*/
|
|
161
|
+
readonly contentPadding: import("@angular/core").InputSignal<VirtualListPadding>;
|
|
162
|
+
/** Space between one row and the next, along the axis: not before the first or after the last. */
|
|
163
|
+
readonly rowGap: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
146
164
|
/**
|
|
147
165
|
* An item's identity, as FlatList's `keyExtractor`: what a measured height, a slot and a held
|
|
148
166
|
* position follow across an insert. Without it an item is its own key, which is enough for items
|
|
@@ -225,6 +243,12 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
225
243
|
private readonly pinning;
|
|
226
244
|
/** Rows size themselves and are laid out in flow, rather than placed at a known offset. */
|
|
227
245
|
readonly measuring: import("@angular/core").Signal<boolean>;
|
|
246
|
+
/** The padding on each side. */
|
|
247
|
+
private readonly edges;
|
|
248
|
+
/** The padding before the first row, along the axis. */
|
|
249
|
+
private readonly lead;
|
|
250
|
+
/** The rows' insets across the axis, as the props that place an absolutely positioned row. */
|
|
251
|
+
private readonly across;
|
|
228
252
|
/** Heights native has reported, by key. */
|
|
229
253
|
private readonly measured;
|
|
230
254
|
/** Bumped when a measurement changes, which is what re-sizes the list. */
|
|
@@ -243,8 +267,13 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
243
267
|
private readonly indexOfKey;
|
|
244
268
|
/** The index, with a dependency on its measurements for whoever is reading it. */
|
|
245
269
|
private layout;
|
|
246
|
-
/**
|
|
270
|
+
/**
|
|
271
|
+
* The list's own total extent along its axis: the padding at both ends, the rows, and a gap
|
|
272
|
+
* between each two of them.
|
|
273
|
+
*/
|
|
247
274
|
private readonly extent;
|
|
275
|
+
/** A fixed-height row and the gap after it. */
|
|
276
|
+
private stride;
|
|
248
277
|
protected readonly canvas: import("@angular/core").Signal<{
|
|
249
278
|
width: number;
|
|
250
279
|
flexDirection: string;
|
|
@@ -267,12 +296,18 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
267
296
|
* A style binding validates its values as the DOM's, and warns on a list in development.
|
|
268
297
|
*/
|
|
269
298
|
private readonly flipText;
|
|
299
|
+
/**
|
|
300
|
+
* What places a row in flow: the gap after it and the padding across the axis, as margins, since
|
|
301
|
+
* native lays the row out. Empty when there are none, so a plain list commits no margins.
|
|
302
|
+
*/
|
|
303
|
+
private readonly flowSpacing;
|
|
270
304
|
/** A row in flow, flipped back when the list is inverted. One object, so rows never re-clone. */
|
|
271
305
|
private readonly flowStyle;
|
|
272
|
-
/** Top of a row. */
|
|
306
|
+
/** Top of a row: the leading padding, and every row and gap before it. */
|
|
273
307
|
private topOf;
|
|
308
|
+
/** A row's own size, without the gap after it. */
|
|
274
309
|
private heightOf;
|
|
275
|
-
/** First row whose bottom is past `y`. */
|
|
310
|
+
/** First row whose bottom, or the gap after it, is past `y`. */
|
|
276
311
|
private indexAt;
|
|
277
312
|
/** The row the viewport's leading edge is in, taken at each scroll. */
|
|
278
313
|
private anchor;
|
|
@@ -327,7 +362,16 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
327
362
|
private successorOf;
|
|
328
363
|
/** Make a queued correction, after the commit that moved the rows has gone to native. */
|
|
329
364
|
private flushScroll;
|
|
365
|
+
/** In development, a list with rows that stays at zero size; see `ZeroSizeWarning`. */
|
|
366
|
+
private readonly zeroSize;
|
|
330
367
|
constructor();
|
|
368
|
+
/** Whether the viewport has been laid out yet, so a zero in it is a measurement. */
|
|
369
|
+
private viewportMeasured;
|
|
370
|
+
/**
|
|
371
|
+
* In development, rows that arrive in a list already measured at zero, or go from one, with no
|
|
372
|
+
* new layout to say so: an update to `items` alone does not lay the viewport out again.
|
|
373
|
+
*/
|
|
374
|
+
private checkZeroSize;
|
|
331
375
|
/** The rows to render, and the edges of the run they sit in. */
|
|
332
376
|
private readonly span;
|
|
333
377
|
readonly window: import("@angular/core").Signal<VirtualRow<T>[]>;
|
|
@@ -479,7 +523,7 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
479
523
|
y?: number;
|
|
480
524
|
};
|
|
481
525
|
}>): void;
|
|
482
|
-
/**
|
|
526
|
+
/** A pinned row's translate, settled once scrolling pauses: see `STICKY_SETTLE_MS`. */
|
|
483
527
|
private settleAfterPause;
|
|
484
528
|
protected onLayout(event: NativeSyntheticEvent<{
|
|
485
529
|
layout?: {
|
|
@@ -508,7 +552,7 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
508
552
|
}>): void;
|
|
509
553
|
private checkEnd;
|
|
510
554
|
static ɵfac: i0.ɵɵFactoryDeclaration<VirtualList<any>, never>;
|
|
511
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<VirtualList<any>, "virtual-list", ["virtualList"], { "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": false; "isSignal": true; }; "estimatedItemHeight": { "alias": "estimatedItemHeight"; "required": false; "isSignal": true; }; "keyExtractor": { "alias": "keyExtractor"; "required": false; "isSignal": true; }; "itemType": { "alias": "itemType"; "required": false; "isSignal": true; }; "maintainVisibleContentPosition": { "alias": "maintainVisibleContentPosition"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "endReachedThreshold": { "alias": "endReachedThreshold"; "required": false; "isSignal": true; }; "horizontal": { "alias": "horizontal"; "required": false; "isSignal": true; }; "inverted": { "alias": "inverted"; "required": false; "isSignal": true; }; "stickyIndices": { "alias": "stickyIndices"; "required": false; "isSignal": true; }; "stickyHeader": { "alias": "stickyHeader"; "required": false; "isSignal": true; }; "keyboardShouldPersistTaps": { "alias": "keyboardShouldPersistTaps"; "required": false; "isSignal": true; }; "itemVisiblePercentThreshold": { "alias": "itemVisiblePercentThreshold"; "required": false; "isSignal": true; }; }, { "endReached": "endReached"; "viewableItemsChanged": "viewableItemsChanged"; }, ["separator", "refreshControl"], ["refresh-control", "[listHeader]", "*", "[listFooter]"], true, never>;
|
|
555
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<VirtualList<any>, "virtual-list", ["virtualList"], { "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": false; "isSignal": true; }; "estimatedItemHeight": { "alias": "estimatedItemHeight"; "required": false; "isSignal": true; }; "contentPadding": { "alias": "contentPadding"; "required": false; "isSignal": true; }; "rowGap": { "alias": "rowGap"; "required": false; "isSignal": true; }; "keyExtractor": { "alias": "keyExtractor"; "required": false; "isSignal": true; }; "itemType": { "alias": "itemType"; "required": false; "isSignal": true; }; "maintainVisibleContentPosition": { "alias": "maintainVisibleContentPosition"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "endReachedThreshold": { "alias": "endReachedThreshold"; "required": false; "isSignal": true; }; "horizontal": { "alias": "horizontal"; "required": false; "isSignal": true; }; "inverted": { "alias": "inverted"; "required": false; "isSignal": true; }; "stickyIndices": { "alias": "stickyIndices"; "required": false; "isSignal": true; }; "stickyHeader": { "alias": "stickyHeader"; "required": false; "isSignal": true; }; "keyboardShouldPersistTaps": { "alias": "keyboardShouldPersistTaps"; "required": false; "isSignal": true; }; "itemVisiblePercentThreshold": { "alias": "itemVisiblePercentThreshold"; "required": false; "isSignal": true; }; }, { "endReached": "endReached"; "viewableItemsChanged": "viewableItemsChanged"; }, ["separator", "refreshControl"], ["refresh-control", "[listHeader]", "*", "[listFooter]"], true, never>;
|
|
512
556
|
}
|
|
513
557
|
/**
|
|
514
558
|
* Put on each row's view: `<view [virtualListRow]="row">`. Applies the row's style, and reports
|
package/dist/virtual-list.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Component, DestroyRef, Directive, ElementRef, TemplateRef, afterEveryRender, booleanAttribute, computed, contentChild, inject, input, linkedSignal, numberAttribute, output, signal, viewChild, } from '@angular/core';
|
|
1
|
+
import { Component, DestroyRef, Directive, ElementRef, TemplateRef, afterEveryRender, booleanAttribute, computed, contentChild, effect, inject, input, linkedSignal, numberAttribute, output, signal, untracked, viewChild, } from '@angular/core';
|
|
2
2
|
import { nativePlatform, pinnedRange, } from '@ng-native/fabric';
|
|
3
3
|
import { HeightIndex } from "./height-index.js";
|
|
4
4
|
import { dismissKeyboardOnTap } from "./keyboard-taps.js";
|
|
5
5
|
import { RefreshControl } from "./refresh-control.js";
|
|
6
|
+
import { STICKY_SETTLE_MS } from "./sticky-headers.js";
|
|
6
7
|
import { TemplateSlot } from "./template-slot.js";
|
|
8
|
+
import { ZeroSizeWarning, checksZeroSize, written } from "./zero-size-warning.js";
|
|
7
9
|
import { View } from "./view.js";
|
|
8
10
|
import { ScrollViewProps } from "./scroll-view-props.js";
|
|
9
11
|
import * as i0 from "@angular/core";
|
|
@@ -111,6 +113,15 @@ export class VirtualList extends ScrollViewProps {
|
|
|
111
113
|
*/
|
|
112
114
|
estimatedItemHeight = input(50, /* @ts-ignore */
|
|
113
115
|
...(ngDevMode ? [{ debugName: "estimatedItemHeight" }] : /* istanbul ignore next */ []));
|
|
116
|
+
/**
|
|
117
|
+
* Space around the rows, inside the scrolling content. The leading side comes before the first
|
|
118
|
+
* row and the trailing side after the last; the sides across the axis inset every row. The
|
|
119
|
+
* header and footer sit outside it, as FlatList's do outside `contentContainerStyle`.
|
|
120
|
+
*/
|
|
121
|
+
contentPadding = input(0, /* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [{ debugName: "contentPadding" }] : /* istanbul ignore next */ []));
|
|
123
|
+
/** Space between one row and the next, along the axis: not before the first or after the last. */
|
|
124
|
+
rowGap = input(0, { ...(ngDevMode ? { debugName: "rowGap" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
114
125
|
/**
|
|
115
126
|
* An item's identity, as FlatList's `keyExtractor`: what a measured height, a slot and a held
|
|
116
127
|
* position follow across an insert. Without it an item is its own key, which is enough for items
|
|
@@ -201,6 +212,25 @@ export class VirtualList extends ScrollViewProps {
|
|
|
201
212
|
/** Rows size themselves and are laid out in flow, rather than placed at a known offset. */
|
|
202
213
|
measuring = computed(() => this.itemHeight() === undefined, /* @ts-ignore */
|
|
203
214
|
...(ngDevMode ? [{ debugName: "measuring" }] : /* istanbul ignore next */ []));
|
|
215
|
+
/** The padding on each side. */
|
|
216
|
+
edges = computed(() => {
|
|
217
|
+
const padding = this.contentPadding();
|
|
218
|
+
if (typeof padding === 'number') {
|
|
219
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
220
|
+
}
|
|
221
|
+
const { top = 0, right = 0, bottom = 0, left = 0 } = padding ?? {};
|
|
222
|
+
return { top, right, bottom, left };
|
|
223
|
+
}, /* @ts-ignore */
|
|
224
|
+
...(ngDevMode ? [{ debugName: "edges" }] : /* istanbul ignore next */ []));
|
|
225
|
+
/** The padding before the first row, along the axis. */
|
|
226
|
+
lead = computed(() => this.horizontal() ? this.edges().left : this.edges().top, /* @ts-ignore */
|
|
227
|
+
...(ngDevMode ? [{ debugName: "lead" }] : /* istanbul ignore next */ []));
|
|
228
|
+
/** The rows' insets across the axis, as the props that place an absolutely positioned row. */
|
|
229
|
+
across = computed(() => {
|
|
230
|
+
const { top, right, bottom, left } = this.edges();
|
|
231
|
+
return this.horizontal() ? { top, bottom } : { left, right };
|
|
232
|
+
}, /* @ts-ignore */
|
|
233
|
+
...(ngDevMode ? [{ debugName: "across" }] : /* istanbul ignore next */ []));
|
|
204
234
|
/** Heights native has reported, by key. */
|
|
205
235
|
measured = new Map();
|
|
206
236
|
/** Bumped when a measurement changes, which is what re-sizes the list. */
|
|
@@ -225,19 +255,22 @@ export class VirtualList extends ScrollViewProps {
|
|
|
225
255
|
const height = this.itemHeight();
|
|
226
256
|
if (typeof height === 'number')
|
|
227
257
|
return null;
|
|
258
|
+
// Each row's entry is its size and the gap after it, so where a row starts counts the gaps
|
|
259
|
+
// before it without a second table.
|
|
260
|
+
const gap = this.rowGap();
|
|
228
261
|
const items = this.items();
|
|
229
262
|
const sizes = new Float64Array(items.length);
|
|
230
263
|
if (height) {
|
|
231
264
|
for (let i = 0; i < items.length; i++)
|
|
232
|
-
sizes[i] = height(items[i], i);
|
|
265
|
+
sizes[i] = height(items[i], i) + gap;
|
|
233
266
|
return new HeightIndex(sizes);
|
|
234
267
|
}
|
|
235
268
|
const estimate = this.estimatedItemHeight();
|
|
236
269
|
const keys = this.keys();
|
|
237
270
|
for (let i = 0; i < items.length; i++) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
271
|
+
const size = this.measured.get(keys ? keys[i] : items[i]) ??
|
|
272
|
+
(typeof estimate === 'number' ? estimate : estimate(items[i], i));
|
|
273
|
+
sizes[i] = size + gap;
|
|
241
274
|
}
|
|
242
275
|
return new HeightIndex(sizes);
|
|
243
276
|
}, /* @ts-ignore */
|
|
@@ -258,14 +291,23 @@ export class VirtualList extends ScrollViewProps {
|
|
|
258
291
|
this.measuredVersion();
|
|
259
292
|
return table;
|
|
260
293
|
}
|
|
261
|
-
/**
|
|
294
|
+
/**
|
|
295
|
+
* The list's own total extent along its axis: the padding at both ends, the rows, and a gap
|
|
296
|
+
* between each two of them.
|
|
297
|
+
*/
|
|
262
298
|
extent = computed(() => {
|
|
263
299
|
const table = this.layout();
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
300
|
+
const count = this.items().length;
|
|
301
|
+
const rows = table ? table.total : count * this.stride();
|
|
302
|
+
const { top, right, bottom, left } = this.edges();
|
|
303
|
+
const padding = this.horizontal() ? left + right : top + bottom;
|
|
304
|
+
return padding + (count ? rows - this.rowGap() : 0);
|
|
267
305
|
}, /* @ts-ignore */
|
|
268
306
|
...(ngDevMode ? [{ debugName: "extent" }] : /* istanbul ignore next */ []));
|
|
307
|
+
/** A fixed-height row and the gap after it. */
|
|
308
|
+
stride() {
|
|
309
|
+
return this.itemHeight() + this.rowGap();
|
|
310
|
+
}
|
|
269
311
|
canvas = computed(() => this.horizontal() ? { width: this.extent(), flexDirection: 'row' } : { height: this.extent() }, /* @ts-ignore */
|
|
270
312
|
...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
|
|
271
313
|
/**
|
|
@@ -292,27 +334,45 @@ export class VirtualList extends ScrollViewProps {
|
|
|
292
334
|
return this.horizontal() ? 'scaleX(-1)' : 'scaleY(-1)';
|
|
293
335
|
}, /* @ts-ignore */
|
|
294
336
|
...(ngDevMode ? [{ debugName: "flipText" }] : /* istanbul ignore next */ []));
|
|
337
|
+
/**
|
|
338
|
+
* What places a row in flow: the gap after it and the padding across the axis, as margins, since
|
|
339
|
+
* native lays the row out. Empty when there are none, so a plain list commits no margins.
|
|
340
|
+
*/
|
|
341
|
+
flowSpacing = computed(() => {
|
|
342
|
+
const gap = this.rowGap();
|
|
343
|
+
const { top, right, bottom, left } = this.edges();
|
|
344
|
+
const spacing = this.horizontal()
|
|
345
|
+
? { marginRight: gap, marginTop: top, marginBottom: bottom }
|
|
346
|
+
: { marginBottom: gap, marginLeft: left, marginRight: right };
|
|
347
|
+
return Object.fromEntries(Object.entries(spacing).filter(([, value]) => value !== 0));
|
|
348
|
+
}, /* @ts-ignore */
|
|
349
|
+
...(ngDevMode ? [{ debugName: "flowSpacing" }] : /* istanbul ignore next */ []));
|
|
295
350
|
/** A row in flow, flipped back when the list is inverted. One object, so rows never re-clone. */
|
|
296
351
|
flowStyle = computed(() => {
|
|
297
352
|
const flip = this.flipText();
|
|
298
|
-
|
|
353
|
+
const spacing = this.flowSpacing();
|
|
354
|
+
if (!flip && Object.keys(spacing).length === 0)
|
|
355
|
+
return IN_FLOW;
|
|
356
|
+
return flip ? { ...spacing, transform: flip } : spacing;
|
|
299
357
|
}, /* @ts-ignore */
|
|
300
358
|
...(ngDevMode ? [{ debugName: "flowStyle" }] : /* istanbul ignore next */ []));
|
|
301
|
-
/** Top of a row. */
|
|
359
|
+
/** Top of a row: the leading padding, and every row and gap before it. */
|
|
302
360
|
topOf(index) {
|
|
303
361
|
const table = this.layout();
|
|
304
|
-
return table ? table.topOf(index) : index * this.
|
|
362
|
+
return this.lead() + (table ? table.topOf(index) : index * this.stride());
|
|
305
363
|
}
|
|
364
|
+
/** A row's own size, without the gap after it. */
|
|
306
365
|
heightOf(index) {
|
|
307
366
|
const table = this.layout();
|
|
308
|
-
return table ? table.heightOf(index) : this.itemHeight();
|
|
367
|
+
return table ? table.heightOf(index) - this.rowGap() : this.itemHeight();
|
|
309
368
|
}
|
|
310
|
-
/** First row whose bottom is past `y`. */
|
|
369
|
+
/** First row whose bottom, or the gap after it, is past `y`. */
|
|
311
370
|
indexAt(y) {
|
|
312
371
|
const table = this.layout();
|
|
372
|
+
const along = y - this.lead();
|
|
313
373
|
if (!table)
|
|
314
|
-
return Math.max(0, Math.floor(
|
|
315
|
-
return table.indexAt(
|
|
374
|
+
return Math.max(0, Math.floor(along / this.stride()));
|
|
375
|
+
return table.indexAt(along);
|
|
316
376
|
}
|
|
317
377
|
// --- holding position -------------------------------------------------------------------
|
|
318
378
|
/** The row the viewport's leading edge is in, taken at each scroll. */
|
|
@@ -450,6 +510,10 @@ export class VirtualList extends ScrollViewProps {
|
|
|
450
510
|
else
|
|
451
511
|
this.scrollToOffset({ offset: this.headerHeight() + pending.offset, animated: false });
|
|
452
512
|
}
|
|
513
|
+
/** In development, a list with rows that stays at zero size; see `ZeroSizeWarning`. */
|
|
514
|
+
zeroSize = checksZeroSize()
|
|
515
|
+
? new ZeroSizeWarning(() => written(this.node), () => !!this.horizontal())
|
|
516
|
+
: null;
|
|
453
517
|
constructor() {
|
|
454
518
|
super();
|
|
455
519
|
afterEveryRender(() => {
|
|
@@ -461,9 +525,25 @@ export class VirtualList extends ScrollViewProps {
|
|
|
461
525
|
inject(DestroyRef).onDestroy(() => {
|
|
462
526
|
stop();
|
|
463
527
|
clearTimeout(this.settleTimer);
|
|
528
|
+
this.zeroSize?.stop();
|
|
464
529
|
for (const held of this.drives.values())
|
|
465
530
|
held.drive.stop();
|
|
466
531
|
});
|
|
532
|
+
if (this.zeroSize)
|
|
533
|
+
this.checkZeroSize(this.zeroSize);
|
|
534
|
+
}
|
|
535
|
+
/** Whether the viewport has been laid out yet, so a zero in it is a measurement. */
|
|
536
|
+
viewportMeasured = false;
|
|
537
|
+
/**
|
|
538
|
+
* In development, rows that arrive in a list already measured at zero, or go from one, with no
|
|
539
|
+
* new layout to say so: an update to `items` alone does not lay the viewport out again.
|
|
540
|
+
*/
|
|
541
|
+
checkZeroSize(warning) {
|
|
542
|
+
effect(() => {
|
|
543
|
+
const hasRows = this.items().length > 0;
|
|
544
|
+
if (this.viewportMeasured)
|
|
545
|
+
warning.laidOut(untracked(this.viewport), hasRows);
|
|
546
|
+
});
|
|
467
547
|
}
|
|
468
548
|
// --- the window ---------------------------------------------------------------------------
|
|
469
549
|
/** The rows to render, and the edges of the run they sit in. */
|
|
@@ -520,7 +600,7 @@ export class VirtualList extends ScrollViewProps {
|
|
|
520
600
|
let lead = span ? this.topOf(span.first) : 0;
|
|
521
601
|
const pinned = this.pinnedIndex();
|
|
522
602
|
if (span && pinned !== null && pinned < span.first) {
|
|
523
|
-
lead = this.nativeSticky ? this.topOf(pinned) : lead - this.heightOf(pinned);
|
|
603
|
+
lead = this.nativeSticky ? this.topOf(pinned) : lead - this.heightOf(pinned) - this.rowGap();
|
|
524
604
|
}
|
|
525
605
|
return this.horizontal() ? { width: Math.max(0, lead) } : { height: Math.max(0, lead) };
|
|
526
606
|
}, /* @ts-ignore */
|
|
@@ -543,7 +623,7 @@ export class VirtualList extends ScrollViewProps {
|
|
|
543
623
|
style: this.nativeSticky
|
|
544
624
|
? this.stickyStyle(pinned)
|
|
545
625
|
: this.measuring()
|
|
546
|
-
? this.pinnedFlowStyle(this.topOf(first) - this.heightOf(pinned))
|
|
626
|
+
? this.pinnedFlowStyle(this.topOf(first) - this.heightOf(pinned) - this.rowGap())
|
|
547
627
|
: this.slotStyle(pinned, this.offset()),
|
|
548
628
|
}));
|
|
549
629
|
}
|
|
@@ -568,11 +648,12 @@ export class VirtualList extends ScrollViewProps {
|
|
|
568
648
|
*/
|
|
569
649
|
leadWindow(rows, pinned, first) {
|
|
570
650
|
const lead = rows[1];
|
|
571
|
-
|
|
572
|
-
|
|
651
|
+
// The pinned row's own gap already follows it in flow.
|
|
652
|
+
const between = this.topOf(first) - this.topOf(pinned) - this.heightOf(pinned) - this.rowGap();
|
|
653
|
+
if (!lead || between <= 0)
|
|
573
654
|
return;
|
|
574
655
|
const margin = this.horizontal() ? 'marginLeft' : 'marginTop';
|
|
575
|
-
rows[1] = this.hold({ ...lead, style: { ...lead.style, [margin]:
|
|
656
|
+
rows[1] = this.hold({ ...lead, style: { ...lead.style, [margin]: between } });
|
|
576
657
|
}
|
|
577
658
|
styleFor(index, pinned) {
|
|
578
659
|
if (this.nativeSticky && this.nextSticky().has(index))
|
|
@@ -587,7 +668,7 @@ export class VirtualList extends ScrollViewProps {
|
|
|
587
668
|
const shift = Math.max(0, this.offset() - at);
|
|
588
669
|
const move = `${this.horizontal() ? 'translateX' : 'translateY'}(${shift}px)`;
|
|
589
670
|
const flip = this.flipText();
|
|
590
|
-
return { zIndex: 1, transform: flip ? `${move} ${flip}` : move };
|
|
671
|
+
return { ...this.flowSpacing(), zIndex: 1, transform: flip ? `${move} ${flip}` : move };
|
|
591
672
|
}
|
|
592
673
|
/**
|
|
593
674
|
* A sticky row native moves: at its own place, with the translate native had reached when
|
|
@@ -595,7 +676,9 @@ export class VirtualList extends ScrollViewProps {
|
|
|
595
676
|
*/
|
|
596
677
|
stickyStyle(index) {
|
|
597
678
|
const horizontal = this.horizontal();
|
|
598
|
-
const style = this.measuring()
|
|
679
|
+
const style = this.measuring()
|
|
680
|
+
? { ...this.flowSpacing() }
|
|
681
|
+
: { ...this.slotStyle(index) };
|
|
599
682
|
style['zIndex'] = 1;
|
|
600
683
|
const top = this.topOf(index);
|
|
601
684
|
const shift = Math.min(Math.max(0, this.settled() - top), this.stopOf(index) - top);
|
|
@@ -740,7 +823,7 @@ export class VirtualList extends ScrollViewProps {
|
|
|
740
823
|
const index = this.indexOfKey()?.get(row.key);
|
|
741
824
|
if (index === undefined)
|
|
742
825
|
return;
|
|
743
|
-
this.table()?.set(index, size);
|
|
826
|
+
this.table()?.set(index, size + this.rowGap());
|
|
744
827
|
this.measuredVersion.update((version) => version + 1);
|
|
745
828
|
}
|
|
746
829
|
/** Drop the measurements of items long gone, so churn cannot grow the map without bound. */
|
|
@@ -805,16 +888,16 @@ export class VirtualList extends ScrollViewProps {
|
|
|
805
888
|
const size = this.heightOf(row.index);
|
|
806
889
|
const horizontal = this.horizontal();
|
|
807
890
|
const flip = this.flipText();
|
|
891
|
+
const across = this.across();
|
|
808
892
|
const cached = this.measuredGaps.get(row.key);
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
893
|
+
const placed = horizontal
|
|
894
|
+
? { ...across, left: start, width: size, transform: flip }
|
|
895
|
+
: { ...across, top: start, height: size, transform: flip };
|
|
896
|
+
if (cached && holds(cached, placed))
|
|
813
897
|
return cached;
|
|
814
|
-
}
|
|
815
898
|
const style = horizontal
|
|
816
|
-
? { position: 'absolute',
|
|
817
|
-
: { position: 'absolute',
|
|
899
|
+
? { position: 'absolute', ...across, left: start, width: size, flexDirection: 'row' }
|
|
900
|
+
: { position: 'absolute', ...across, top: start, height: size };
|
|
818
901
|
style['justifyContent'] = 'flex-end';
|
|
819
902
|
style['pointerEvents'] = 'box-none';
|
|
820
903
|
if (flip)
|
|
@@ -953,18 +1036,16 @@ export class VirtualList extends ScrollViewProps {
|
|
|
953
1036
|
const key = horizontal ? 'left' : 'top';
|
|
954
1037
|
const extent = horizontal ? 'width' : 'height';
|
|
955
1038
|
const flip = this.flipText();
|
|
1039
|
+
const across = this.across();
|
|
956
1040
|
let style = this.slots.get(index);
|
|
957
1041
|
// The flip is part of the comparison because turning the list over changes every row, and a
|
|
958
1042
|
// cached slot would otherwise keep the orientation it was built with.
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
style[key] !== start ||
|
|
962
|
-
style['transform'] !== flip ||
|
|
963
|
-
pinnedAt !== undefined;
|
|
1043
|
+
const placed = { ...across, [extent]: size, [key]: start, transform: flip };
|
|
1044
|
+
const stale = !style || pinnedAt !== undefined || !holds(style, placed);
|
|
964
1045
|
if (stale) {
|
|
965
1046
|
style = horizontal
|
|
966
|
-
? { position: 'absolute',
|
|
967
|
-
: { position: 'absolute',
|
|
1047
|
+
? { position: 'absolute', ...across, left: start, width: size }
|
|
1048
|
+
: { position: 'absolute', ...across, top: start, height: size };
|
|
968
1049
|
// A pinned row draws over the ones sliding under it; the transform is the row's half of
|
|
969
1050
|
// the inversion.
|
|
970
1051
|
if (pinnedAt !== undefined)
|
|
@@ -999,14 +1080,16 @@ export class VirtualList extends ScrollViewProps {
|
|
|
999
1080
|
this.checkEnd(along);
|
|
1000
1081
|
this.announceViewable(content);
|
|
1001
1082
|
}
|
|
1002
|
-
/**
|
|
1083
|
+
/** A pinned row's translate, settled once scrolling pauses: see `STICKY_SETTLE_MS`. */
|
|
1003
1084
|
settleAfterPause(content) {
|
|
1004
1085
|
clearTimeout(this.settleTimer);
|
|
1005
|
-
this.settleTimer = setTimeout(() => this.settled.set(content),
|
|
1086
|
+
this.settleTimer = setTimeout(() => this.settled.set(content), STICKY_SETTLE_MS);
|
|
1006
1087
|
}
|
|
1007
1088
|
onLayout(event) {
|
|
1008
1089
|
const layout = event.nativeEvent?.layout;
|
|
1009
1090
|
this.viewport.set((this.horizontal() ? layout?.width : layout?.height) ?? 0);
|
|
1091
|
+
this.viewportMeasured = true;
|
|
1092
|
+
this.zeroSize?.laidOut(this.viewport(), this.items().length > 0);
|
|
1010
1093
|
this.announceViewable(this.lastOffset);
|
|
1011
1094
|
}
|
|
1012
1095
|
/** The header's extent along the axis, which every row sits after. */
|
|
@@ -1054,7 +1137,7 @@ export class VirtualList extends ScrollViewProps {
|
|
|
1054
1137
|
this.endReached.emit({ distanceFromEnd });
|
|
1055
1138
|
}
|
|
1056
1139
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: VirtualList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1057
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: VirtualList, isStandalone: true, selector: "virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, estimatedItemHeight: { classPropertyName: "estimatedItemHeight", publicName: "estimatedItemHeight", isSignal: true, isRequired: false, transformFunction: null }, keyExtractor: { classPropertyName: "keyExtractor", publicName: "keyExtractor", isSignal: true, isRequired: false, transformFunction: null }, itemType: { classPropertyName: "itemType", publicName: "itemType", isSignal: true, isRequired: false, transformFunction: null }, maintainVisibleContentPosition: { classPropertyName: "maintainVisibleContentPosition", publicName: "maintainVisibleContentPosition", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, endReachedThreshold: { classPropertyName: "endReachedThreshold", publicName: "endReachedThreshold", isSignal: true, isRequired: false, transformFunction: null }, horizontal: { classPropertyName: "horizontal", publicName: "horizontal", isSignal: true, isRequired: false, transformFunction: null }, inverted: { classPropertyName: "inverted", publicName: "inverted", isSignal: true, isRequired: false, transformFunction: null }, stickyIndices: { classPropertyName: "stickyIndices", publicName: "stickyIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, keyboardShouldPersistTaps: { classPropertyName: "keyboardShouldPersistTaps", publicName: "keyboardShouldPersistTaps", isSignal: true, isRequired: false, transformFunction: null }, itemVisiblePercentThreshold: { classPropertyName: "itemVisiblePercentThreshold", publicName: "itemVisiblePercentThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { endReached: "endReached", viewableItemsChanged: "viewableItemsChanged" }, host: { listeners: { "scroll": "onScroll($event)", "layout": "onLayout($event)", "contentSizeChange": "onContentSize($event)" }, properties: { "flexDirection": "horizontal() ? 'row' : 'column'", "horizontal": "horizontal()", "nestedScrollEnabled": "nestedScroll()", "transform": "flip()", "scrollEventThrottle": "scrollEventThrottle()" } }, queries: [{ propertyName: "separator", first: true, predicate: VirtualListSeparator, descendants: true, isSignal: true }, { propertyName: "refreshControl", first: true, predicate: RefreshControl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "canvasView", first: true, predicate: ["rows"], descendants: true, read: ElementRef, isSignal: true }], exportAs: ["virtualList"], usesInheritance: true, ngImport: i0, template: `
|
|
1140
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: VirtualList, isStandalone: true, selector: "virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, estimatedItemHeight: { classPropertyName: "estimatedItemHeight", publicName: "estimatedItemHeight", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, rowGap: { classPropertyName: "rowGap", publicName: "rowGap", isSignal: true, isRequired: false, transformFunction: null }, keyExtractor: { classPropertyName: "keyExtractor", publicName: "keyExtractor", isSignal: true, isRequired: false, transformFunction: null }, itemType: { classPropertyName: "itemType", publicName: "itemType", isSignal: true, isRequired: false, transformFunction: null }, maintainVisibleContentPosition: { classPropertyName: "maintainVisibleContentPosition", publicName: "maintainVisibleContentPosition", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, endReachedThreshold: { classPropertyName: "endReachedThreshold", publicName: "endReachedThreshold", isSignal: true, isRequired: false, transformFunction: null }, horizontal: { classPropertyName: "horizontal", publicName: "horizontal", isSignal: true, isRequired: false, transformFunction: null }, inverted: { classPropertyName: "inverted", publicName: "inverted", isSignal: true, isRequired: false, transformFunction: null }, stickyIndices: { classPropertyName: "stickyIndices", publicName: "stickyIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, keyboardShouldPersistTaps: { classPropertyName: "keyboardShouldPersistTaps", publicName: "keyboardShouldPersistTaps", isSignal: true, isRequired: false, transformFunction: null }, itemVisiblePercentThreshold: { classPropertyName: "itemVisiblePercentThreshold", publicName: "itemVisiblePercentThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { endReached: "endReached", viewableItemsChanged: "viewableItemsChanged" }, host: { listeners: { "scroll": "onScroll($event)", "layout": "onLayout($event)", "contentSizeChange": "onContentSize($event)" }, properties: { "flexDirection": "horizontal() ? 'row' : 'column'", "horizontal": "horizontal()", "nestedScrollEnabled": "nestedScroll()", "transform": "flip()", "scrollEventThrottle": "scrollEventThrottle()" } }, queries: [{ propertyName: "separator", first: true, predicate: VirtualListSeparator, descendants: true, isSignal: true }, { propertyName: "refreshControl", first: true, predicate: RefreshControl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "canvasView", first: true, predicate: ["rows"], descendants: true, read: ElementRef, isSignal: true }], exportAs: ["virtualList"], usesInheritance: true, ngImport: i0, template: `
|
|
1058
1141
|
<ng-content select="refresh-control" />
|
|
1059
1142
|
<view [style]="headerStyle()" (layout)="onHeaderLayout($event)"
|
|
1060
1143
|
><ng-content select="[listHeader]"
|
|
@@ -1122,7 +1205,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
1122
1205
|
'(contentSizeChange)': 'onContentSize($event)',
|
|
1123
1206
|
},
|
|
1124
1207
|
}]
|
|
1125
|
-
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], estimatedItemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "estimatedItemHeight", required: false }] }], keyExtractor: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyExtractor", required: false }] }], itemType: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemType", required: false }] }], maintainVisibleContentPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "maintainVisibleContentPosition", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], endReachedThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "endReachedThreshold", required: false }] }], horizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "horizontal", required: false }] }], inverted: [{ type: i0.Input, args: [{ isSignal: true, alias: "inverted", required: false }] }], stickyIndices: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyIndices", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], keyboardShouldPersistTaps: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardShouldPersistTaps", required: false }] }], itemVisiblePercentThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemVisiblePercentThreshold", required: false }] }], endReached: [{ type: i0.Output, args: ["endReached"] }], viewableItemsChanged: [{ type: i0.Output, args: ["viewableItemsChanged"] }], separator: [{ type: i0.ContentChild, args: [i0.forwardRef(() => VirtualListSeparator), { isSignal: true }] }], refreshControl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => RefreshControl), { isSignal: true }] }], canvasView: [{ type: i0.ViewChild, args: ['rows', { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
1208
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], estimatedItemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "estimatedItemHeight", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], rowGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowGap", required: false }] }], keyExtractor: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyExtractor", required: false }] }], itemType: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemType", required: false }] }], maintainVisibleContentPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "maintainVisibleContentPosition", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], endReachedThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "endReachedThreshold", required: false }] }], horizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "horizontal", required: false }] }], inverted: [{ type: i0.Input, args: [{ isSignal: true, alias: "inverted", required: false }] }], stickyIndices: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyIndices", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], keyboardShouldPersistTaps: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardShouldPersistTaps", required: false }] }], itemVisiblePercentThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemVisiblePercentThreshold", required: false }] }], endReached: [{ type: i0.Output, args: ["endReached"] }], viewableItemsChanged: [{ type: i0.Output, args: ["viewableItemsChanged"] }], separator: [{ type: i0.ContentChild, args: [i0.forwardRef(() => VirtualListSeparator), { isSignal: true }] }], refreshControl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => RefreshControl), { isSignal: true }] }], canvasView: [{ type: i0.ViewChild, args: ['rows', { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
1209
|
+
/** Whether a cached slot style already has every one of these props. */
|
|
1210
|
+
function holds(style, props) {
|
|
1211
|
+
return Object.entries(props).every(([prop, value]) => style[prop] === value);
|
|
1212
|
+
}
|
|
1126
1213
|
/**
|
|
1127
1214
|
* Put on each row's view: `<view [virtualListRow]="row">`. Applies the row's style, and reports
|
|
1128
1215
|
* the row's layout to the list, which is how a list of rows that size themselves learns how tall
|