react-virtual-renderer 1.1.2 → 1.1.4
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/App.d.ts.map +1 -1
- package/dist/App.js +65 -3
- package/dist/App.js.map +1 -1
- package/dist/components/VirtualGrid.d.ts +30 -0
- package/dist/components/VirtualGrid.d.ts.map +1 -1
- package/dist/components/VirtualGrid.js +147 -117
- package/dist/components/VirtualGrid.js.map +1 -1
- package/dist/components/VirtualList.d.ts +0 -4
- package/dist/components/VirtualList.d.ts.map +1 -1
- package/dist/components/VirtualList.js +66 -47
- package/dist/components/VirtualList.js.map +1 -1
- package/dist/components/VirtualLiveRegion.d.ts +12 -0
- package/dist/components/VirtualLiveRegion.d.ts.map +1 -0
- package/dist/components/VirtualLiveRegion.js +27 -0
- package/dist/components/VirtualLiveRegion.js.map +1 -0
- package/dist/core/FenwickTree.d.ts +47 -0
- package/dist/core/FenwickTree.d.ts.map +1 -0
- package/dist/core/FenwickTree.js +206 -0
- package/dist/core/FenwickTree.js.map +1 -0
- package/dist/core/GridLayoutEngine.d.ts +64 -0
- package/dist/core/GridLayoutEngine.d.ts.map +1 -0
- package/dist/core/GridLayoutEngine.js +153 -0
- package/dist/core/GridLayoutEngine.js.map +1 -0
- package/dist/core/GroupedListEngine.d.ts +21 -0
- package/dist/core/GroupedListEngine.d.ts.map +1 -0
- package/dist/core/GroupedListEngine.js +22 -0
- package/dist/core/GroupedListEngine.js.map +1 -0
- package/dist/core/ListController.d.ts +31 -0
- package/dist/core/ListController.d.ts.map +1 -0
- package/dist/core/ListController.js +71 -0
- package/dist/core/ListController.js.map +1 -0
- package/dist/core/MeasurementSystem.d.ts +54 -6
- package/dist/core/MeasurementSystem.d.ts.map +1 -1
- package/dist/core/MeasurementSystem.js +113 -9
- package/dist/core/MeasurementSystem.js.map +1 -1
- package/dist/core/VirtualizationEngine.d.ts +28 -5
- package/dist/core/VirtualizationEngine.d.ts.map +1 -1
- package/dist/core/VirtualizationEngine.js +58 -52
- package/dist/core/VirtualizationEngine.js.map +1 -1
- package/dist/examples/DynamicHeightLists.d.ts +2 -0
- package/dist/examples/DynamicHeightLists.d.ts.map +1 -0
- package/dist/examples/DynamicHeightLists.js +42 -0
- package/dist/examples/DynamicHeightLists.js.map +1 -0
- package/dist/examples/FixedGrid.d.ts +2 -0
- package/dist/examples/FixedGrid.d.ts.map +1 -0
- package/dist/examples/FixedGrid.js +40 -0
- package/dist/examples/FixedGrid.js.map +1 -0
- package/dist/examples/GroupedContactList.d.ts +2 -0
- package/dist/examples/GroupedContactList.d.ts.map +1 -0
- package/dist/examples/GroupedContactList.js +20 -0
- package/dist/examples/GroupedContactList.js.map +1 -0
- package/dist/examples/InfiniteScrollLists.d.ts +2 -0
- package/dist/examples/InfiniteScrollLists.d.ts.map +1 -0
- package/dist/examples/InfiniteScrollLists.js +93 -0
- package/dist/examples/InfiniteScrollLists.js.map +1 -0
- package/dist/examples/MasonaryGrid.d.ts +2 -0
- package/dist/examples/MasonaryGrid.d.ts.map +1 -0
- package/dist/examples/MasonaryGrid.js +36 -0
- package/dist/examples/MasonaryGrid.js.map +1 -0
- package/dist/examples/PerfHarness.d.ts +2 -0
- package/dist/examples/PerfHarness.d.ts.map +1 -0
- package/dist/examples/PerfHarness.js +131 -0
- package/dist/examples/PerfHarness.js.map +1 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.d.ts +32 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.d.ts.map +1 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.js +89 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.js.map +1 -0
- package/dist/hooks/useInfiniteScroll.d.ts +37 -14
- package/dist/hooks/useInfiniteScroll.d.ts.map +1 -1
- package/dist/hooks/useInfiniteScroll.js +48 -38
- package/dist/hooks/useInfiniteScroll.js.map +1 -1
- package/dist/hooks/useRevoingTabIndex.d.ts +11 -0
- package/dist/hooks/useRevoingTabIndex.d.ts.map +1 -0
- package/dist/hooks/useRevoingTabIndex.js +28 -0
- package/dist/hooks/useRevoingTabIndex.js.map +1 -0
- package/dist/hooks/useVirtualAllyProps.d.ts +23 -0
- package/dist/hooks/useVirtualAllyProps.d.ts.map +1 -0
- package/dist/hooks/useVirtualAllyProps.js +25 -0
- package/dist/hooks/useVirtualAllyProps.js.map +1 -0
- package/dist/hooks/useVirtualList.d.ts +82 -4
- package/dist/hooks/useVirtualList.d.ts.map +1 -1
- package/dist/hooks/useVirtualList.js +220 -92
- package/dist/hooks/useVirtualList.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental, append-only masonry/grid position engine.
|
|
3
|
+
*
|
|
4
|
+
* PERFORMANCE: the previous VirtualGrid recomputed EVERY item's
|
|
5
|
+
* position from scratch (a full O(n) pass) whenever the items array
|
|
6
|
+
* changed identity - including the ordinary infinite-scroll case of
|
|
7
|
+
* appending 20 new items to an existing 50,000-item masonry board,
|
|
8
|
+
* which turned a single "load more" into an O(n) pass over everything
|
|
9
|
+
* already laid out.
|
|
10
|
+
*
|
|
11
|
+
* This engine keeps running state - per-column heights for masonry, a
|
|
12
|
+
* running row-offset for dynamic-height grid rows - and only computes
|
|
13
|
+
* positions for items it hasn't seen before. Appending k new items
|
|
14
|
+
* costs O(k), never O(n + k). A full reset (columns/width/gap/layout
|
|
15
|
+
* changed) is the only path that pays an O(n) cost, same as any other
|
|
16
|
+
* virtualization library when the layout itself is invalidated.
|
|
17
|
+
*
|
|
18
|
+
* NOTE ON VIRTUAL_GRID'S FAST PATH: this engine is only used for
|
|
19
|
+
* masonry layout, or grid layout with a per-index rowHeight function.
|
|
20
|
+
* Plain grid layout with a fixed numeric rowHeight never touches this
|
|
21
|
+
* file at all - VirtualGrid computes those positions with direct O(1)
|
|
22
|
+
* arithmetic and no array (see VirtualGrid.tsx), which is faster still.
|
|
23
|
+
*/
|
|
24
|
+
export class GridLayoutEngine {
|
|
25
|
+
constructor() {
|
|
26
|
+
Object.defineProperty(this, "positions", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: []
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "colHeights", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: []
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "rowOffsets", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: [0]
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "currentRowMax", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: 0
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(this, "maxBottom", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
writable: true,
|
|
54
|
+
value: 0
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(this, "params", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: null
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
sameParams(p) {
|
|
64
|
+
if (!this.params)
|
|
65
|
+
return false;
|
|
66
|
+
return (this.params.cols === p.cols &&
|
|
67
|
+
this.params.colWidth === p.colWidth &&
|
|
68
|
+
this.params.gap === p.gap &&
|
|
69
|
+
this.params.layout === p.layout &&
|
|
70
|
+
this.params.rowHeight === p.rowHeight);
|
|
71
|
+
}
|
|
72
|
+
reset(p) {
|
|
73
|
+
this.positions = [];
|
|
74
|
+
this.colHeights = new Array(p.cols).fill(0);
|
|
75
|
+
this.rowOffsets = [0];
|
|
76
|
+
this.currentRowMax = 0;
|
|
77
|
+
this.maxBottom = 0;
|
|
78
|
+
this.params = p;
|
|
79
|
+
}
|
|
80
|
+
sizeOf(rowHeight, index) {
|
|
81
|
+
return typeof rowHeight === 'function' ? rowHeight(index) : rowHeight;
|
|
82
|
+
}
|
|
83
|
+
/** Masonry: place each new item in whichever column is currently shortest. */
|
|
84
|
+
appendMasonry(fromIndex, itemCount, p) {
|
|
85
|
+
for (let i = fromIndex; i < itemCount; i++) {
|
|
86
|
+
const h = this.sizeOf(p.rowHeight, i);
|
|
87
|
+
let minCol = 0;
|
|
88
|
+
for (let c = 1; c < p.cols; c++) {
|
|
89
|
+
if (this.colHeights[c] < this.colHeights[minCol])
|
|
90
|
+
minCol = c;
|
|
91
|
+
}
|
|
92
|
+
const x = minCol * (p.colWidth + p.gap);
|
|
93
|
+
const y = this.colHeights[minCol];
|
|
94
|
+
this.positions.push({ x, y, width: p.colWidth, height: h });
|
|
95
|
+
this.colHeights[minCol] += h + p.gap;
|
|
96
|
+
if (y + h > this.maxBottom)
|
|
97
|
+
this.maxBottom = y + h;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Grid with per-index rowHeight function: rows advance by the
|
|
102
|
+
* tallest item seen in that row so far. `rowOffsets[r]` is only
|
|
103
|
+
* ever written once (when row r's first item is appended), reading
|
|
104
|
+
* `currentRowMax` accumulated while row r-1 was being built - so
|
|
105
|
+
* this stays correct across multiple incremental append() calls,
|
|
106
|
+
* not just a single full pass.
|
|
107
|
+
*/
|
|
108
|
+
appendGrid(fromIndex, itemCount, p) {
|
|
109
|
+
for (let i = fromIndex; i < itemCount; i++) {
|
|
110
|
+
const row = Math.floor(i / p.cols);
|
|
111
|
+
const col = i % p.cols;
|
|
112
|
+
const h = this.sizeOf(p.rowHeight, i);
|
|
113
|
+
while (this.rowOffsets.length <= row) {
|
|
114
|
+
const prevRow = this.rowOffsets.length - 1;
|
|
115
|
+
const gapBefore = prevRow >= 0 ? p.gap : 0;
|
|
116
|
+
this.rowOffsets.push(this.rowOffsets[prevRow] + this.currentRowMax + gapBefore);
|
|
117
|
+
this.currentRowMax = 0;
|
|
118
|
+
}
|
|
119
|
+
const y = this.rowOffsets[row];
|
|
120
|
+
const x = col * (p.colWidth + p.gap);
|
|
121
|
+
this.positions.push({ x, y, width: p.colWidth, height: h });
|
|
122
|
+
this.currentRowMax = Math.max(this.currentRowMax, h);
|
|
123
|
+
if (y + h > this.maxBottom)
|
|
124
|
+
this.maxBottom = y + h;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Ensure positions exist for `itemCount` items under the given
|
|
129
|
+
* params. Existing entries are reused by reference - callers get
|
|
130
|
+
* back the same array instances for indices they've already seen,
|
|
131
|
+
* which keeps this cheap to call on every render.
|
|
132
|
+
*/
|
|
133
|
+
sync(itemCount, p) {
|
|
134
|
+
if (!this.sameParams(p) || itemCount < this.positions.length) {
|
|
135
|
+
this.reset(p);
|
|
136
|
+
}
|
|
137
|
+
const from = this.positions.length;
|
|
138
|
+
if (from < itemCount) {
|
|
139
|
+
if (p.layout === 'masonry') {
|
|
140
|
+
this.appendMasonry(from, itemCount, p);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.appendGrid(from, itemCount, p);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return this.positions;
|
|
147
|
+
}
|
|
148
|
+
/** O(1) - tracked incrementally as items are appended, never rescanned. */
|
|
149
|
+
getTotalHeight() {
|
|
150
|
+
return this.maxBottom;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=GridLayoutEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GridLayoutEngine.js","sourceRoot":"","sources":["../../src/core/GridLayoutEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface GroupDefinition<T> {
|
|
2
|
+
items: T[];
|
|
3
|
+
getGroupKey: (item: T) => string;
|
|
4
|
+
}
|
|
5
|
+
export interface FlattenedRow<T> {
|
|
6
|
+
type: 'header' | 'item';
|
|
7
|
+
groupKey: string;
|
|
8
|
+
item?: T;
|
|
9
|
+
itemIndex?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Flattens grouped data into one virtualizable array with header rows
|
|
13
|
+
* interspersed - VirtualList treats a header as just another row, no
|
|
14
|
+
* special-casing in the engine. Your existing `stickyIndices` prop is
|
|
15
|
+
* what actually pins headers visually while scrolling.
|
|
16
|
+
*/
|
|
17
|
+
export declare function flattenGroups<T>({ items, getGroupKey }: GroupDefinition<T>): {
|
|
18
|
+
rows: FlattenedRow<T>[];
|
|
19
|
+
stickyIndices: number[];
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=GroupedListEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupedListEngine.d.ts","sourceRoot":"","sources":["../../src/core/GroupedListEngine.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,eAAe,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;IAC1E,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CAC3B,CAgBA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flattens grouped data into one virtualizable array with header rows
|
|
3
|
+
* interspersed - VirtualList treats a header as just another row, no
|
|
4
|
+
* special-casing in the engine. Your existing `stickyIndices` prop is
|
|
5
|
+
* what actually pins headers visually while scrolling.
|
|
6
|
+
*/
|
|
7
|
+
export function flattenGroups({ items, getGroupKey }) {
|
|
8
|
+
const rows = [];
|
|
9
|
+
const stickyIndices = [];
|
|
10
|
+
let lastKey = null;
|
|
11
|
+
items.forEach((item, itemIndex) => {
|
|
12
|
+
const key = getGroupKey(item);
|
|
13
|
+
if (key !== lastKey) {
|
|
14
|
+
stickyIndices.push(rows.length);
|
|
15
|
+
rows.push({ type: 'header', groupKey: key });
|
|
16
|
+
lastKey = key;
|
|
17
|
+
}
|
|
18
|
+
rows.push({ type: 'item', groupKey: key, item, itemIndex });
|
|
19
|
+
});
|
|
20
|
+
return { rows, stickyIndices };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=GroupedListEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupedListEngine.js","sourceRoot":"","sources":["../../src/core/GroupedListEngine.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAI,EAAE,KAAK,EAAE,WAAW,EAAsB;IAIvE,MAAM,IAAI,GAAsB,EAAE,CAAC;IACnC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;QAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7C,OAAO,GAAG,GAAG,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ListControllerOptions {
|
|
2
|
+
itemCount: number;
|
|
3
|
+
estimatedItemSize: number;
|
|
4
|
+
overscan?: number;
|
|
5
|
+
}
|
|
6
|
+
type Listener = () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Framework-agnostic controller: owns the engine + scroll state,
|
|
9
|
+
* exposes subscribe/getSnapshot. React binds via useSyncExternalStore;
|
|
10
|
+
* Vue could bind via a reactive ref; Svelte via a store - the engine
|
|
11
|
+
* itself never needs to know any of that exists.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ListController {
|
|
14
|
+
private engine;
|
|
15
|
+
private scrollOffset;
|
|
16
|
+
private containerSize;
|
|
17
|
+
private itemCount;
|
|
18
|
+
private overscan;
|
|
19
|
+
private listeners;
|
|
20
|
+
constructor({ itemCount, estimatedItemSize, overscan }: ListControllerOptions);
|
|
21
|
+
subscribe: (listener: Listener) => () => boolean;
|
|
22
|
+
private notify;
|
|
23
|
+
setScrollOffset(offset: number): void;
|
|
24
|
+
setContainerSize(size: number): void;
|
|
25
|
+
setItemCount(count: number): void;
|
|
26
|
+
recordItemSize(index: number, size: number): void;
|
|
27
|
+
getSnapshot(): import("..").ItemSize[];
|
|
28
|
+
getTotalSize(): number;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=ListController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListController.d.ts","sourceRoot":"","sources":["../../src/core/ListController.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B;;;;;GAKG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAuB;gBAE5B,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAY,EAAE,EAAE,qBAAqB;IAOjF,SAAS,GAAI,UAAU,QAAQ,mBAG7B;IAEF,OAAO,CAAC,MAAM;IAEd,eAAe,CAAC,MAAM,EAAE,MAAM;IAC9B,gBAAgB,CAAC,IAAI,EAAE,MAAM;IAC7B,YAAY,CAAC,KAAK,EAAE,MAAM;IAC1B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAE1C,WAAW;IAIX,YAAY;CACf"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// packages/core/src/ListController.ts
|
|
2
|
+
import { VirtualizationEngine } from './VirtualizationEngine';
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic controller: owns the engine + scroll state,
|
|
5
|
+
* exposes subscribe/getSnapshot. React binds via useSyncExternalStore;
|
|
6
|
+
* Vue could bind via a reactive ref; Svelte via a store - the engine
|
|
7
|
+
* itself never needs to know any of that exists.
|
|
8
|
+
*/
|
|
9
|
+
export class ListController {
|
|
10
|
+
constructor({ itemCount, estimatedItemSize, overscan = 5 }) {
|
|
11
|
+
Object.defineProperty(this, "engine", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "scrollOffset", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "containerSize", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: 0
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "itemCount", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "overscan", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "listeners", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: new Set()
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(this, "subscribe", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: (listener) => {
|
|
52
|
+
this.listeners.add(listener);
|
|
53
|
+
return () => this.listeners.delete(listener);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this.engine = new VirtualizationEngine(estimatedItemSize);
|
|
57
|
+
this.itemCount = itemCount;
|
|
58
|
+
this.overscan = overscan;
|
|
59
|
+
this.engine.setItemCount(itemCount);
|
|
60
|
+
}
|
|
61
|
+
notify() { this.listeners.forEach((l) => l()); }
|
|
62
|
+
setScrollOffset(offset) { this.scrollOffset = offset; this.notify(); }
|
|
63
|
+
setContainerSize(size) { this.containerSize = size; this.notify(); }
|
|
64
|
+
setItemCount(count) { this.itemCount = count; this.engine.setItemCount(count); this.notify(); }
|
|
65
|
+
recordItemSize(index, size) { this.engine.recordItemSize(index, size); this.notify(); }
|
|
66
|
+
getSnapshot() {
|
|
67
|
+
return this.engine.getVisibleItems(this.scrollOffset, this.containerSize, this.itemCount, this.overscan);
|
|
68
|
+
}
|
|
69
|
+
getTotalSize() { return this.engine.getTotalSize(); }
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=ListController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListController.js","sourceRoot":"","sources":["../../src/core/ListController.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAU9D;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IAQvB,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,GAAG,CAAC,EAAyB;QAPzE;;;;;WAA6B;QAC7B;;;;mBAAe,CAAC;WAAC;QACjB;;;;mBAAgB,CAAC;WAAC;QAClB;;;;;WAAkB;QAClB;;;;;WAAiB;QACjB;;;;mBAAY,IAAI,GAAG,EAAY;WAAC;QASxC;;;;mBAAY,CAAC,QAAkB,EAAE,EAAE;gBAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjD,CAAC;WAAC;QATE,IAAI,CAAC,MAAM,GAAG,IAAI,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAOO,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExD,eAAe,CAAC,MAAc,IAAI,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9E,gBAAgB,CAAC,IAAY,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5E,YAAY,CAAC,KAAa,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvG,cAAc,CAAC,KAAa,EAAE,IAAY,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEvG,WAAW;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7G,CAAC;IAED,YAAY,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;CACxD"}
|
|
@@ -1,19 +1,54 @@
|
|
|
1
1
|
export declare class MeasurementSystem {
|
|
2
2
|
private resizeObserver;
|
|
3
3
|
private measurements;
|
|
4
|
+
/**
|
|
5
|
+
* NEW (additive): separate cache for width measurements, used
|
|
6
|
+
* only when an element is observed in 'horizontal' axis mode.
|
|
7
|
+
* Existing height-only consumers never populate or read this.
|
|
8
|
+
*/
|
|
9
|
+
private widthMeasurements;
|
|
4
10
|
private observedElements;
|
|
11
|
+
/**
|
|
12
|
+
* NEW (additive): tracks which axis each observed element
|
|
13
|
+
* should report on. Defaults to 'height' to preserve existing
|
|
14
|
+
* behavior for every caller that doesn't pass an axis.
|
|
15
|
+
*/
|
|
16
|
+
private observedAxis;
|
|
5
17
|
private pendingUpdates;
|
|
18
|
+
/**
|
|
19
|
+
* NEW (additive): pending width updates, flushed alongside
|
|
20
|
+
* height updates on the same RAF batch.
|
|
21
|
+
*/
|
|
22
|
+
private pendingWidthUpdates;
|
|
6
23
|
private rafId;
|
|
7
24
|
private onMeasure;
|
|
25
|
+
/**
|
|
26
|
+
* NEW (additive): optional width callback. If not provided,
|
|
27
|
+
* width measurements are still tracked internally (available
|
|
28
|
+
* via getWidthMeasurement) but no callback fires - existing
|
|
29
|
+
* callers that only pass onMeasure are unaffected.
|
|
30
|
+
*/
|
|
31
|
+
private onMeasureWidth;
|
|
8
32
|
constructor();
|
|
9
33
|
/**
|
|
10
|
-
* Start measuring container
|
|
34
|
+
* Start measuring container.
|
|
35
|
+
*
|
|
36
|
+
* `onMeasureWidth` is optional and additive: existing callers
|
|
37
|
+
* that only pass `onMeasure` keep working exactly as before
|
|
38
|
+
* (height-only). Pass `onMeasureWidth` to also receive width
|
|
39
|
+
* updates for elements observed in 'width' axis mode.
|
|
11
40
|
*/
|
|
12
|
-
startMeasuring(container: HTMLElement, onMeasure: (index: number, size: number) => void): void;
|
|
41
|
+
startMeasuring(container: HTMLElement, onMeasure: (index: number, size: number) => void, onMeasureWidth?: (index: number, size: number) => void): void;
|
|
13
42
|
/**
|
|
14
|
-
* Observe item
|
|
43
|
+
* Observe item.
|
|
44
|
+
*
|
|
45
|
+
* `axis` is optional and additive, defaulting to 'height' so
|
|
46
|
+
* every existing call site (which never passes a third arg)
|
|
47
|
+
* behaves exactly as before. Pass axis='width' for elements in
|
|
48
|
+
* a horizontal-scrolling list so contentRect.width is measured
|
|
49
|
+
* instead of contentRect.height.
|
|
15
50
|
*/
|
|
16
|
-
observeItem(index: number, element: HTMLElement): void;
|
|
51
|
+
observeItem(index: number, element: HTMLElement, axis?: 'height' | 'width'): void;
|
|
17
52
|
/**
|
|
18
53
|
* Unobserve item
|
|
19
54
|
*/
|
|
@@ -27,15 +62,28 @@ export declare class MeasurementSystem {
|
|
|
27
62
|
*/
|
|
28
63
|
private flushPendingUpdates;
|
|
29
64
|
/**
|
|
30
|
-
* Fallback measurement
|
|
65
|
+
* Fallback measurement.
|
|
66
|
+
*
|
|
67
|
+
* `axis` is optional and additive, defaulting to 'height' to
|
|
68
|
+
* preserve existing behavior for all current call sites.
|
|
31
69
|
*/
|
|
32
|
-
syncMeasure(index: number, element: HTMLElement): void;
|
|
70
|
+
syncMeasure(index: number, element: HTMLElement, axis?: 'height' | 'width'): void;
|
|
33
71
|
/**
|
|
34
72
|
* Preserve measurements
|
|
35
73
|
*/
|
|
36
74
|
stopMeasuring(): void;
|
|
37
75
|
getMeasurement(index: number): number | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* NEW (additive): mirrors getMeasurement() but for the width
|
|
78
|
+
* cache. Returns undefined for any index never observed in
|
|
79
|
+
* 'width' axis mode.
|
|
80
|
+
*/
|
|
81
|
+
getWidthMeasurement(index: number): number | undefined;
|
|
38
82
|
getAllMeasurements(): Map<number, number>;
|
|
83
|
+
/**
|
|
84
|
+
* NEW (additive): mirrors getAllMeasurements() for widths.
|
|
85
|
+
*/
|
|
86
|
+
getAllWidthMeasurements(): Map<number, number>;
|
|
39
87
|
clearPending(): void;
|
|
40
88
|
/**
|
|
41
89
|
* Explicit cache clear
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeasurementSystem.d.ts","sourceRoot":"","sources":["../../src/core/MeasurementSystem.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO,CAAC,gBAAgB,CACe;IAEvC,OAAO,CAAC,cAAc,CACQ;IAE9B,OAAO,CAAC,KAAK,CAAuB;IAEpC,OAAO,CAAC,SAAS,CAEC;;IAclB
|
|
1
|
+
{"version":3,"file":"MeasurementSystem.d.ts","sourceRoot":"","sources":["../../src/core/MeasurementSystem.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,YAAY,CAA6B;IAEjD;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAA6B;IAEtD,OAAO,CAAC,gBAAgB,CACe;IAEvC;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAC+B;IAEnD,OAAO,CAAC,cAAc,CACQ;IAE9B;;;OAGG;IACH,OAAO,CAAC,mBAAmB,CACG;IAE9B,OAAO,CAAC,KAAK,CAAuB;IAEpC,OAAO,CAAC,SAAS,CAEC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,cAAc,CAEJ;;IAclB;;;;;;;OAOG;IACH,cAAc,CACV,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,CACP,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,IAAI,EACT,cAAc,CAAC,EAAE,CACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,IAAI,GACV,IAAI;IA6BP;;;;;;;;OAQG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GACpC,IAAI;IAsBP;;OAEG;IACH,aAAa,CACT,OAAO,EAAE,WAAW,GACrB,IAAI;IAYP;;OAEG;IACH,OAAO,CAAC,YAAY,CAkElB;IAEF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAiC3B;;;;;OAKG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GACpC,IAAI;IAwCP;;OAEG;IACH,aAAa,IAAI,IAAI;IAarB,cAAc,CACV,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,SAAS;IAMrB;;;;OAIG;IACH,mBAAmB,CACf,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,SAAS;IAIrB,kBAAkB,IAAI,GAAG,CACrB,MAAM,EACN,MAAM,CACT;IAMD;;OAEG;IACH,uBAAuB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI9C,YAAY,IAAI,IAAI;IAKpB;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAKzB,OAAO,IAAI,IAAI;CAgBlB"}
|
|
@@ -12,18 +12,50 @@ export class MeasurementSystem {
|
|
|
12
12
|
writable: true,
|
|
13
13
|
value: new Map()
|
|
14
14
|
});
|
|
15
|
+
/**
|
|
16
|
+
* NEW (additive): separate cache for width measurements, used
|
|
17
|
+
* only when an element is observed in 'horizontal' axis mode.
|
|
18
|
+
* Existing height-only consumers never populate or read this.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(this, "widthMeasurements", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: new Map()
|
|
25
|
+
});
|
|
15
26
|
Object.defineProperty(this, "observedElements", {
|
|
16
27
|
enumerable: true,
|
|
17
28
|
configurable: true,
|
|
18
29
|
writable: true,
|
|
19
30
|
value: new WeakMap()
|
|
20
31
|
});
|
|
32
|
+
/**
|
|
33
|
+
* NEW (additive): tracks which axis each observed element
|
|
34
|
+
* should report on. Defaults to 'height' to preserve existing
|
|
35
|
+
* behavior for every caller that doesn't pass an axis.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(this, "observedAxis", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: new WeakMap()
|
|
42
|
+
});
|
|
21
43
|
Object.defineProperty(this, "pendingUpdates", {
|
|
22
44
|
enumerable: true,
|
|
23
45
|
configurable: true,
|
|
24
46
|
writable: true,
|
|
25
47
|
value: new Map()
|
|
26
48
|
});
|
|
49
|
+
/**
|
|
50
|
+
* NEW (additive): pending width updates, flushed alongside
|
|
51
|
+
* height updates on the same RAF batch.
|
|
52
|
+
*/
|
|
53
|
+
Object.defineProperty(this, "pendingWidthUpdates", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: new Map()
|
|
58
|
+
});
|
|
27
59
|
Object.defineProperty(this, "rafId", {
|
|
28
60
|
enumerable: true,
|
|
29
61
|
configurable: true,
|
|
@@ -36,6 +68,18 @@ export class MeasurementSystem {
|
|
|
36
68
|
writable: true,
|
|
37
69
|
value: null
|
|
38
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* NEW (additive): optional width callback. If not provided,
|
|
73
|
+
* width measurements are still tracked internally (available
|
|
74
|
+
* via getWidthMeasurement) but no callback fires - existing
|
|
75
|
+
* callers that only pass onMeasure are unaffected.
|
|
76
|
+
*/
|
|
77
|
+
Object.defineProperty(this, "onMeasureWidth", {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
writable: true,
|
|
81
|
+
value: null
|
|
82
|
+
});
|
|
39
83
|
/**
|
|
40
84
|
* ResizeObserver callback
|
|
41
85
|
*/
|
|
@@ -50,6 +94,16 @@ export class MeasurementSystem {
|
|
|
50
94
|
if (index === undefined) {
|
|
51
95
|
continue;
|
|
52
96
|
}
|
|
97
|
+
const axis = this.observedAxis.get(element) ?? 'height';
|
|
98
|
+
if (axis === 'width') {
|
|
99
|
+
const widthSize = Math.round(entry.contentRect.width);
|
|
100
|
+
if (this.widthMeasurements.get(index) !==
|
|
101
|
+
widthSize) {
|
|
102
|
+
this.widthMeasurements.set(index, widthSize);
|
|
103
|
+
this.pendingWidthUpdates.set(index, widthSize);
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
53
107
|
const size = Math.round(entry.contentRect.height);
|
|
54
108
|
if (this.measurements.get(index) === size) {
|
|
55
109
|
continue;
|
|
@@ -67,10 +121,16 @@ export class MeasurementSystem {
|
|
|
67
121
|
}
|
|
68
122
|
}
|
|
69
123
|
/**
|
|
70
|
-
* Start measuring container
|
|
124
|
+
* Start measuring container.
|
|
125
|
+
*
|
|
126
|
+
* `onMeasureWidth` is optional and additive: existing callers
|
|
127
|
+
* that only pass `onMeasure` keep working exactly as before
|
|
128
|
+
* (height-only). Pass `onMeasureWidth` to also receive width
|
|
129
|
+
* updates for elements observed in 'width' axis mode.
|
|
71
130
|
*/
|
|
72
|
-
startMeasuring(container, onMeasure) {
|
|
131
|
+
startMeasuring(container, onMeasure, onMeasureWidth) {
|
|
73
132
|
this.onMeasure = onMeasure;
|
|
133
|
+
this.onMeasureWidth = onMeasureWidth ?? null;
|
|
74
134
|
const items = container.querySelectorAll('[data-virtualize-index]');
|
|
75
135
|
items.forEach((item) => {
|
|
76
136
|
const indexAttr = item.dataset.virtualizeIndex;
|
|
@@ -82,14 +142,21 @@ export class MeasurementSystem {
|
|
|
82
142
|
});
|
|
83
143
|
}
|
|
84
144
|
/**
|
|
85
|
-
* Observe item
|
|
145
|
+
* Observe item.
|
|
146
|
+
*
|
|
147
|
+
* `axis` is optional and additive, defaulting to 'height' so
|
|
148
|
+
* every existing call site (which never passes a third arg)
|
|
149
|
+
* behaves exactly as before. Pass axis='width' for elements in
|
|
150
|
+
* a horizontal-scrolling list so contentRect.width is measured
|
|
151
|
+
* instead of contentRect.height.
|
|
86
152
|
*/
|
|
87
|
-
observeItem(index, element) {
|
|
153
|
+
observeItem(index, element, axis = 'height') {
|
|
88
154
|
element.dataset.virtualizeIndex =
|
|
89
155
|
String(index);
|
|
90
156
|
this.observedElements.set(element, index);
|
|
157
|
+
this.observedAxis.set(element, axis);
|
|
91
158
|
this.resizeObserver?.observe(element);
|
|
92
|
-
this.syncMeasure(index, element);
|
|
159
|
+
this.syncMeasure(index, element, axis);
|
|
93
160
|
}
|
|
94
161
|
/**
|
|
95
162
|
* Unobserve item
|
|
@@ -97,6 +164,7 @@ export class MeasurementSystem {
|
|
|
97
164
|
unobserveItem(element) {
|
|
98
165
|
this.resizeObserver?.unobserve(element);
|
|
99
166
|
this.observedElements.delete(element);
|
|
167
|
+
this.observedAxis.delete(element);
|
|
100
168
|
}
|
|
101
169
|
/**
|
|
102
170
|
* Batch updates using RAF
|
|
@@ -111,19 +179,34 @@ export class MeasurementSystem {
|
|
|
111
179
|
this.onMeasure?.(index, size);
|
|
112
180
|
});
|
|
113
181
|
this.pendingUpdates.clear();
|
|
182
|
+
this.pendingWidthUpdates.forEach((size, index) => {
|
|
183
|
+
this.onMeasureWidth?.(index, size);
|
|
184
|
+
});
|
|
185
|
+
this.pendingWidthUpdates.clear();
|
|
114
186
|
this.rafId = null;
|
|
115
187
|
});
|
|
116
188
|
}
|
|
117
189
|
/**
|
|
118
|
-
* Fallback measurement
|
|
190
|
+
* Fallback measurement.
|
|
191
|
+
*
|
|
192
|
+
* `axis` is optional and additive, defaulting to 'height' to
|
|
193
|
+
* preserve existing behavior for all current call sites.
|
|
119
194
|
*/
|
|
120
|
-
syncMeasure(index, element) {
|
|
121
|
-
const
|
|
122
|
-
|
|
195
|
+
syncMeasure(index, element, axis = 'height') {
|
|
196
|
+
const rect = element.getBoundingClientRect();
|
|
197
|
+
const size = axis === 'width' ? rect.width : rect.height;
|
|
123
198
|
if (size <= 0) {
|
|
124
199
|
return;
|
|
125
200
|
}
|
|
126
201
|
const rounded = Math.round(size);
|
|
202
|
+
if (axis === 'width') {
|
|
203
|
+
if (this.widthMeasurements.get(index) === rounded) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.widthMeasurements.set(index, rounded);
|
|
207
|
+
this.onMeasureWidth?.(index, rounded);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
127
210
|
if (this.measurements.get(index) === rounded) {
|
|
128
211
|
return;
|
|
129
212
|
}
|
|
@@ -136,6 +219,7 @@ export class MeasurementSystem {
|
|
|
136
219
|
stopMeasuring() {
|
|
137
220
|
this.resizeObserver?.disconnect();
|
|
138
221
|
this.pendingUpdates.clear();
|
|
222
|
+
this.pendingWidthUpdates.clear();
|
|
139
223
|
if (this.rafId !== null) {
|
|
140
224
|
cancelAnimationFrame(this.rafId);
|
|
141
225
|
}
|
|
@@ -143,25 +227,45 @@ export class MeasurementSystem {
|
|
|
143
227
|
getMeasurement(index) {
|
|
144
228
|
return this.measurements.get(index);
|
|
145
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* NEW (additive): mirrors getMeasurement() but for the width
|
|
232
|
+
* cache. Returns undefined for any index never observed in
|
|
233
|
+
* 'width' axis mode.
|
|
234
|
+
*/
|
|
235
|
+
getWidthMeasurement(index) {
|
|
236
|
+
return this.widthMeasurements.get(index);
|
|
237
|
+
}
|
|
146
238
|
getAllMeasurements() {
|
|
147
239
|
return new Map(this.measurements);
|
|
148
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* NEW (additive): mirrors getAllMeasurements() for widths.
|
|
243
|
+
*/
|
|
244
|
+
getAllWidthMeasurements() {
|
|
245
|
+
return new Map(this.widthMeasurements);
|
|
246
|
+
}
|
|
149
247
|
clearPending() {
|
|
150
248
|
this.pendingUpdates.clear();
|
|
249
|
+
this.pendingWidthUpdates.clear();
|
|
151
250
|
}
|
|
152
251
|
/**
|
|
153
252
|
* Explicit cache clear
|
|
154
253
|
*/
|
|
155
254
|
clearMeasurements() {
|
|
156
255
|
this.measurements.clear();
|
|
256
|
+
this.widthMeasurements.clear();
|
|
157
257
|
}
|
|
158
258
|
destroy() {
|
|
159
259
|
this.stopMeasuring();
|
|
160
260
|
this.measurements.clear();
|
|
161
261
|
this.pendingUpdates.clear();
|
|
262
|
+
this.widthMeasurements.clear();
|
|
263
|
+
this.pendingWidthUpdates.clear();
|
|
162
264
|
this.observedElements =
|
|
163
265
|
new WeakMap();
|
|
266
|
+
this.observedAxis = new WeakMap();
|
|
164
267
|
this.onMeasure = null;
|
|
268
|
+
this.onMeasureWidth = null;
|
|
165
269
|
}
|
|
166
270
|
}
|
|
167
271
|
//# sourceMappingURL=MeasurementSystem.js.map
|