@tanstack/virtual-core 3.5.0 → 3.6.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/cjs/index.cjs +136 -68
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +8 -5
- package/dist/cjs/utils.cjs +3 -3
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/index.d.ts +8 -5
- package/dist/esm/index.js +136 -68
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +3 -3
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +160 -84
- package/src/utils.ts +8 -4
package/dist/cjs/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["export type NoInfer<A extends any> = [A][A extends any ? 0 : never]\n\nexport type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>\n\nexport function memo<TDeps extends readonly any[], TResult>(\n getDeps: () => [...TDeps],\n fn: (...args: NoInfer<[...TDeps]>) => TResult,\n opts: {\n key: false | string\n debug?: () => any\n onChange?: (result: TResult) => void\n initialDeps?: TDeps\n },\n) {\n let deps = opts.initialDeps ?? []\n let result: TResult | undefined\n\n return (): TResult => {\n let depTime: number\n if (opts.key && opts.debug?.()) depTime = Date.now()\n\n const newDeps = getDeps()\n\n const depsChanged =\n newDeps.length !== deps.length ||\n newDeps.some((dep: any, index: number) => deps[index] !== dep)\n\n if (!depsChanged) {\n return result!\n }\n\n deps = newDeps\n\n let resultTime: number\n if (opts.key && opts.debug?.()) resultTime = Date.now()\n\n result = fn(...newDeps)\n\n if (opts.key && opts.debug?.()) {\n const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100\n const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100\n const resultFpsPercentage = resultEndTime / 16\n\n const pad = (str: number | string, num: number) => {\n str = String(str)\n while (str.length < num) {\n str = ' ' + str\n }\n return str\n }\n\n console.info(\n `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,\n `\n font-size: .6rem;\n font-weight: bold;\n color: hsl(${Math.max(\n 0,\n Math.min(120 - 120 * resultFpsPercentage, 120),\n )}deg 100% 31%);`,\n opts?.key,\n )\n }\n\n opts?.onChange?.(result)\n\n return result!\n }\n}\n\nexport function notUndefined<T>(value: T | undefined, msg?: string): T {\n if (value === undefined) {\n throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ''}`)\n } else {\n return value\n }\n}\n\nexport const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1\n\nexport const debounce = (fn: Function
|
|
1
|
+
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["export type NoInfer<A extends any> = [A][A extends any ? 0 : never]\n\nexport type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>\n\nexport function memo<TDeps extends readonly any[], TResult>(\n getDeps: () => [...TDeps],\n fn: (...args: NoInfer<[...TDeps]>) => TResult,\n opts: {\n key: false | string\n debug?: () => any\n onChange?: (result: TResult) => void\n initialDeps?: TDeps\n },\n) {\n let deps = opts.initialDeps ?? []\n let result: TResult | undefined\n\n return (): TResult => {\n let depTime: number\n if (opts.key && opts.debug?.()) depTime = Date.now()\n\n const newDeps = getDeps()\n\n const depsChanged =\n newDeps.length !== deps.length ||\n newDeps.some((dep: any, index: number) => deps[index] !== dep)\n\n if (!depsChanged) {\n return result!\n }\n\n deps = newDeps\n\n let resultTime: number\n if (opts.key && opts.debug?.()) resultTime = Date.now()\n\n result = fn(...newDeps)\n\n if (opts.key && opts.debug?.()) {\n const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100\n const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100\n const resultFpsPercentage = resultEndTime / 16\n\n const pad = (str: number | string, num: number) => {\n str = String(str)\n while (str.length < num) {\n str = ' ' + str\n }\n return str\n }\n\n console.info(\n `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,\n `\n font-size: .6rem;\n font-weight: bold;\n color: hsl(${Math.max(\n 0,\n Math.min(120 - 120 * resultFpsPercentage, 120),\n )}deg 100% 31%);`,\n opts?.key,\n )\n }\n\n opts?.onChange?.(result)\n\n return result!\n }\n}\n\nexport function notUndefined<T>(value: T | undefined, msg?: string): T {\n if (value === undefined) {\n throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ''}`)\n } else {\n return value\n }\n}\n\nexport const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1\n\nexport const debounce = (\n targetWindow: Window & typeof globalThis,\n fn: Function,\n ms: number,\n) => {\n let timeoutId: number\n return function (this: any, ...args: any[]) {\n targetWindow.clearTimeout(timeoutId)\n timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms)\n }\n}\n"],"names":[],"mappings":";;AAIgB,SAAA,KACd,SACA,IACA,MAMA;AACI,MAAA,OAAO,KAAK,eAAe;AAC3B,MAAA;AAEJ,SAAO,MAAe;;AAChB,QAAA;AACA,QAAA,KAAK,SAAO,UAAK,UAAL;AAAgB,gBAAU,KAAK;AAE/C,UAAM,UAAU;AAEhB,UAAM,cACJ,QAAQ,WAAW,KAAK,UACxB,QAAQ,KAAK,CAAC,KAAU,UAAkB,KAAK,KAAK,MAAM,GAAG;AAE/D,QAAI,CAAC,aAAa;AACT,aAAA;AAAA,IACT;AAEO,WAAA;AAEH,QAAA;AACA,QAAA,KAAK,SAAO,UAAK,UAAL;AAAgB,mBAAa,KAAK;AAEzC,aAAA,GAAG,GAAG,OAAO;AAEtB,QAAI,KAAK,SAAO,UAAK,UAAL,gCAAgB;AACxB,YAAA,aAAa,KAAK,OAAO,KAAK,QAAQ,WAAY,GAAG,IAAI;AACzD,YAAA,gBAAgB,KAAK,OAAO,KAAK,QAAQ,cAAe,GAAG,IAAI;AACrE,YAAM,sBAAsB,gBAAgB;AAEtC,YAAA,MAAM,CAAC,KAAsB,QAAgB;AACjD,cAAM,OAAO,GAAG;AACT,eAAA,IAAI,SAAS,KAAK;AACvB,gBAAM,MAAM;AAAA,QACd;AACO,eAAA;AAAA,MAAA;AAGD,cAAA;AAAA,QACN,OAAO,IAAI,eAAe,CAAC,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;AAAA,QACnD;AAAA;AAAA;AAAA,yBAGiB,KAAK;AAAA,UAChB;AAAA,UACA,KAAK,IAAI,MAAM,MAAM,qBAAqB,GAAG;AAAA,QAC9C,CAAA;AAAA,QACL,6BAAM;AAAA,MAAA;AAAA,IAEV;AAEA,uCAAM,aAAN,8BAAiB;AAEV,WAAA;AAAA,EAAA;AAEX;AAEgB,SAAA,aAAgB,OAAsB,KAAiB;AACrE,MAAI,UAAU,QAAW;AACjB,UAAA,IAAI,MAAM,uBAAuB,MAAM,KAAK,GAAG,KAAK,EAAE,EAAE;AAAA,EAAA,OACzD;AACE,WAAA;AAAA,EACT;AACF;AAEa,MAAA,cAAc,CAAC,GAAW,MAAc,KAAK,IAAI,IAAI,CAAC,IAAI;AAEhE,MAAM,WAAW,CACtB,cACA,IACA,OACG;AACC,MAAA;AACJ,SAAO,YAAwB,MAAa;AAC1C,iBAAa,aAAa,SAAS;AACvB,gBAAA,aAAa,WAAW,MAAM,GAAG,MAAM,MAAM,IAAI,GAAG,EAAE;AAAA,EAAA;AAEtE;;;;;"}
|
package/dist/cjs/utils.d.cts
CHANGED
|
@@ -8,4 +8,4 @@ export declare function memo<TDeps extends readonly any[], TResult>(getDeps: ()
|
|
|
8
8
|
}): () => TResult;
|
|
9
9
|
export declare function notUndefined<T>(value: T | undefined, msg?: string): T;
|
|
10
10
|
export declare const approxEqual: (a: number, b: number) => boolean;
|
|
11
|
-
export declare const debounce: (fn: Function, ms: number) => (this: any, ...args: any[]) => void;
|
|
11
|
+
export declare const debounce: (targetWindow: Window & typeof globalThis, fn: Function, ms: number) => (this: any, ...args: any[]) => void;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -71,18 +71,20 @@ export interface VirtualizerOptions<TScrollElement extends Element | Window, TIt
|
|
|
71
71
|
initialMeasurementsCache?: VirtualItem[];
|
|
72
72
|
lanes?: number;
|
|
73
73
|
isScrollingResetDelay?: number;
|
|
74
|
+
enabled?: boolean;
|
|
74
75
|
}
|
|
75
76
|
export declare class Virtualizer<TScrollElement extends Element | Window, TItemElement extends Element> {
|
|
76
77
|
private unsubs;
|
|
77
78
|
options: Required<VirtualizerOptions<TScrollElement, TItemElement>>;
|
|
78
79
|
scrollElement: TScrollElement | null;
|
|
80
|
+
targetWindow: (Window & typeof globalThis) | null;
|
|
79
81
|
isScrolling: boolean;
|
|
80
82
|
private scrollToIndexTimeoutId;
|
|
81
83
|
measurementsCache: VirtualItem[];
|
|
82
84
|
private itemSizeCache;
|
|
83
85
|
private pendingMeasuredCacheIndexes;
|
|
84
|
-
scrollRect: Rect;
|
|
85
|
-
scrollOffset: number;
|
|
86
|
+
scrollRect: Rect | null;
|
|
87
|
+
scrollOffset: number | null;
|
|
86
88
|
scrollDirection: ScrollDirection | null;
|
|
87
89
|
private scrollAdjustments;
|
|
88
90
|
shouldAdjustScrollPositionOnItemSizeChange: undefined | ((item: VirtualItem, delta: number, instance: Virtualizer<TScrollElement, TItemElement>) => boolean);
|
|
@@ -99,8 +101,9 @@ export declare class Virtualizer<TScrollElement extends Element | Window, TItemE
|
|
|
99
101
|
_didMount: () => () => void;
|
|
100
102
|
_willUpdate: () => void;
|
|
101
103
|
private getSize;
|
|
102
|
-
private
|
|
104
|
+
private getScrollOffset;
|
|
103
105
|
private getFurthestMeasurement;
|
|
106
|
+
private getMeasurementOptions;
|
|
104
107
|
private getMeasurements;
|
|
105
108
|
calculateRange: () => {
|
|
106
109
|
startIndex: number;
|
|
@@ -112,9 +115,9 @@ export declare class Virtualizer<TScrollElement extends Element | Window, TItemE
|
|
|
112
115
|
resizeItem: (item: VirtualItem, size: number) => void;
|
|
113
116
|
measureElement: (node: TItemElement | null) => void;
|
|
114
117
|
getVirtualItems: () => VirtualItem[];
|
|
115
|
-
getVirtualItemForOffset: (offset: number) => VirtualItem;
|
|
118
|
+
getVirtualItemForOffset: (offset: number) => VirtualItem | undefined;
|
|
116
119
|
getOffsetForAlignment: (toOffset: number, align: ScrollAlignment) => number;
|
|
117
|
-
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"];
|
|
120
|
+
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined;
|
|
118
121
|
private isDynamicMode;
|
|
119
122
|
private cancelScrollToIndex;
|
|
120
123
|
scrollToOffset: (toOffset: number, { align, behavior }?: ScrollToOffsetOptions) => void;
|
package/dist/esm/index.js
CHANGED
|
@@ -14,16 +14,20 @@ const observeElementRect = (instance, cb) => {
|
|
|
14
14
|
if (!element) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
+
const targetWindow = instance.targetWindow;
|
|
18
|
+
if (!targetWindow) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
17
21
|
const handler = (rect) => {
|
|
18
22
|
const { width, height } = rect;
|
|
19
23
|
cb({ width: Math.round(width), height: Math.round(height) });
|
|
20
24
|
};
|
|
21
25
|
handler(element.getBoundingClientRect());
|
|
22
|
-
if (
|
|
26
|
+
if (!targetWindow.ResizeObserver) {
|
|
23
27
|
return () => {
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
|
-
const observer = new ResizeObserver((entries) => {
|
|
30
|
+
const observer = new targetWindow.ResizeObserver((entries) => {
|
|
27
31
|
const entry = entries[0];
|
|
28
32
|
if (entry == null ? void 0 : entry.borderBoxSize) {
|
|
29
33
|
const box = entry.borderBoxSize[0];
|
|
@@ -62,10 +66,18 @@ const observeElementOffset = (instance, cb) => {
|
|
|
62
66
|
if (!element) {
|
|
63
67
|
return;
|
|
64
68
|
}
|
|
69
|
+
const targetWindow = instance.targetWindow;
|
|
70
|
+
if (!targetWindow) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
65
73
|
let offset = 0;
|
|
66
|
-
const fallback = supportsScrollend ? () => void 0 : debounce(
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
const fallback = supportsScrollend ? () => void 0 : debounce(
|
|
75
|
+
targetWindow,
|
|
76
|
+
() => {
|
|
77
|
+
cb(offset, false);
|
|
78
|
+
},
|
|
79
|
+
instance.options.isScrollingResetDelay
|
|
80
|
+
);
|
|
69
81
|
const createHandler = (isScrolling) => () => {
|
|
70
82
|
offset = element[instance.options.horizontal ? "scrollLeft" : "scrollTop"];
|
|
71
83
|
fallback();
|
|
@@ -86,10 +98,18 @@ const observeWindowOffset = (instance, cb) => {
|
|
|
86
98
|
if (!element) {
|
|
87
99
|
return;
|
|
88
100
|
}
|
|
101
|
+
const targetWindow = instance.targetWindow;
|
|
102
|
+
if (!targetWindow) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
89
105
|
let offset = 0;
|
|
90
|
-
const fallback = supportsScrollend ? () => void 0 : debounce(
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
const fallback = supportsScrollend ? () => void 0 : debounce(
|
|
107
|
+
targetWindow,
|
|
108
|
+
() => {
|
|
109
|
+
cb(offset, false);
|
|
110
|
+
},
|
|
111
|
+
instance.options.isScrollingResetDelay
|
|
112
|
+
);
|
|
93
113
|
const createHandler = (isScrolling) => () => {
|
|
94
114
|
offset = element[instance.options.horizontal ? "scrollX" : "scrollY"];
|
|
95
115
|
fallback();
|
|
@@ -145,11 +165,14 @@ class Virtualizer {
|
|
|
145
165
|
constructor(opts) {
|
|
146
166
|
this.unsubs = [];
|
|
147
167
|
this.scrollElement = null;
|
|
168
|
+
this.targetWindow = null;
|
|
148
169
|
this.isScrolling = false;
|
|
149
170
|
this.scrollToIndexTimeoutId = null;
|
|
150
171
|
this.measurementsCache = [];
|
|
151
172
|
this.itemSizeCache = /* @__PURE__ */ new Map();
|
|
152
173
|
this.pendingMeasuredCacheIndexes = [];
|
|
174
|
+
this.scrollRect = null;
|
|
175
|
+
this.scrollOffset = null;
|
|
153
176
|
this.scrollDirection = null;
|
|
154
177
|
this.scrollAdjustments = 0;
|
|
155
178
|
this.measureElementCache = /* @__PURE__ */ new Map();
|
|
@@ -158,15 +181,15 @@ class Virtualizer {
|
|
|
158
181
|
const get = () => {
|
|
159
182
|
if (_ro) {
|
|
160
183
|
return _ro;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
entries.forEach((entry) => {
|
|
164
|
-
this._measureElement(entry.target, entry);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
} else {
|
|
184
|
+
}
|
|
185
|
+
if (!this.targetWindow || !this.targetWindow.ResizeObserver) {
|
|
168
186
|
return null;
|
|
169
187
|
}
|
|
188
|
+
return _ro = new this.targetWindow.ResizeObserver((entries) => {
|
|
189
|
+
entries.forEach((entry) => {
|
|
190
|
+
this._measureElement(entry.target, entry);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
170
193
|
};
|
|
171
194
|
return {
|
|
172
195
|
disconnect: () => {
|
|
@@ -210,6 +233,7 @@ class Virtualizer {
|
|
|
210
233
|
initialMeasurementsCache: [],
|
|
211
234
|
lanes: 1,
|
|
212
235
|
isScrollingResetDelay: 150,
|
|
236
|
+
enabled: true,
|
|
213
237
|
...opts2
|
|
214
238
|
};
|
|
215
239
|
};
|
|
@@ -228,20 +252,31 @@ class Virtualizer {
|
|
|
228
252
|
this.unsubs.filter(Boolean).forEach((d) => d());
|
|
229
253
|
this.unsubs = [];
|
|
230
254
|
this.scrollElement = null;
|
|
255
|
+
this.targetWindow = null;
|
|
256
|
+
this.observer.disconnect();
|
|
257
|
+
this.measureElementCache.clear();
|
|
231
258
|
};
|
|
232
259
|
this._didMount = () => {
|
|
233
|
-
this.measureElementCache.forEach(this.observer.observe);
|
|
234
260
|
return () => {
|
|
235
|
-
this.observer.disconnect();
|
|
236
261
|
this.cleanup();
|
|
237
262
|
};
|
|
238
263
|
};
|
|
239
264
|
this._willUpdate = () => {
|
|
240
|
-
|
|
265
|
+
var _a;
|
|
266
|
+
const scrollElement = this.options.enabled ? this.options.getScrollElement() : null;
|
|
241
267
|
if (this.scrollElement !== scrollElement) {
|
|
242
268
|
this.cleanup();
|
|
269
|
+
if (!scrollElement) {
|
|
270
|
+
this.notify(false, false);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
243
273
|
this.scrollElement = scrollElement;
|
|
244
|
-
this.
|
|
274
|
+
if (this.scrollElement && "ownerDocument" in this.scrollElement) {
|
|
275
|
+
this.targetWindow = this.scrollElement.ownerDocument.defaultView;
|
|
276
|
+
} else {
|
|
277
|
+
this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null;
|
|
278
|
+
}
|
|
279
|
+
this._scrollToOffset(this.getScrollOffset(), {
|
|
245
280
|
adjustments: void 0,
|
|
246
281
|
behavior: void 0
|
|
247
282
|
});
|
|
@@ -254,7 +289,7 @@ class Virtualizer {
|
|
|
254
289
|
this.unsubs.push(
|
|
255
290
|
this.options.observeElementOffset(this, (offset, isScrolling) => {
|
|
256
291
|
this.scrollAdjustments = 0;
|
|
257
|
-
this.scrollDirection = isScrolling ? this.
|
|
292
|
+
this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null;
|
|
258
293
|
this.scrollOffset = offset;
|
|
259
294
|
const prevIsScrolling = this.isScrolling;
|
|
260
295
|
this.isScrolling = isScrolling;
|
|
@@ -264,28 +299,21 @@ class Virtualizer {
|
|
|
264
299
|
}
|
|
265
300
|
};
|
|
266
301
|
this.getSize = () => {
|
|
302
|
+
if (!this.options.enabled) {
|
|
303
|
+
this.scrollRect = null;
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
this.scrollRect = this.scrollRect ?? this.options.initialRect;
|
|
267
307
|
return this.scrollRect[this.options.horizontal ? "width" : "height"];
|
|
268
308
|
};
|
|
269
|
-
this.
|
|
270
|
-
()
|
|
271
|
-
this.
|
|
272
|
-
|
|
273
|
-
this.options.scrollMargin,
|
|
274
|
-
this.options.getItemKey
|
|
275
|
-
],
|
|
276
|
-
(count, paddingStart, scrollMargin, getItemKey) => {
|
|
277
|
-
this.pendingMeasuredCacheIndexes = [];
|
|
278
|
-
return {
|
|
279
|
-
count,
|
|
280
|
-
paddingStart,
|
|
281
|
-
scrollMargin,
|
|
282
|
-
getItemKey
|
|
283
|
-
};
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
key: false
|
|
309
|
+
this.getScrollOffset = () => {
|
|
310
|
+
if (!this.options.enabled) {
|
|
311
|
+
this.scrollOffset = null;
|
|
312
|
+
return 0;
|
|
287
313
|
}
|
|
288
|
-
|
|
314
|
+
this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset);
|
|
315
|
+
return this.scrollOffset;
|
|
316
|
+
};
|
|
289
317
|
this.getFurthestMeasurement = (measurements, index) => {
|
|
290
318
|
const furthestMeasurementsFound = /* @__PURE__ */ new Map();
|
|
291
319
|
const furthestMeasurements = /* @__PURE__ */ new Map();
|
|
@@ -313,9 +341,42 @@ class Virtualizer {
|
|
|
313
341
|
return a.end - b.end;
|
|
314
342
|
})[0] : void 0;
|
|
315
343
|
};
|
|
344
|
+
this.getMeasurementOptions = memo(
|
|
345
|
+
() => [
|
|
346
|
+
this.options.count,
|
|
347
|
+
this.options.paddingStart,
|
|
348
|
+
this.options.scrollMargin,
|
|
349
|
+
this.options.getItemKey,
|
|
350
|
+
this.options.enabled
|
|
351
|
+
],
|
|
352
|
+
(count, paddingStart, scrollMargin, getItemKey, enabled) => {
|
|
353
|
+
this.pendingMeasuredCacheIndexes = [];
|
|
354
|
+
return {
|
|
355
|
+
count,
|
|
356
|
+
paddingStart,
|
|
357
|
+
scrollMargin,
|
|
358
|
+
getItemKey,
|
|
359
|
+
enabled
|
|
360
|
+
};
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
key: false
|
|
364
|
+
}
|
|
365
|
+
);
|
|
316
366
|
this.getMeasurements = memo(
|
|
317
367
|
() => [this.getMeasurementOptions(), this.itemSizeCache],
|
|
318
|
-
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => {
|
|
368
|
+
({ count, paddingStart, scrollMargin, getItemKey, enabled }, itemSizeCache) => {
|
|
369
|
+
if (!enabled) {
|
|
370
|
+
this.measurementsCache = [];
|
|
371
|
+
this.itemSizeCache.clear();
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
if (this.measurementsCache.length === 0) {
|
|
375
|
+
this.measurementsCache = this.options.initialMeasurementsCache;
|
|
376
|
+
this.measurementsCache.forEach((item) => {
|
|
377
|
+
this.itemSizeCache.set(item.key, item.size);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
319
380
|
const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
|
|
320
381
|
this.pendingMeasuredCacheIndexes = [];
|
|
321
382
|
const measurements = this.measurementsCache.slice(0, min);
|
|
@@ -345,7 +406,7 @@ class Virtualizer {
|
|
|
345
406
|
}
|
|
346
407
|
);
|
|
347
408
|
this.calculateRange = memo(
|
|
348
|
-
() => [this.getMeasurements(), this.getSize(), this.
|
|
409
|
+
() => [this.getMeasurements(), this.getSize(), this.getScrollOffset()],
|
|
349
410
|
(measurements, outerSize, scrollOffset) => {
|
|
350
411
|
return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({
|
|
351
412
|
measurements,
|
|
@@ -390,7 +451,7 @@ class Virtualizer {
|
|
|
390
451
|
return parseInt(indexStr, 10);
|
|
391
452
|
};
|
|
392
453
|
this._measureElement = (node, entry) => {
|
|
393
|
-
const item = this.
|
|
454
|
+
const item = this.getMeasurements()[this.indexFromElement(node)];
|
|
394
455
|
if (!item || !node.isConnected) {
|
|
395
456
|
this.measureElementCache.forEach((cached, key) => {
|
|
396
457
|
if (cached === node) {
|
|
@@ -415,11 +476,11 @@ class Virtualizer {
|
|
|
415
476
|
const itemSize = this.itemSizeCache.get(item.key) ?? item.size;
|
|
416
477
|
const delta = size - itemSize;
|
|
417
478
|
if (delta !== 0) {
|
|
418
|
-
if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.
|
|
479
|
+
if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments) {
|
|
419
480
|
if (process.env.NODE_ENV !== "production" && this.options.debug) {
|
|
420
481
|
console.info("correction", delta);
|
|
421
482
|
}
|
|
422
|
-
this._scrollToOffset(this.
|
|
483
|
+
this._scrollToOffset(this.getScrollOffset(), {
|
|
423
484
|
adjustments: this.scrollAdjustments += delta,
|
|
424
485
|
behavior: void 0
|
|
425
486
|
});
|
|
@@ -453,6 +514,9 @@ class Virtualizer {
|
|
|
453
514
|
);
|
|
454
515
|
this.getVirtualItemForOffset = (offset) => {
|
|
455
516
|
const measurements = this.getMeasurements();
|
|
517
|
+
if (measurements.length === 0) {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
456
520
|
return notUndefined(
|
|
457
521
|
measurements[findNearestBinarySearch(
|
|
458
522
|
0,
|
|
@@ -464,10 +528,11 @@ class Virtualizer {
|
|
|
464
528
|
};
|
|
465
529
|
this.getOffsetForAlignment = (toOffset, align) => {
|
|
466
530
|
const size = this.getSize();
|
|
531
|
+
const scrollOffset = this.getScrollOffset();
|
|
467
532
|
if (align === "auto") {
|
|
468
|
-
if (toOffset <=
|
|
533
|
+
if (toOffset <= scrollOffset) {
|
|
469
534
|
align = "start";
|
|
470
|
-
} else if (toOffset >=
|
|
535
|
+
} else if (toOffset >= scrollOffset + size) {
|
|
471
536
|
align = "end";
|
|
472
537
|
} else {
|
|
473
538
|
align = "start";
|
|
@@ -482,28 +547,33 @@ class Virtualizer {
|
|
|
482
547
|
}
|
|
483
548
|
const scrollSizeProp = this.options.horizontal ? "scrollWidth" : "scrollHeight";
|
|
484
549
|
const scrollSize = this.scrollElement ? "document" in this.scrollElement ? this.scrollElement.document.documentElement[scrollSizeProp] : this.scrollElement[scrollSizeProp] : 0;
|
|
485
|
-
const maxOffset = scrollSize -
|
|
550
|
+
const maxOffset = scrollSize - size;
|
|
486
551
|
return Math.max(Math.min(maxOffset, toOffset), 0);
|
|
487
552
|
};
|
|
488
553
|
this.getOffsetForIndex = (index, align = "auto") => {
|
|
489
554
|
index = Math.max(0, Math.min(index, this.options.count - 1));
|
|
490
|
-
const
|
|
555
|
+
const item = this.getMeasurements()[index];
|
|
556
|
+
if (!item) {
|
|
557
|
+
return void 0;
|
|
558
|
+
}
|
|
559
|
+
const size = this.getSize();
|
|
560
|
+
const scrollOffset = this.getScrollOffset();
|
|
491
561
|
if (align === "auto") {
|
|
492
|
-
if (
|
|
562
|
+
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) {
|
|
493
563
|
align = "end";
|
|
494
|
-
} else if (
|
|
564
|
+
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
|
|
495
565
|
align = "start";
|
|
496
566
|
} else {
|
|
497
|
-
return [
|
|
567
|
+
return [scrollOffset, align];
|
|
498
568
|
}
|
|
499
569
|
}
|
|
500
|
-
const toOffset = align === "end" ?
|
|
570
|
+
const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart;
|
|
501
571
|
return [this.getOffsetForAlignment(toOffset, align), align];
|
|
502
572
|
};
|
|
503
573
|
this.isDynamicMode = () => this.measureElementCache.size > 0;
|
|
504
574
|
this.cancelScrollToIndex = () => {
|
|
505
|
-
if (this.scrollToIndexTimeoutId !== null) {
|
|
506
|
-
clearTimeout(this.scrollToIndexTimeoutId);
|
|
575
|
+
if (this.scrollToIndexTimeoutId !== null && this.targetWindow) {
|
|
576
|
+
this.targetWindow.clearTimeout(this.scrollToIndexTimeoutId);
|
|
507
577
|
this.scrollToIndexTimeoutId = null;
|
|
508
578
|
}
|
|
509
579
|
};
|
|
@@ -527,17 +597,22 @@ class Virtualizer {
|
|
|
527
597
|
"The `smooth` scroll behavior is not fully supported with dynamic size."
|
|
528
598
|
);
|
|
529
599
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
600
|
+
const offsetAndAlign = this.getOffsetForIndex(index, initialAlign);
|
|
601
|
+
if (!offsetAndAlign)
|
|
602
|
+
return;
|
|
603
|
+
const [offset, align] = offsetAndAlign;
|
|
604
|
+
this._scrollToOffset(offset, { adjustments: void 0, behavior });
|
|
605
|
+
if (behavior !== "smooth" && this.isDynamicMode() && this.targetWindow) {
|
|
606
|
+
this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => {
|
|
534
607
|
this.scrollToIndexTimeoutId = null;
|
|
535
608
|
const elementInDOM = this.measureElementCache.has(
|
|
536
609
|
this.options.getItemKey(index)
|
|
537
610
|
);
|
|
538
611
|
if (elementInDOM) {
|
|
539
|
-
const [
|
|
540
|
-
|
|
612
|
+
const [latestOffset] = notUndefined(
|
|
613
|
+
this.getOffsetForIndex(index, align)
|
|
614
|
+
);
|
|
615
|
+
if (!approxEqual(latestOffset, this.getScrollOffset())) {
|
|
541
616
|
this.scrollToIndex(index, { align, behavior });
|
|
542
617
|
}
|
|
543
618
|
} else {
|
|
@@ -553,7 +628,7 @@ class Virtualizer {
|
|
|
553
628
|
"The `smooth` scroll behavior is not fully supported with dynamic size."
|
|
554
629
|
);
|
|
555
630
|
}
|
|
556
|
-
this._scrollToOffset(this.
|
|
631
|
+
this._scrollToOffset(this.getScrollOffset() + delta, {
|
|
557
632
|
adjustments: void 0,
|
|
558
633
|
behavior
|
|
559
634
|
});
|
|
@@ -583,13 +658,6 @@ class Virtualizer {
|
|
|
583
658
|
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, false);
|
|
584
659
|
};
|
|
585
660
|
this.setOptions(opts);
|
|
586
|
-
this.scrollRect = this.options.initialRect;
|
|
587
|
-
this.scrollOffset = typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset;
|
|
588
|
-
this.measurementsCache = this.options.initialMeasurementsCache;
|
|
589
|
-
this.measurementsCache.forEach((item) => {
|
|
590
|
-
this.itemSizeCache.set(item.key, item.size);
|
|
591
|
-
});
|
|
592
|
-
this.notify(false, false);
|
|
593
661
|
}
|
|
594
662
|
}
|
|
595
663
|
const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
|