virtua 0.49.2 → 0.50.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.
Files changed (59) hide show
  1. package/README.md +39 -5
  2. package/lib/angular/ListItem.d.ts +1 -0
  3. package/lib/angular/VList.d.ts +85 -0
  4. package/lib/angular/Virtualizer.d.ts +141 -0
  5. package/lib/angular/WindowVirtualizer.d.ts +103 -0
  6. package/lib/angular/index.d.ts +6 -0
  7. package/lib/angular/index.js +1148 -0
  8. package/lib/angular/index.js.map +1 -0
  9. package/lib/angular/utils.d.ts +19 -0
  10. package/lib/core/index.cjs +237 -233
  11. package/lib/core/index.cjs.map +1 -1
  12. package/lib/core/index.d.ts +6 -6
  13. package/lib/core/index.js +272 -272
  14. package/lib/core/index.js.map +1 -1
  15. package/lib/index.cjs +529 -499
  16. package/lib/index.cjs.map +1 -1
  17. package/lib/index.d.ts +2 -2
  18. package/lib/index.js +321 -315
  19. package/lib/index.js.map +1 -1
  20. package/lib/react/VGrid.d.ts +2 -2
  21. package/lib/react/VList.d.ts +3 -3
  22. package/lib/react/Virtualizer.d.ts +3 -3
  23. package/lib/react/WindowVirtualizer.d.ts +3 -3
  24. package/lib/react/index.d.ts +9 -9
  25. package/lib/react/types.d.ts +1 -1
  26. package/lib/solid/VList.d.ts +3 -6
  27. package/lib/solid/Virtualizer.d.ts +2 -5
  28. package/lib/solid/WindowVirtualizer.d.ts +2 -5
  29. package/lib/solid/index.cjs +331 -323
  30. package/lib/solid/index.cjs.map +1 -1
  31. package/lib/solid/index.d.ts +7 -7
  32. package/lib/solid/index.js +249 -257
  33. package/lib/solid/index.js.map +1 -1
  34. package/lib/solid/index.jsx +1224 -1566
  35. package/lib/solid/index.jsx.map +1 -1
  36. package/lib/solid/types.d.ts +1 -1
  37. package/lib/svelte/ListItem.svelte +12 -2
  38. package/lib/svelte/VList.svelte +2 -0
  39. package/lib/svelte/VList.type.d.ts +3 -3
  40. package/lib/svelte/Virtualizer.svelte +2 -0
  41. package/lib/svelte/Virtualizer.type.d.ts +8 -3
  42. package/lib/svelte/WindowVirtualizer.type.d.ts +2 -2
  43. package/lib/svelte/index.d.ts +6 -6
  44. package/lib/svelte/index.js +4 -4
  45. package/lib/svelte/types.d.ts +1 -1
  46. package/lib/svelte/utils.d.ts +7 -0
  47. package/lib/svelte/utils.js +11 -11
  48. package/lib/svelte/utils.js.map +1 -1
  49. package/lib/vue/VList.d.ts +2 -3
  50. package/lib/vue/Virtualizer.d.ts +3 -4
  51. package/lib/vue/WindowVirtualizer.d.ts +2 -3
  52. package/lib/vue/index.cjs +327 -317
  53. package/lib/vue/index.cjs.map +1 -1
  54. package/lib/vue/index.d.ts +6 -6
  55. package/lib/vue/index.js +284 -284
  56. package/lib/vue/index.js.map +1 -1
  57. package/lib/vue/utils.d.ts +1 -1
  58. package/package.json +40 -17
  59. package/lib/svelte/index.js.map +0 -1
@@ -1,1622 +1,1280 @@
1
- import { mergeProps, createEffect, onCleanup, createMemo, createSignal, onMount, createComputed, on, untrack, For, splitProps } from 'solid-js';
2
- import { Dynamic } from 'solid-js/web';
1
+ import { For, createComputed, createEffect, createMemo, createSignal, mergeProps, on, onCleanup, onMount, splitProps, untrack } from "solid-js";
2
+ import { Dynamic } from "solid-js/web";
3
3
 
4
- /** @internal */
5
- const NULL = null;
6
- /** @internal */
7
- const { min, max, abs, floor } = Math;
8
- /**
9
- * @internal
10
- */
11
- const clamp = (value, minValue, maxValue) => min(maxValue, max(minValue, value));
12
- /**
13
- * @internal
14
- */
15
- const sort = (arr) => {
16
- return [...arr].sort((a, b) => a - b);
4
+ //#region src/core/utils.ts
5
+ var NULL = null;
6
+ var { min, max, abs, floor } = Math;
7
+ var clamp = (value, minValue, maxValue) => min(maxValue, max(minValue, value));
8
+ var sort = (arr) => {
9
+ return [...arr].sort((a, b) => a - b);
17
10
  };
18
- /**
19
- * @internal
20
- */
21
- const microtask = typeof queueMicrotask === "function"
22
- ? queueMicrotask
23
- : (fn) => {
24
- Promise.resolve().then(fn);
25
- };
26
- /**
27
- * @internal
28
- */
29
- const createPromise = () => {
30
- let resolve;
31
- const promise = new Promise((res) => {
32
- resolve = res;
33
- });
34
- return [promise, resolve];
11
+ var microtask = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => {
12
+ Promise.resolve().then(fn);
35
13
  };
36
- /**
37
- * @internal
38
- */
39
- const once = (fn) => {
40
- let cache;
41
- return () => {
42
- if (fn) {
43
- cache = fn();
44
- fn = undefined;
45
- }
46
- return cache;
47
- };
14
+ var createPromise = () => {
15
+ let resolve;
16
+ const promise = new Promise((res) => {
17
+ resolve = res;
18
+ });
19
+ return [promise, resolve];
20
+ };
21
+ var once = (fn) => {
22
+ let cache;
23
+ return () => {
24
+ if (fn) {
25
+ cache = fn();
26
+ fn = undefined;
27
+ }
28
+ return cache;
29
+ };
48
30
  };
49
31
 
50
- /** @internal */
51
- const UNCACHED = -1;
52
- const fill = (array, length, prepend) => {
53
- const key = prepend ? "unshift" : "push";
54
- for (let i = 0; i < length; i++) {
55
- array[key](UNCACHED);
56
- }
57
- return array;
32
+ //#endregion
33
+ //#region src/core/cache.ts
34
+ var UNCACHED = -1;
35
+ var fill = (array, length, prepend) => {
36
+ const key = prepend ? "unshift" : "push";
37
+ for (let i = 0; i < length; i++) {
38
+ array[key](-1);
39
+ }
40
+ return array;
58
41
  };
59
- /**
60
- * @internal
61
- */
62
- const getItemSize = (cache, index) => {
63
- const size = cache._sizes[index];
64
- return size === UNCACHED ? cache._defaultItemSize : size;
42
+ var getItemSize = (cache, index) => {
43
+ const size = cache._sizes[index];
44
+ return size === -1 ? cache._defaultItemSize : size;
65
45
  };
66
- /**
67
- * @internal
68
- */
69
- const setItemSize = (cache, index, size) => {
70
- const isInitialMeasurement = cache._sizes[index] === UNCACHED;
71
- cache._sizes[index] = size;
72
- // mark as dirty
73
- cache._computedOffsetIndex = min(index, cache._computedOffsetIndex);
74
- return isInitialMeasurement;
46
+ var setItemSize = (cache, index, size) => {
47
+ const isInitialMeasurement = cache._sizes[index] === -1;
48
+ cache._sizes[index] = size;
49
+ cache._computedOffsetIndex = min(index, cache._computedOffsetIndex);
50
+ return isInitialMeasurement;
75
51
  };
76
- /**
77
- * @internal
78
- */
79
- const getItemOffset = (cache, index) => {
80
- if (!cache._length)
81
- return 0;
82
- if (cache._computedOffsetIndex >= index) {
83
- return cache._offsets[index];
84
- }
85
- if (cache._computedOffsetIndex < 0) {
86
- // first offset must be 0 to avoid returning NaN, which can cause infinite rerender.
87
- // https://github.com/inokawa/virtua/pull/160
88
- cache._offsets[0] = 0;
89
- cache._computedOffsetIndex = 0;
90
- }
91
- let i = cache._computedOffsetIndex;
92
- let top = cache._offsets[i];
93
- while (i < index) {
94
- top += getItemSize(cache, i);
95
- cache._offsets[++i] = top;
96
- }
97
- // mark as measured
98
- cache._computedOffsetIndex = index;
99
- return top;
52
+ var getItemOffset = (cache, index) => {
53
+ if (!cache._length) return 0;
54
+ if (cache._computedOffsetIndex >= index) {
55
+ return cache._offsets[index];
56
+ }
57
+ if (cache._computedOffsetIndex < 0) {
58
+ cache._offsets[0] = 0;
59
+ cache._computedOffsetIndex = 0;
60
+ }
61
+ let i = cache._computedOffsetIndex;
62
+ let top = cache._offsets[i];
63
+ while (i < index) {
64
+ top += getItemSize(cache, i);
65
+ cache._offsets[++i] = top;
66
+ }
67
+ cache._computedOffsetIndex = index;
68
+ return top;
100
69
  };
101
- /**
102
- * Finds the index of an item in the cache whose computed offset is closest to the specified offset.
103
- *
104
- * @internal
105
- */
106
- const findIndex = (cache, offset, low = 0, high = cache._length - 1) => {
107
- // Find with binary search
108
- let found = low;
109
- while (low <= high) {
110
- const mid = floor((low + high) / 2);
111
- if (getItemOffset(cache, mid) <= offset) {
112
- found = mid;
113
- low = mid + 1;
114
- }
115
- else {
116
- high = mid - 1;
117
- }
118
- }
119
- return clamp(found, 0, cache._length - 1);
70
+ var findIndex = (cache, offset, low = 0, high = cache._length - 1) => {
71
+ let found = low;
72
+ while (low <= high) {
73
+ const mid = floor((low + high) / 2);
74
+ if (getItemOffset(cache, mid) <= offset) {
75
+ found = mid;
76
+ low = mid + 1;
77
+ } else {
78
+ high = mid - 1;
79
+ }
80
+ }
81
+ return clamp(found, 0, cache._length - 1);
120
82
  };
121
- /**
122
- * @internal
123
- */
124
- const computeRange = (cache, startOffset, endOffset, prevStartIndex) => {
125
- // Clamp because prevStartIndex may exceed the limit when children decreased a lot after scrolling
126
- prevStartIndex = min(prevStartIndex, cache._length - 1);
127
- if (getItemOffset(cache, prevStartIndex) <= startOffset) {
128
- // search forward
129
- // start <= end, prevStartIndex <= start
130
- const end = findIndex(cache, endOffset, prevStartIndex);
131
- return [findIndex(cache, startOffset, prevStartIndex, end), end];
132
- }
133
- else {
134
- // search backward
135
- // start <= end, start <= prevStartIndex
136
- const start = findIndex(cache, startOffset, undefined, prevStartIndex);
137
- return [start, findIndex(cache, endOffset, start)];
138
- }
83
+ var computeRange = (cache, startOffset, endOffset, prevStartIndex) => {
84
+ prevStartIndex = min(prevStartIndex, cache._length - 1);
85
+ if (getItemOffset(cache, prevStartIndex) <= startOffset) {
86
+ const end = findIndex(cache, endOffset, prevStartIndex);
87
+ return [findIndex(cache, startOffset, prevStartIndex, end), end];
88
+ } else {
89
+ const start = findIndex(cache, startOffset, undefined, prevStartIndex);
90
+ return [start, findIndex(cache, endOffset, start)];
91
+ }
139
92
  };
140
- /**
141
- * @internal
142
- */
143
- const estimateDefaultItemSize = (cache, startIndex) => {
144
- let measuredCountBeforeStart = 0;
145
- // This function will be called after measurement so measured size array must be longer than 0
146
- const measuredSizes = [];
147
- cache._sizes.forEach((s, i) => {
148
- if (s !== UNCACHED) {
149
- measuredSizes.push(s);
150
- if (i < startIndex) {
151
- measuredCountBeforeStart++;
152
- }
153
- }
154
- });
155
- // Discard cache for now
156
- cache._computedOffsetIndex = -1;
157
- // Calculate median
158
- const sorted = sort(measuredSizes);
159
- const len = sorted.length;
160
- const mid = (len / 2) | 0;
161
- const median = len % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
162
- const prevDefaultItemSize = cache._defaultItemSize;
163
- // Calculate diff of unmeasured items before start
164
- return (((cache._defaultItemSize = median) - prevDefaultItemSize) *
165
- max(startIndex - measuredCountBeforeStart, 0));
93
+ var estimateDefaultItemSize = (cache, startIndex) => {
94
+ let measuredCountBeforeStart = 0;
95
+ const measuredSizes = [];
96
+ cache._sizes.forEach((s, i) => {
97
+ if (s !== -1) {
98
+ measuredSizes.push(s);
99
+ if (i < startIndex) {
100
+ measuredCountBeforeStart++;
101
+ }
102
+ }
103
+ });
104
+ cache._computedOffsetIndex = -1;
105
+ const sorted = sort(measuredSizes);
106
+ const len = sorted.length;
107
+ const mid = len / 2 | 0;
108
+ const median = len % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
109
+ const prevDefaultItemSize = cache._defaultItemSize;
110
+ return ((cache._defaultItemSize = median) - prevDefaultItemSize) * max(startIndex - measuredCountBeforeStart, 0);
166
111
  };
167
- /**
168
- * @internal
169
- */
170
- const initCache = (length, itemSize, sizes) => {
171
- return {
172
- _defaultItemSize: itemSize,
173
- _sizes: sizes
174
- ? // https://github.com/inokawa/virtua/issues/441
175
- fill(sizes.slice(0, min(length, sizes.length)), max(0, length - sizes.length))
176
- : fill([], length),
177
- _length: length,
178
- _computedOffsetIndex: -1,
179
- _offsets: fill([], length + 1),
180
- };
112
+ var initCache = (length, itemSize, sizes) => {
113
+ return {
114
+ _defaultItemSize: itemSize,
115
+ _sizes: sizes ? fill(sizes.slice(0, min(length, sizes.length)), max(0, length - sizes.length)) : fill([], length),
116
+ _length: length,
117
+ _computedOffsetIndex: -1,
118
+ _offsets: fill([], length + 1)
119
+ };
181
120
  };
182
- /**
183
- * @internal
184
- */
185
- const takeCacheSnapshot = (cache) => {
186
- return [cache._sizes.slice(), cache._defaultItemSize];
121
+ var takeCacheSnapshot = (cache) => {
122
+ return [cache._sizes.slice(), cache._defaultItemSize];
187
123
  };
188
- /**
189
- * @internal
190
- */
191
- const updateCacheLength = (cache, length, isShift) => {
192
- const diff = length - cache._length;
193
- cache._computedOffsetIndex = isShift
194
- ? // Discard cache for now
195
- -1
196
- : min(length - 1, cache._computedOffsetIndex);
197
- cache._length = length;
198
- if (diff > 0) {
199
- // Added
200
- fill(cache._offsets, diff);
201
- fill(cache._sizes, diff, isShift);
202
- return cache._defaultItemSize * diff;
203
- }
204
- else {
205
- // Removed
206
- cache._offsets.splice(diff);
207
- return (isShift ? cache._sizes.splice(0, -diff) : cache._sizes.splice(diff)).reduce((acc, removed) => acc - (removed === UNCACHED ? cache._defaultItemSize : removed), 0);
208
- }
124
+ var updateCacheLength = (cache, length, isShift) => {
125
+ const diff = length - cache._length;
126
+ cache._computedOffsetIndex = isShift ? -1 : min(length - 1, cache._computedOffsetIndex);
127
+ cache._length = length;
128
+ if (diff > 0) {
129
+ fill(cache._offsets, diff);
130
+ fill(cache._sizes, diff, isShift);
131
+ return cache._defaultItemSize * diff;
132
+ } else {
133
+ cache._offsets.splice(diff);
134
+ return (isShift ? cache._sizes.splice(0, -diff) : cache._sizes.splice(diff)).reduce((acc, removed) => acc - (removed === -1 ? cache._defaultItemSize : removed), 0);
135
+ }
209
136
  };
210
137
 
211
- /**
212
- * @internal
213
- */
214
- const isBrowser = typeof window !== "undefined";
215
- /**
216
- * @internal
217
- */
218
- const getDocumentElement = (doc) => doc.documentElement;
219
- /**
220
- * @internal
221
- */
222
- const getCurrentDocument = (node) => node.ownerDocument;
223
- /**
224
- * @internal
225
- */
226
- const getCurrentWindow = (doc) => doc.defaultView;
227
- /**
228
- * Currently, all browsers on iOS/iPadOS are WebKit, including WebView.
229
- * @internal
230
- */
231
- const isIOSWebKit = /*#__PURE__*/ once(() => {
232
- if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
233
- return true;
234
- }
235
- // Modern iPad detection (iPadOS 13+)
236
- // iPadOS 13+ reports the same userAgent/platform information as macOS, to enable desktop sites.
237
- // So we treat devices that have macOS like information but with touch support as iPadOS.
238
- // https://stackoverflow.com/questions/57776001/how-to-detect-ipad-pro-as-ipad-using-javascript
239
- return navigator.platform === "MacIntel" && navigator.maxTouchPoints > 0;
138
+ //#endregion
139
+ //#region src/core/environment.ts
140
+ var isBrowser = typeof window !== "undefined";
141
+ var getDocumentElement = (doc) => doc.documentElement;
142
+ var getCurrentDocument = (node) => node.ownerDocument;
143
+ var getCurrentWindow = (doc) => doc.defaultView;
144
+ var isIOSWebKit = /*#__PURE__*/ once(() => {
145
+ if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
146
+ return true;
147
+ }
148
+ return navigator.platform === "MacIntel" && navigator.maxTouchPoints > 0;
240
149
  });
241
- /**
242
- * @internal
243
- */
244
- const isSmoothScrollSupported = /*#__PURE__*/ once(() => {
245
- return "scrollBehavior" in getDocumentElement(document).style;
150
+ var isSmoothScrollSupported = /*#__PURE__*/ once(() => {
151
+ return "scrollBehavior" in getDocumentElement(document).style;
246
152
  });
247
153
 
248
- const MAX_INT_32 = 0x7fffffff;
249
- const SCROLL_IDLE = 0;
250
- const SCROLL_DOWN = 1;
251
- const SCROLL_UP = 2;
252
- const SCROLL_BY_NATIVE = 0;
253
- const SCROLL_BY_MANUAL_SCROLL = 1;
254
- const SCROLL_BY_SHIFT = 2;
255
- /** @internal */
256
- const ACTION_SCROLL = 1;
257
- /** @internal */
258
- const ACTION_SCROLL_END = 2;
259
- /** @internal */
260
- const ACTION_ITEM_RESIZE = 3;
261
- /** @internal */
262
- const ACTION_VIEWPORT_RESIZE = 4;
263
- /** @internal */
264
- const ACTION_ITEMS_LENGTH_CHANGE = 5;
265
- /** @internal */
266
- const ACTION_START_OFFSET_CHANGE = 6;
267
- /** @internal */
268
- const ACTION_MANUAL_SCROLL = 7;
269
- /** @internal */
270
- const ACTION_BEFORE_MANUAL_SMOOTH_SCROLL = 8;
271
- /** @internal */
272
- const UPDATE_VIRTUAL_STATE = 0b0001;
273
- /** @internal */
274
- const UPDATE_SIZE_EVENT = 0b0010;
275
- /** @internal */
276
- const UPDATE_SCROLL_EVENT = 0b0100;
277
- /** @internal */
278
- const UPDATE_SCROLL_END_EVENT = 0b1000;
279
- /**
280
- * @internal
281
- */
282
- const getScrollSize = (store) => {
283
- return max(store.$getTotalSize(), store.$getViewportSize());
154
+ //#endregion
155
+ //#region src/core/store.ts
156
+ var MAX_INT_32 = 2147483647;
157
+ var SCROLL_IDLE = 0;
158
+ var SCROLL_DOWN = 1;
159
+ var SCROLL_UP = 2;
160
+ var SCROLL_BY_NATIVE = 0;
161
+ var SCROLL_BY_MANUAL_SCROLL = 1;
162
+ var SCROLL_BY_SHIFT = 2;
163
+ var ACTION_SCROLL = 1;
164
+ var ACTION_SCROLL_END = 2;
165
+ var ACTION_ITEM_RESIZE = 3;
166
+ var ACTION_VIEWPORT_RESIZE = 4;
167
+ var ACTION_ITEMS_LENGTH_CHANGE = 5;
168
+ var ACTION_START_OFFSET_CHANGE = 6;
169
+ var ACTION_MANUAL_SCROLL = 7;
170
+ var ACTION_BEFORE_MANUAL_SMOOTH_SCROLL = 8;
171
+ var UPDATE_VIRTUAL_STATE = 1;
172
+ var UPDATE_SIZE_EVENT = 2;
173
+ var UPDATE_SCROLL_EVENT = 4;
174
+ var UPDATE_SCROLL_END_EVENT = 8;
175
+ var getScrollSize = (store) => {
176
+ return max(store.$getTotalSize(), store.$getViewportSize());
284
177
  };
285
- /**
286
- * @internal
287
- */
288
- const createVirtualStore = (elementsCount, itemSize = 40, ssrCount = 0, cacheSnapshot, shouldAutoEstimateItemSize = false) => {
289
- let isSSR = !!ssrCount;
290
- let stateVersion = 1;
291
- let viewportSize = 0;
292
- let startSpacerSize = 0;
293
- let scrollOffset = 0;
294
- let jump = 0;
295
- let pendingJump = 0;
296
- let _flushedJump = 0;
297
- let _scrollDirection = SCROLL_IDLE;
298
- let _scrollMode = SCROLL_BY_NATIVE;
299
- let _frozenRange = NULL;
300
- let _prevRange = [0, isSSR ? max(ssrCount - 1, 0) : -1];
301
- let _totalMeasuredSize = 0;
302
- let _isViewportMeasured = false;
303
- const cache = initCache(elementsCount, cacheSnapshot
304
- ? cacheSnapshot[1]
305
- : itemSize, cacheSnapshot && cacheSnapshot[0]);
306
- const subscribers = new Set();
307
- const getRelativeScrollOffset = () => scrollOffset - startSpacerSize;
308
- const getVisibleOffset = () => getRelativeScrollOffset() + pendingJump + jump;
309
- const getRange = (startOffset, endOffset) => {
310
- return computeRange(cache, startOffset, endOffset, _prevRange[0]);
311
- };
312
- const getTotalSize = () => getItemOffset(cache, cache._length);
313
- const getItemOffset$1 = (index, fromEnd) => {
314
- const offset = getItemOffset(cache, index) - pendingJump;
315
- if (fromEnd) {
316
- return getTotalSize() - offset - getItemSize$1(index);
317
- }
318
- return offset;
319
- };
320
- const getItemSize$1 = (index) => {
321
- return getItemSize(cache, index);
322
- };
323
- const isSizeEqual = (index, value = UNCACHED) => {
324
- return cache._sizes[index] === value;
325
- };
326
- const applyJump = (j) => {
327
- if (j) {
328
- if (
329
- // In iOS WebKit browsers, updating scroll position will stop scrolling so it have to be deferred during scrolling.
330
- (isIOSWebKit() && _scrollDirection !== SCROLL_IDLE) ||
331
- // Before imperative smooth scrolling, we measure all items which may be visible during scrolling.
332
- // However, especially in Firefox, there are rare cases where items resize while scrolling, which can stop smooth scrolling.
333
- (_frozenRange && _scrollMode === SCROLL_BY_MANUAL_SCROLL)) {
334
- pendingJump += j;
335
- }
336
- else {
337
- jump += j;
338
- }
339
- }
340
- };
341
- return {
342
- $dispose: () => {
343
- subscribers.clear();
344
- },
345
- $getStateVersion: () => stateVersion,
346
- $getCacheSnapshot: () => {
347
- return takeCacheSnapshot(cache);
348
- },
349
- $getRange: (bufferSize = 200) => {
350
- if (!_isViewportMeasured || isSSR) {
351
- // Return range for SSR, or return [0, -1] to render nothing, until the scroll offset and viewport size are determined.
352
- // https://github.com/inokawa/virtua/issues/415
353
- // https://github.com/inokawa/virtua/pull/818
354
- return _prevRange;
355
- }
356
- let startIndex;
357
- let endIndex;
358
- if (_flushedJump) {
359
- // Return previous range for consistent render until next scroll event comes in.
360
- // And it must be clamped. https://github.com/inokawa/virtua/issues/597
361
- [startIndex, endIndex] = _prevRange;
362
- }
363
- else {
364
- let startOffset = max(0, getVisibleOffset());
365
- let endOffset = startOffset + viewportSize;
366
- // For faster initial render pass, returns without buffer if measurement seems to be in progress.
367
- if (!shouldAutoEstimateItemSize) {
368
- bufferSize = max(0, bufferSize);
369
- if (_scrollDirection !== SCROLL_DOWN) {
370
- startOffset -= bufferSize;
371
- }
372
- if (_scrollDirection !== SCROLL_UP) {
373
- endOffset += bufferSize;
374
- }
375
- }
376
- [startIndex, endIndex] = _prevRange = getRange(max(0, startOffset), max(0, endOffset));
377
- if (_frozenRange) {
378
- startIndex = min(startIndex, _frozenRange[0]);
379
- endIndex = max(endIndex, _frozenRange[1]);
380
- }
381
- }
382
- return [max(startIndex, 0), min(endIndex, cache._length - 1)];
383
- },
384
- $findItemIndex: (offset) => findIndex(cache, offset - startSpacerSize),
385
- $isUnmeasuredItem: isSizeEqual,
386
- $getItemOffset: getItemOffset$1,
387
- $getItemSize: getItemSize$1,
388
- $getItemsLength: () => cache._length,
389
- $getScrollOffset: () => scrollOffset,
390
- $isScrolling: () => _scrollDirection !== SCROLL_IDLE,
391
- $getViewportSize: () => viewportSize,
392
- $getStartSpacerSize: () => startSpacerSize,
393
- $getTotalSize: getTotalSize,
394
- _flushJump: () => {
395
- _flushedJump = jump;
396
- jump = 0;
397
- return [_flushedJump, _scrollMode === SCROLL_BY_SHIFT];
398
- },
399
- $subscribe: (target, cb) => {
400
- const sub = [target, cb];
401
- subscribers.add(sub);
402
- return () => {
403
- subscribers.delete(sub);
404
- };
405
- },
406
- $update: (type, payload) => {
407
- let shouldFlushPendingJump;
408
- let shouldSync;
409
- let mutated = 0;
410
- switch (type) {
411
- case ACTION_SCROLL: {
412
- if (payload === scrollOffset && _scrollMode === SCROLL_BY_NATIVE) {
413
- // Ignore scroll events from different direction
414
- break;
415
- }
416
- const flushedJump = _flushedJump;
417
- _flushedJump = 0;
418
- const delta = payload - scrollOffset;
419
- const distance = abs(delta);
420
- // Scroll event after jump compensation is not reliable because it may result in the opposite direction.
421
- // The delta of artificial scroll may not be equal with the jump because it may be batched with other scrolls.
422
- // And at least in latest Chrome/Firefox/Safari in 2023, setting value to scrollTop/scrollLeft can lose subpixel because its integer (sometimes float probably depending on dpr).
423
- const isJustJumped = flushedJump && distance < abs(flushedJump) + 1;
424
- // Scroll events are dispatched enough so it's ok to skip some of them.
425
- if (!isJustJumped &&
426
- // Ignore until manual scrolling
427
- _scrollMode === SCROLL_BY_NATIVE) {
428
- _scrollDirection = delta < 0 ? SCROLL_UP : SCROLL_DOWN;
429
- }
430
- // TODO This will cause glitch in reverse infinite scrolling. Disable this until better solution is found.
431
- // if (
432
- // pendingJump &&
433
- // ((_scrollDirection === SCROLL_UP &&
434
- // payload - max(pendingJump, 0) <= 0) ||
435
- // (_scrollDirection === SCROLL_DOWN &&
436
- // payload - min(pendingJump, 0) >= getScrollOffsetMax()))
437
- // ) {
438
- // // Flush if almost reached to start or end
439
- // shouldFlushPendingJump = true;
440
- // }
441
- if (isSSR) {
442
- isSSR = false;
443
- }
444
- scrollOffset = payload;
445
- mutated = UPDATE_SCROLL_EVENT;
446
- // Skip if offset is not changed
447
- // Scroll offset may exceed min or max especially in Safari's elastic scrolling.
448
- const relativeOffset = getRelativeScrollOffset();
449
- if (relativeOffset >= -viewportSize &&
450
- relativeOffset <= getTotalSize()) {
451
- mutated += UPDATE_VIRTUAL_STATE;
452
- // Update synchronously if scrolled a lot
453
- shouldSync = distance > viewportSize;
454
- }
455
- break;
456
- }
457
- case ACTION_SCROLL_END: {
458
- mutated = UPDATE_SCROLL_END_EVENT;
459
- if (_scrollDirection !== SCROLL_IDLE) {
460
- shouldFlushPendingJump = true;
461
- mutated += UPDATE_VIRTUAL_STATE;
462
- }
463
- _scrollDirection = SCROLL_IDLE;
464
- _scrollMode = SCROLL_BY_NATIVE;
465
- _frozenRange = NULL;
466
- break;
467
- }
468
- case ACTION_ITEM_RESIZE: {
469
- const updated = payload.filter(([index, size]) => !isSizeEqual(index, size));
470
- // Skip if all items are cached and not updated
471
- if (!updated.length) {
472
- break;
473
- }
474
- // Calculate jump by resize to minimize junks in appearance
475
- applyJump(updated.reduce((acc, [index, size]) => {
476
- let shouldKeep;
477
- if (
478
- // Keep distance from end during shifting
479
- _scrollMode === SCROLL_BY_SHIFT) {
480
- shouldKeep = true;
481
- }
482
- else if (_frozenRange &&
483
- _scrollMode === SCROLL_BY_MANUAL_SCROLL) {
484
- // https://github.com/inokawa/virtua/issues/380
485
- // https://github.com/inokawa/virtua/issues/758
486
- shouldKeep = index < _frozenRange[0];
487
- }
488
- else {
489
- // Otherwise we should maintain visible position
490
- const start = getRelativeScrollOffset();
491
- const itemOffset = getItemOffset$1(index);
492
- const itemSize = getItemSize$1(index);
493
- shouldKeep =
494
- _scrollDirection !== SCROLL_DOWN &&
495
- _scrollMode === SCROLL_BY_NATIVE
496
- ? // https://github.com/inokawa/virtua/issues/385
497
- // https://github.com/inokawa/virtua/discussions/865
498
- // https://github.com/inokawa/virtua/issues/893
499
- // Use "<=" instead of "<" here so the item whose bottom rests
500
- // exactly on the viewport top (the row directly above an
501
- // item anchored to the top) is compensated too.
502
- itemOffset + itemSize <= start
503
- : // https://github.com/inokawa/virtua/pull/868
504
- itemOffset < start &&
505
- itemOffset + itemSize < start + viewportSize;
506
- }
507
- if (shouldKeep) {
508
- acc += size - getItemSize$1(index);
509
- }
510
- return acc;
511
- }, 0));
512
- // Update item sizes
513
- for (const [index, size] of updated) {
514
- const prevSize = getItemSize$1(index);
515
- const isInitialMeasurement = setItemSize(cache, index, size);
516
- if (shouldAutoEstimateItemSize) {
517
- _totalMeasuredSize += isInitialMeasurement
518
- ? size
519
- : size - prevSize;
520
- }
521
- }
522
- // Estimate initial item size from measured sizes
523
- if (shouldAutoEstimateItemSize &&
524
- viewportSize &&
525
- // If the total size is lower than the viewport, the item may be a empty state
526
- _totalMeasuredSize > viewportSize) {
527
- applyJump(estimateDefaultItemSize(cache, findIndex(cache, getVisibleOffset())));
528
- shouldAutoEstimateItemSize = false;
529
- }
530
- mutated = UPDATE_VIRTUAL_STATE + UPDATE_SIZE_EVENT;
531
- // Synchronous update is necessary in current design to minimize visible glitch in concurrent rendering.
532
- // However this seems to be the main cause of the errors from ResizeObserver.
533
- // https://github.com/inokawa/virtua/issues/470
534
- //
535
- // And in React, synchronous update with flushSync after asynchronous update will overtake the asynchronous one.
536
- // If items resize happens just after scroll, race condition can occur depending on implementation.
537
- shouldSync = true;
538
- break;
539
- }
540
- case ACTION_VIEWPORT_RESIZE: {
541
- if (viewportSize !== payload) {
542
- if (!viewportSize) {
543
- _isViewportMeasured = shouldSync = true;
544
- }
545
- viewportSize = payload;
546
- mutated = UPDATE_VIRTUAL_STATE + UPDATE_SIZE_EVENT;
547
- }
548
- break;
549
- }
550
- case ACTION_ITEMS_LENGTH_CHANGE: {
551
- if (payload[1]) {
552
- applyJump(updateCacheLength(cache, payload[0], true));
553
- _scrollMode = SCROLL_BY_SHIFT;
554
- mutated = UPDATE_VIRTUAL_STATE;
555
- }
556
- else {
557
- updateCacheLength(cache, payload[0]);
558
- // https://github.com/inokawa/virtua/issues/552
559
- // https://github.com/inokawa/virtua/issues/557
560
- mutated = UPDATE_VIRTUAL_STATE;
561
- }
562
- break;
563
- }
564
- case ACTION_START_OFFSET_CHANGE: {
565
- startSpacerSize = payload;
566
- break;
567
- }
568
- case ACTION_MANUAL_SCROLL: {
569
- _scrollMode = SCROLL_BY_MANUAL_SCROLL;
570
- break;
571
- }
572
- case ACTION_BEFORE_MANUAL_SMOOTH_SCROLL: {
573
- _frozenRange = getRange(payload, payload + viewportSize);
574
- mutated = UPDATE_VIRTUAL_STATE;
575
- break;
576
- }
577
- }
578
- if (mutated) {
579
- stateVersion = (stateVersion & MAX_INT_32) + 1;
580
- if (shouldFlushPendingJump && pendingJump) {
581
- jump += pendingJump;
582
- pendingJump = 0;
583
- }
584
- subscribers.forEach(([target, cb]) => {
585
- // Early return to skip React's computation
586
- if (!(mutated & target)) {
587
- return;
588
- }
589
- // https://github.com/facebook/react/issues/25191
590
- // https://github.com/facebook/react/blob/a5fc797db14c6e05d4d5c4dbb22a0dd70d41f5d5/packages/react-reconciler/src/ReactFiberWorkLoop.js#L1443-L1447
591
- cb(shouldSync);
592
- });
593
- }
594
- },
595
- };
178
+ var createVirtualStore = (elementsCount, itemSize = 40, ssrCount = 0, cacheSnapshot, shouldAutoEstimateItemSize = false) => {
179
+ let isSSR = !!ssrCount;
180
+ let stateVersion = 1;
181
+ let viewportSize = 0;
182
+ let startSpacerSize = 0;
183
+ let scrollOffset = 0;
184
+ let jump = 0;
185
+ let pendingJump = 0;
186
+ let _flushedJump = 0;
187
+ let _scrollDirection = SCROLL_IDLE;
188
+ let _scrollMode = SCROLL_BY_NATIVE;
189
+ let _frozenRange = null;
190
+ let _prevRange = [0, isSSR ? max(ssrCount - 1, 0) : -1];
191
+ let _totalMeasuredSize = 0;
192
+ let _isViewportMeasured = false;
193
+ const cache = initCache(elementsCount, cacheSnapshot ? cacheSnapshot[1] : itemSize, cacheSnapshot && cacheSnapshot[0]);
194
+ const subscribers = new Set();
195
+ const getRelativeScrollOffset = () => scrollOffset - startSpacerSize;
196
+ const getVisibleOffset = () => getRelativeScrollOffset() + pendingJump + jump;
197
+ const getRange = (startOffset, endOffset) => {
198
+ return computeRange(cache, startOffset, endOffset, _prevRange[0]);
199
+ };
200
+ const getTotalSize = () => getItemOffset(cache, cache._length);
201
+ const getItemOffset$1 = (index, fromEnd) => {
202
+ const offset = getItemOffset(cache, index) - pendingJump;
203
+ if (fromEnd) {
204
+ return getTotalSize() - offset - getItemSize$1(index);
205
+ }
206
+ return offset;
207
+ };
208
+ const getItemSize$1 = (index) => {
209
+ return getItemSize(cache, index);
210
+ };
211
+ const isSizeEqual = (index, value = -1) => {
212
+ return cache._sizes[index] === value;
213
+ };
214
+ const applyJump = (j) => {
215
+ if (j) {
216
+ if (isIOSWebKit() && _scrollDirection !== SCROLL_IDLE || _frozenRange && _scrollMode === SCROLL_BY_MANUAL_SCROLL) {
217
+ pendingJump += j;
218
+ } else {
219
+ jump += j;
220
+ }
221
+ }
222
+ };
223
+ return {
224
+ $dispose: () => {
225
+ subscribers.clear();
226
+ },
227
+ $getStateVersion: () => stateVersion,
228
+ $getCacheSnapshot: () => {
229
+ return takeCacheSnapshot(cache);
230
+ },
231
+ $getRange: (bufferSize = 200) => {
232
+ if (!_isViewportMeasured || isSSR) {
233
+ return _prevRange;
234
+ }
235
+ let startIndex;
236
+ let endIndex;
237
+ if (_flushedJump) {
238
+ [startIndex, endIndex] = _prevRange;
239
+ } else {
240
+ let startOffset = max(0, getVisibleOffset());
241
+ let endOffset = startOffset + viewportSize;
242
+ if (!shouldAutoEstimateItemSize) {
243
+ bufferSize = max(0, bufferSize);
244
+ if (_scrollDirection !== SCROLL_DOWN) {
245
+ startOffset -= bufferSize;
246
+ }
247
+ if (_scrollDirection !== SCROLL_UP) {
248
+ endOffset += bufferSize;
249
+ }
250
+ }
251
+ [startIndex, endIndex] = _prevRange = getRange(max(0, startOffset), max(0, endOffset));
252
+ if (_frozenRange) {
253
+ startIndex = min(startIndex, _frozenRange[0]);
254
+ endIndex = max(endIndex, _frozenRange[1]);
255
+ }
256
+ }
257
+ return [max(startIndex, 0), min(endIndex, cache._length - 1)];
258
+ },
259
+ $findItemIndex: (offset) => findIndex(cache, offset - startSpacerSize),
260
+ $isUnmeasuredItem: isSizeEqual,
261
+ $getItemOffset: getItemOffset$1,
262
+ $getItemSize: getItemSize$1,
263
+ $getItemsLength: () => cache._length,
264
+ $getScrollOffset: () => scrollOffset,
265
+ $isScrolling: () => _scrollDirection !== SCROLL_IDLE,
266
+ $getViewportSize: () => viewportSize,
267
+ $getStartSpacerSize: () => startSpacerSize,
268
+ $getTotalSize: getTotalSize,
269
+ _flushJump: () => {
270
+ _flushedJump = jump;
271
+ jump = 0;
272
+ return [_flushedJump, _scrollMode === SCROLL_BY_SHIFT];
273
+ },
274
+ $subscribe: (target, cb) => {
275
+ const sub = [target, cb];
276
+ subscribers.add(sub);
277
+ return () => {
278
+ subscribers.delete(sub);
279
+ };
280
+ },
281
+ $update: (type, payload) => {
282
+ let shouldFlushPendingJump;
283
+ let shouldSync;
284
+ let mutated = 0;
285
+ switch (type) {
286
+ case 1: {
287
+ if (payload === scrollOffset && _scrollMode === SCROLL_BY_NATIVE) {
288
+ break;
289
+ }
290
+ const flushedJump = _flushedJump;
291
+ _flushedJump = 0;
292
+ const delta = payload - scrollOffset;
293
+ const distance = abs(delta);
294
+ const isJustJumped = flushedJump && distance < abs(flushedJump) + 1;
295
+ if (!isJustJumped && _scrollMode === SCROLL_BY_NATIVE) {
296
+ _scrollDirection = delta < 0 ? SCROLL_UP : SCROLL_DOWN;
297
+ }
298
+ if (isSSR) {
299
+ isSSR = false;
300
+ }
301
+ scrollOffset = payload;
302
+ mutated = 4;
303
+ const relativeOffset = getRelativeScrollOffset();
304
+ if (relativeOffset >= -viewportSize && relativeOffset <= getTotalSize()) {
305
+ mutated += 1;
306
+ shouldSync = distance > viewportSize;
307
+ }
308
+ break;
309
+ }
310
+ case 2: {
311
+ mutated = 8;
312
+ if (_scrollDirection !== SCROLL_IDLE) {
313
+ shouldFlushPendingJump = true;
314
+ mutated += 1;
315
+ }
316
+ _scrollDirection = SCROLL_IDLE;
317
+ _scrollMode = SCROLL_BY_NATIVE;
318
+ _frozenRange = null;
319
+ break;
320
+ }
321
+ case 3: {
322
+ const updated = payload.filter(([index, size]) => !isSizeEqual(index, size));
323
+ if (!updated.length) {
324
+ break;
325
+ }
326
+ applyJump(updated.reduce((acc, [index, size]) => {
327
+ let shouldKeep;
328
+ if (_scrollMode === SCROLL_BY_SHIFT) {
329
+ shouldKeep = true;
330
+ } else if (_frozenRange && _scrollMode === SCROLL_BY_MANUAL_SCROLL) {
331
+ shouldKeep = index < _frozenRange[0];
332
+ } else {
333
+ const start = getRelativeScrollOffset();
334
+ const itemOffset = getItemOffset$1(index);
335
+ const itemSize = getItemSize$1(index);
336
+ shouldKeep = _scrollDirection !== SCROLL_DOWN && _scrollMode === SCROLL_BY_NATIVE ? itemOffset + itemSize <= start : itemOffset < start && itemOffset + itemSize < start + viewportSize;
337
+ }
338
+ if (shouldKeep) {
339
+ acc += size - getItemSize$1(index);
340
+ }
341
+ return acc;
342
+ }, 0));
343
+ for (const [index, size] of updated) {
344
+ const prevSize = getItemSize$1(index);
345
+ const isInitialMeasurement = setItemSize(cache, index, size);
346
+ if (shouldAutoEstimateItemSize) {
347
+ _totalMeasuredSize += isInitialMeasurement ? size : size - prevSize;
348
+ }
349
+ }
350
+ if (shouldAutoEstimateItemSize && viewportSize && _totalMeasuredSize > viewportSize) {
351
+ applyJump(estimateDefaultItemSize(cache, findIndex(cache, getVisibleOffset())));
352
+ shouldAutoEstimateItemSize = false;
353
+ }
354
+ mutated = 1 + 2;
355
+ shouldSync = true;
356
+ break;
357
+ }
358
+ case 4: {
359
+ if (viewportSize !== payload) {
360
+ if (!viewportSize) {
361
+ _isViewportMeasured = shouldSync = true;
362
+ }
363
+ viewportSize = payload;
364
+ mutated = 1 + 2;
365
+ }
366
+ break;
367
+ }
368
+ case 5: {
369
+ if (payload[1]) {
370
+ applyJump(updateCacheLength(cache, payload[0], true));
371
+ _scrollMode = SCROLL_BY_SHIFT;
372
+ mutated = 1;
373
+ } else {
374
+ updateCacheLength(cache, payload[0]);
375
+ mutated = 1;
376
+ }
377
+ break;
378
+ }
379
+ case 6: {
380
+ startSpacerSize = payload;
381
+ break;
382
+ }
383
+ case 7: {
384
+ _scrollMode = SCROLL_BY_MANUAL_SCROLL;
385
+ break;
386
+ }
387
+ case 8: {
388
+ _frozenRange = getRange(payload, payload + viewportSize);
389
+ mutated = 1;
390
+ break;
391
+ }
392
+ }
393
+ if (mutated) {
394
+ stateVersion = (stateVersion & MAX_INT_32) + 1;
395
+ if (shouldFlushPendingJump && pendingJump) {
396
+ jump += pendingJump;
397
+ pendingJump = 0;
398
+ }
399
+ subscribers.forEach(([target, cb]) => {
400
+ if (!(mutated & target)) {
401
+ return;
402
+ }
403
+ cb(shouldSync);
404
+ });
405
+ }
406
+ }
407
+ };
596
408
  };
597
409
 
598
- const timeout = setTimeout;
599
- const debounce = (fn, ms) => {
600
- let id;
601
- const cancel = () => {
602
- if (id != NULL) {
603
- clearTimeout(id);
604
- }
605
- };
606
- const debouncedFn = () => {
607
- cancel();
608
- id = timeout(() => {
609
- id = NULL;
610
- fn();
611
- }, ms);
612
- };
613
- debouncedFn._cancel = cancel;
614
- return debouncedFn;
410
+ //#endregion
411
+ //#region src/core/scroller.ts
412
+ var timeout = setTimeout;
413
+ var debounce = (fn, ms) => {
414
+ let id;
415
+ const cancel = () => {
416
+ if (id != null) {
417
+ clearTimeout(id);
418
+ }
419
+ };
420
+ const debouncedFn = () => {
421
+ cancel();
422
+ id = timeout(() => {
423
+ id = null;
424
+ fn();
425
+ }, ms);
426
+ };
427
+ debouncedFn._cancel = cancel;
428
+ return debouncedFn;
615
429
  };
616
- /**
617
- * scrollTop/scrollLeft can be negative value under certain styles.
618
- * - direction: rtl https://github.com/othree/jquery.rtl-scroll-type
619
- * - writing-mode https://people.igalia.com/fwang/scrollable-elements-in-non-default-writing-modes/
620
- * - flex-direction: column-reverse/row-reverse
621
- *
622
- * top/left bottom/right
623
- * 0 100 spec compliant bottom/right overflow, or possibly top/left overflow in Chrome earlier than v85
624
- * -100 0 spec compliant top/left overflow
625
- * https://drafts.csswg.org/cssom-view/#scroll-an-element
626
- */
627
- const normalizeScrollOffset = (offset, isNegative) => {
628
- return isNegative ? -offset : offset;
430
+ var normalizeScrollOffset = (offset, isNegative) => {
431
+ return isNegative ? -offset : offset;
629
432
  };
630
- const createScrollObserver = (store, viewport, isHorizontal, getScrollOffset, updateScrollOffset, getStartOffset) => {
631
- const now = Date.now;
632
- let lastScrollTime = 0;
633
- let wheeling = false;
634
- let touching = false;
635
- let justTouchEnded = false;
636
- let stillMomentumScrolling = false;
637
- const onScrollEnd = debounce(() => {
638
- if (wheeling || touching) {
639
- wheeling = false;
640
- // Wait while wheeling or touching
641
- onScrollEnd();
642
- return;
643
- }
644
- justTouchEnded = false;
645
- store.$update(ACTION_SCROLL_END);
646
- }, 150);
647
- const onScroll = () => {
648
- lastScrollTime = now();
649
- if (justTouchEnded) {
650
- stillMomentumScrolling = true;
651
- }
652
- if (getStartOffset) {
653
- store.$update(ACTION_START_OFFSET_CHANGE, getStartOffset());
654
- }
655
- store.$update(ACTION_SCROLL, getScrollOffset());
656
- onScrollEnd();
657
- };
658
- // Infer scroll state also from wheel events
659
- // Sometimes scroll events do not fire when frame dropped even if the visual have been already scrolled
660
- const onWheel = ((e) => {
661
- if (wheeling ||
662
- // Scroll start should be detected with scroll event
663
- !store.$isScrolling() ||
664
- // Probably a pinch-to-zoom gesture
665
- e.ctrlKey) {
666
- return;
667
- }
668
- const timeDelta = now() - lastScrollTime;
669
- if (
670
- // Check if wheel event occurs some time after scrolling
671
- 150 > timeDelta &&
672
- 50 < timeDelta &&
673
- // Get delta before checking deltaMode for firefox behavior
674
- // https://github.com/w3c/uievents/issues/181#issuecomment-392648065
675
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1392460#c34
676
- (isHorizontal ? e.deltaX : e.deltaY)) {
677
- wheeling = true;
678
- }
679
- }); // FIXME type error. why only here?
680
- const onTouchStart = () => {
681
- touching = true;
682
- justTouchEnded = stillMomentumScrolling = false;
683
- };
684
- const onTouchEnd = () => {
685
- touching = false;
686
- if (isIOSWebKit()) {
687
- justTouchEnded = true;
688
- }
689
- };
690
- viewport.addEventListener("scroll", onScroll);
691
- viewport.addEventListener("wheel", onWheel, { passive: true });
692
- viewport.addEventListener("touchstart", onTouchStart, { passive: true });
693
- viewport.addEventListener("touchend", onTouchEnd, { passive: true });
694
- return {
695
- _dispose: () => {
696
- viewport.removeEventListener("scroll", onScroll);
697
- viewport.removeEventListener("wheel", onWheel);
698
- viewport.removeEventListener("touchstart", onTouchStart);
699
- viewport.removeEventListener("touchend", onTouchEnd);
700
- onScrollEnd._cancel();
701
- },
702
- _fixScrollJump: () => {
703
- const [jump, shift] = store._flushJump();
704
- if (!jump)
705
- return;
706
- updateScrollOffset(jump, shift, stillMomentumScrolling);
707
- stillMomentumScrolling = false;
708
- if (shift && store.$getViewportSize() > store.$getTotalSize()) {
709
- // In this case applying jump may not cause scroll.
710
- // Current logic expects scroll event occurs after applying jump so we dispatch it manually.
711
- store.$update(ACTION_SCROLL, getScrollOffset());
712
- }
713
- },
714
- };
433
+ var createScrollObserver = (store, viewport, isHorizontal, getScrollOffset, updateScrollOffset, getStartOffset) => {
434
+ const now = Date.now;
435
+ let lastScrollTime = 0;
436
+ let wheeling = false;
437
+ let touching = false;
438
+ let justTouchEnded = false;
439
+ let stillMomentumScrolling = false;
440
+ const onScrollEnd = debounce(() => {
441
+ if (wheeling || touching) {
442
+ wheeling = false;
443
+ onScrollEnd();
444
+ return;
445
+ }
446
+ justTouchEnded = false;
447
+ store.$update(2);
448
+ }, 150);
449
+ const onScroll = () => {
450
+ lastScrollTime = now();
451
+ if (justTouchEnded) {
452
+ stillMomentumScrolling = true;
453
+ }
454
+ if (getStartOffset) {
455
+ store.$update(6, getStartOffset());
456
+ }
457
+ store.$update(1, getScrollOffset());
458
+ onScrollEnd();
459
+ };
460
+ const onWheel = ((e) => {
461
+ if (wheeling || !store.$isScrolling() || e.ctrlKey) {
462
+ return;
463
+ }
464
+ const timeDelta = now() - lastScrollTime;
465
+ if (150 > timeDelta && 50 < timeDelta && (isHorizontal ? e.deltaX : e.deltaY)) {
466
+ wheeling = true;
467
+ }
468
+ });
469
+ const onTouchStart = () => {
470
+ touching = true;
471
+ justTouchEnded = stillMomentumScrolling = false;
472
+ };
473
+ const onTouchEnd = () => {
474
+ touching = false;
475
+ if (isIOSWebKit()) {
476
+ justTouchEnded = true;
477
+ }
478
+ };
479
+ viewport.addEventListener("scroll", onScroll);
480
+ viewport.addEventListener("wheel", onWheel, { passive: true });
481
+ viewport.addEventListener("touchstart", onTouchStart, { passive: true });
482
+ viewport.addEventListener("touchend", onTouchEnd, { passive: true });
483
+ return {
484
+ _dispose: () => {
485
+ viewport.removeEventListener("scroll", onScroll);
486
+ viewport.removeEventListener("wheel", onWheel);
487
+ viewport.removeEventListener("touchstart", onTouchStart);
488
+ viewport.removeEventListener("touchend", onTouchEnd);
489
+ onScrollEnd._cancel();
490
+ },
491
+ _fixScrollJump: () => {
492
+ const [jump, shift] = store._flushJump();
493
+ if (!jump) return;
494
+ updateScrollOffset(jump, shift, stillMomentumScrolling);
495
+ stillMomentumScrolling = false;
496
+ if (shift && store.$getViewportSize() > store.$getTotalSize()) {
497
+ store.$update(1, getScrollOffset());
498
+ }
499
+ }
500
+ };
715
501
  };
716
- const createScrollScheduler = (store, initialized, scroll) => {
717
- let cancelScroll;
718
- // The given offset will be clamped by browser
719
- // https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
720
- return [
721
- async (getTargetOffset, smooth) => {
722
- // Wait for element assign. The element may be undefined if scrollRef prop is used and scroll is scheduled on mount.
723
- // https://github.com/inokawa/virtua/pull/733
724
- // https://github.com/inokawa/virtua/pull/750
725
- if (!(await initialized())) {
726
- return;
727
- }
728
- if (cancelScroll) {
729
- // Cancel waiting scrollTo
730
- cancelScroll();
731
- }
732
- const waitForMeasurement = () => {
733
- // Wait for the scroll destination items to be measured.
734
- // The measurement will be done asynchronously and the timing is not predictable so we use promise.
735
- const [promise, resolve] = createPromise();
736
- cancelScroll = () => {
737
- resolve(false);
738
- };
739
- // Resize event may not happen when the window/tab is not visible, or during browser back in Safari.
740
- // We have to wait for the initial measurement to avoid failing imperative scroll on mount.
741
- // https://github.com/inokawa/virtua/issues/450
742
- if (store.$getViewportSize()) {
743
- // Cancel when items around scroll destination completely measured
744
- timeout(cancelScroll, 150);
745
- }
746
- return [
747
- promise,
748
- store.$subscribe(UPDATE_SIZE_EVENT, () => {
749
- resolve(true);
750
- }),
751
- ];
752
- };
753
- if (smooth && isSmoothScrollSupported()) {
754
- store.$update(ACTION_BEFORE_MANUAL_SMOOTH_SCROLL, getTargetOffset());
755
- // https://github.com/inokawa/virtua/issues/590
756
- microtask(async () => {
757
- while (true) {
758
- let done = true;
759
- for (let [i, end] = store.$getRange(); i <= end; i++) {
760
- if (store.$isUnmeasuredItem(i)) {
761
- done = false;
762
- break;
763
- }
764
- }
765
- if (done) {
766
- break;
767
- }
768
- const [promise, unsubscribe] = waitForMeasurement();
769
- try {
770
- if (!(await promise)) {
771
- // canceled
772
- return;
773
- }
774
- }
775
- finally {
776
- unsubscribe();
777
- }
778
- }
779
- store.$update(ACTION_MANUAL_SCROLL);
780
- scroll(getTargetOffset(), smooth);
781
- });
782
- }
783
- else {
784
- while (true) {
785
- const [promise, unsubscribe] = waitForMeasurement();
786
- try {
787
- store.$update(ACTION_MANUAL_SCROLL);
788
- scroll(getTargetOffset());
789
- if (!(await promise)) {
790
- // canceled or finished
791
- return;
792
- }
793
- }
794
- finally {
795
- unsubscribe();
796
- }
797
- }
798
- }
799
- },
800
- () => {
801
- cancelScroll && cancelScroll();
802
- },
803
- ];
502
+ var createScrollScheduler = (store, initialized, scroll) => {
503
+ let cancelScroll;
504
+ return [async (getTargetOffset, smooth) => {
505
+ if (!await initialized()) {
506
+ return;
507
+ }
508
+ if (cancelScroll) {
509
+ cancelScroll();
510
+ }
511
+ const waitForMeasurement = () => {
512
+ const [promise, resolve] = createPromise();
513
+ cancelScroll = () => {
514
+ resolve(false);
515
+ };
516
+ if (store.$getViewportSize()) {
517
+ timeout(cancelScroll, 150);
518
+ }
519
+ return [promise, store.$subscribe(2, () => {
520
+ resolve(true);
521
+ })];
522
+ };
523
+ if (smooth && isSmoothScrollSupported()) {
524
+ store.$update(8, getTargetOffset());
525
+ microtask(async () => {
526
+ while (true) {
527
+ let done = true;
528
+ for (let [i, end] = store.$getRange(); i <= end; i++) {
529
+ if (store.$isUnmeasuredItem(i)) {
530
+ done = false;
531
+ break;
532
+ }
533
+ }
534
+ if (done) {
535
+ break;
536
+ }
537
+ const [promise, unsubscribe] = waitForMeasurement();
538
+ try {
539
+ if (!await promise) {
540
+ return;
541
+ }
542
+ } finally {
543
+ unsubscribe();
544
+ }
545
+ }
546
+ store.$update(7);
547
+ scroll(getTargetOffset(), smooth);
548
+ });
549
+ } else {
550
+ while (true) {
551
+ const [promise, unsubscribe] = waitForMeasurement();
552
+ try {
553
+ store.$update(7);
554
+ scroll(getTargetOffset());
555
+ if (!await promise) {
556
+ return;
557
+ }
558
+ } finally {
559
+ unsubscribe();
560
+ }
561
+ }
562
+ }
563
+ }, () => {
564
+ cancelScroll && cancelScroll();
565
+ }];
804
566
  };
805
- /**
806
- * @internal
807
- */
808
- const createScroller = (store, isHorizontal) => {
809
- let viewportElement;
810
- let scrollObserver;
811
- let initialized = createPromise();
812
- let isNegative = false;
813
- const scrollOffsetKey = isHorizontal ? "scrollLeft" : "scrollTop";
814
- const overflowKey = isHorizontal ? "overflowX" : "overflowY";
815
- const [scheduleScroll, cancelScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
816
- offset = normalizeScrollOffset(offset, isNegative);
817
- if (smooth) {
818
- viewportElement.scrollTo({
819
- [isHorizontal ? "left" : "top"]: offset,
820
- behavior: "smooth",
821
- });
822
- }
823
- else {
824
- viewportElement[scrollOffsetKey] = offset;
825
- }
826
- });
827
- return {
828
- $observe(_, viewport) {
829
- viewportElement = viewport;
830
- if (isHorizontal) {
831
- isNegative = getComputedStyle(viewport).direction === "rtl";
832
- }
833
- scrollObserver = createScrollObserver(store, viewport, isHorizontal, () => normalizeScrollOffset(viewport[scrollOffsetKey], isNegative), (jump, shift, isMomentumScrolling) => {
834
- // If we update scroll position while touching on iOS, the position will be reverted.
835
- // However iOS WebKit fires touch events only once at the beginning of momentum scrolling.
836
- // That means we have no reliable way to confirm still touched or not if user touches more than once during momentum scrolling...
837
- // This is a hack for the suspectable situations, inspired by https://github.com/prud/ios-overflow-scroll-to-top
838
- if (isMomentumScrolling) {
839
- const style = viewport.style;
840
- const prev = style[overflowKey];
841
- style[overflowKey] = "hidden";
842
- timeout(() => {
843
- style[overflowKey] = prev;
844
- });
845
- }
846
- // Use absolute position not to exceed scrollable bounds
847
- // https://github.com/inokawa/virtua/discussions/475
848
- viewport[scrollOffsetKey] = normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative);
849
- if (shift) {
850
- // https://github.com/inokawa/virtua/issues/357
851
- cancelScroll();
852
- }
853
- });
854
- initialized[1](true);
855
- },
856
- $dispose() {
857
- scrollObserver && scrollObserver._dispose();
858
- initialized[1](false);
859
- // https://github.com/inokawa/virtua/pull/765
860
- initialized = createPromise();
861
- },
862
- $isNegative: () => isNegative,
863
- $scrollTo(offset) {
864
- scheduleScroll(() => offset);
865
- },
866
- $scrollBy(offset) {
867
- offset += store.$getScrollOffset();
868
- scheduleScroll(() => offset);
869
- },
870
- $scrollToIndex(index, { align, smooth, offset = 0 } = {}) {
871
- index = clamp(index, 0, store.$getItemsLength() - 1);
872
- if (align === "nearest") {
873
- const itemOffset = store.$getItemOffset(index);
874
- const scrollOffset = store.$getScrollOffset();
875
- if (itemOffset < scrollOffset) {
876
- align = "start";
877
- }
878
- else if (itemOffset + store.$getItemSize(index) >
879
- scrollOffset + store.$getViewportSize()) {
880
- align = "end";
881
- }
882
- else {
883
- // already completely visible
884
- return;
885
- }
886
- }
887
- scheduleScroll(() => {
888
- return (offset +
889
- store.$getStartSpacerSize() +
890
- store.$getItemOffset(index) +
891
- (align === "end"
892
- ? store.$getItemSize(index) - store.$getViewportSize()
893
- : align === "center"
894
- ? (store.$getItemSize(index) - store.$getViewportSize()) / 2
895
- : 0));
896
- }, smooth);
897
- },
898
- $fixScrollJump: () => {
899
- scrollObserver && scrollObserver._fixScrollJump();
900
- },
901
- };
567
+ var createScroller = (store, isHorizontal) => {
568
+ let viewportElement;
569
+ let scrollObserver;
570
+ let initialized = createPromise();
571
+ let isNegative = false;
572
+ const scrollOffsetKey = isHorizontal ? "scrollLeft" : "scrollTop";
573
+ const overflowKey = isHorizontal ? "overflowX" : "overflowY";
574
+ const [scheduleScroll, cancelScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
575
+ offset = normalizeScrollOffset(offset, isNegative);
576
+ if (smooth) {
577
+ viewportElement.scrollTo({
578
+ [isHorizontal ? "left" : "top"]: offset,
579
+ behavior: "smooth"
580
+ });
581
+ } else {
582
+ viewportElement[scrollOffsetKey] = offset;
583
+ }
584
+ });
585
+ return {
586
+ $observe(_, viewport) {
587
+ viewportElement = viewport;
588
+ if (isHorizontal) {
589
+ isNegative = getComputedStyle(viewport).direction === "rtl";
590
+ }
591
+ scrollObserver = createScrollObserver(store, viewport, isHorizontal, () => normalizeScrollOffset(viewport[scrollOffsetKey], isNegative), (jump, shift, isMomentumScrolling) => {
592
+ if (isMomentumScrolling) {
593
+ const style = viewport.style;
594
+ const prev = style[overflowKey];
595
+ style[overflowKey] = "hidden";
596
+ timeout(() => {
597
+ style[overflowKey] = prev;
598
+ });
599
+ }
600
+ viewport[scrollOffsetKey] = normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative);
601
+ if (shift) {
602
+ cancelScroll();
603
+ }
604
+ });
605
+ initialized[1](true);
606
+ },
607
+ $dispose() {
608
+ scrollObserver && scrollObserver._dispose();
609
+ initialized[1](false);
610
+ initialized = createPromise();
611
+ },
612
+ $isNegative: () => isNegative,
613
+ $scrollTo(offset) {
614
+ scheduleScroll(() => offset);
615
+ },
616
+ $scrollBy(offset) {
617
+ offset += store.$getScrollOffset();
618
+ scheduleScroll(() => offset);
619
+ },
620
+ $scrollToIndex(index, { align, smooth, offset = 0 } = {}) {
621
+ index = clamp(index, 0, store.$getItemsLength() - 1);
622
+ if (align === "nearest") {
623
+ const itemOffset = store.$getItemOffset(index);
624
+ const scrollOffset = store.$getScrollOffset();
625
+ if (itemOffset < scrollOffset) {
626
+ align = "start";
627
+ } else if (itemOffset + store.$getItemSize(index) > scrollOffset + store.$getViewportSize()) {
628
+ align = "end";
629
+ } else {
630
+ return;
631
+ }
632
+ }
633
+ scheduleScroll(() => {
634
+ return offset + store.$getStartSpacerSize() + store.$getItemOffset(index) + (align === "end" ? store.$getItemSize(index) - store.$getViewportSize() : align === "center" ? (store.$getItemSize(index) - store.$getViewportSize()) / 2 : 0);
635
+ }, smooth);
636
+ },
637
+ $fixScrollJump: () => {
638
+ scrollObserver && scrollObserver._fixScrollJump();
639
+ }
640
+ };
902
641
  };
903
- /**
904
- * @internal
905
- */
906
- const createWindowScroller = (store, isHorizontal) => {
907
- let containerElement;
908
- let scrollObserver;
909
- let initialized = createPromise();
910
- let isNegative = false;
911
- const scrollToKey = isHorizontal ? "left" : "top";
912
- const [scheduleScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
913
- offset = normalizeScrollOffset(offset, isNegative);
914
- const window = getCurrentWindow(getCurrentDocument(containerElement));
915
- if (smooth) {
916
- window.scroll({
917
- [scrollToKey]: offset,
918
- behavior: "smooth",
919
- });
920
- }
921
- else {
922
- window.scroll({
923
- [scrollToKey]: offset,
924
- });
925
- }
926
- });
927
- const calcOffsetToViewport = (node, viewport, window, isHorizontal, offset = 0) => {
928
- // TODO calc offset only when it changes (maybe impossible)
929
- const offsetKey = isHorizontal ? "offsetLeft" : "offsetTop";
930
- const offsetSum = offset +
931
- (isHorizontal && isNegative
932
- ? window.innerWidth - node[offsetKey] - node.offsetWidth
933
- : node[offsetKey]);
934
- const parent = node.offsetParent;
935
- if (node === viewport || !parent) {
936
- return offsetSum;
937
- }
938
- return calcOffsetToViewport(parent, viewport, window, isHorizontal, offsetSum);
939
- };
940
- return {
941
- $observe(container) {
942
- containerElement = container;
943
- const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
944
- const document = getCurrentDocument(container);
945
- const window = getCurrentWindow(document);
946
- if (isHorizontal) {
947
- // Detect RTL document
948
- isNegative =
949
- getComputedStyle(getDocumentElement(document)).direction === "rtl";
950
- }
951
- scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeScrollOffset(window[scrollOffsetKey], isNegative), (jump, shift) => {
952
- // TODO support case two window scrollers exist in the same view
953
- if (shift) {
954
- // Use absolute position not to exceed scrollable bounds
955
- window.scroll({
956
- [scrollToKey]: normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative),
957
- });
958
- }
959
- else {
960
- // Use window.scrollBy here, which causes less layout shift for some reason.
961
- window.scrollBy({
962
- [scrollToKey]: normalizeScrollOffset(jump, isNegative),
963
- });
964
- }
965
- }, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
966
- initialized[1](true);
967
- },
968
- $dispose() {
969
- scrollObserver && scrollObserver._dispose();
970
- containerElement = undefined;
971
- initialized[1](false);
972
- // https://github.com/inokawa/virtua/pull/765
973
- initialized = createPromise();
974
- },
975
- $isNegative: () => isNegative,
976
- $fixScrollJump: () => {
977
- scrollObserver && scrollObserver._fixScrollJump();
978
- },
979
- $scrollToIndex(index, { align, smooth, offset = 0 } = {}) {
980
- if (!containerElement)
981
- return;
982
- index = clamp(index, 0, store.$getItemsLength() - 1);
983
- if (align === "nearest") {
984
- const itemOffset = store.$getItemOffset(index);
985
- const scrollOffset = store.$getScrollOffset();
986
- if (itemOffset < scrollOffset) {
987
- align = "start";
988
- }
989
- else if (itemOffset + store.$getItemSize(index) >
990
- scrollOffset + store.$getViewportSize()) {
991
- align = "end";
992
- }
993
- else {
994
- return;
995
- }
996
- }
997
- const document = getCurrentDocument(containerElement);
998
- const window = getCurrentWindow(document);
999
- const html = getDocumentElement(document);
1000
- const getScrollbarSize = () => store.$getViewportSize() -
1001
- (isHorizontal ? html.clientWidth : html.clientHeight);
1002
- scheduleScroll(() => {
1003
- return (offset +
1004
- // Calculate target scroll position including container's offset from document
1005
- calcOffsetToViewport(containerElement, document.body, window, isHorizontal) +
1006
- // store._getStartSpacerSize() +
1007
- store.$getItemOffset(index) +
1008
- (align === "end"
1009
- ? store.$getItemSize(index) -
1010
- (store.$getViewportSize() - getScrollbarSize())
1011
- : align === "center"
1012
- ? (store.$getItemSize(index) -
1013
- (store.$getViewportSize() - getScrollbarSize())) /
1014
- 2
1015
- : 0));
1016
- }, smooth);
1017
- },
1018
- };
642
+ var createWindowScroller = (store, isHorizontal) => {
643
+ let containerElement;
644
+ let scrollObserver;
645
+ let initialized = createPromise();
646
+ let isNegative = false;
647
+ const scrollToKey = isHorizontal ? "left" : "top";
648
+ const [scheduleScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
649
+ offset = normalizeScrollOffset(offset, isNegative);
650
+ const window = getCurrentWindow(getCurrentDocument(containerElement));
651
+ if (smooth) {
652
+ window.scroll({
653
+ [scrollToKey]: offset,
654
+ behavior: "smooth"
655
+ });
656
+ } else {
657
+ window.scroll({ [scrollToKey]: offset });
658
+ }
659
+ });
660
+ const calcOffsetToViewport = (node, viewport, window, isHorizontal, offset = 0) => {
661
+ const offsetKey = isHorizontal ? "offsetLeft" : "offsetTop";
662
+ const offsetSum = offset + (isHorizontal && isNegative ? window.innerWidth - node[offsetKey] - node.offsetWidth : node[offsetKey]);
663
+ const parent = node.offsetParent;
664
+ if (node === viewport || !parent) {
665
+ return offsetSum;
666
+ }
667
+ return calcOffsetToViewport(parent, viewport, window, isHorizontal, offsetSum);
668
+ };
669
+ return {
670
+ $observe(container) {
671
+ containerElement = container;
672
+ const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
673
+ const document = getCurrentDocument(container);
674
+ const window = getCurrentWindow(document);
675
+ if (isHorizontal) {
676
+ isNegative = getComputedStyle(getDocumentElement(document)).direction === "rtl";
677
+ }
678
+ scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeScrollOffset(window[scrollOffsetKey], isNegative), (jump, shift) => {
679
+ if (shift) {
680
+ window.scroll({ [scrollToKey]: normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative) });
681
+ } else {
682
+ window.scrollBy({ [scrollToKey]: normalizeScrollOffset(jump, isNegative) });
683
+ }
684
+ }, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
685
+ initialized[1](true);
686
+ },
687
+ $dispose() {
688
+ scrollObserver && scrollObserver._dispose();
689
+ containerElement = undefined;
690
+ initialized[1](false);
691
+ initialized = createPromise();
692
+ },
693
+ $isNegative: () => isNegative,
694
+ $fixScrollJump: () => {
695
+ scrollObserver && scrollObserver._fixScrollJump();
696
+ },
697
+ $scrollToIndex(index, { align, smooth, offset = 0 } = {}) {
698
+ if (!containerElement) return;
699
+ index = clamp(index, 0, store.$getItemsLength() - 1);
700
+ if (align === "nearest") {
701
+ const itemOffset = store.$getItemOffset(index);
702
+ const scrollOffset = store.$getScrollOffset();
703
+ if (itemOffset < scrollOffset) {
704
+ align = "start";
705
+ } else if (itemOffset + store.$getItemSize(index) > scrollOffset + store.$getViewportSize()) {
706
+ align = "end";
707
+ } else {
708
+ return;
709
+ }
710
+ }
711
+ const document = getCurrentDocument(containerElement);
712
+ const window = getCurrentWindow(document);
713
+ const html = getDocumentElement(document);
714
+ const getScrollbarSize = () => store.$getViewportSize() - (isHorizontal ? html.clientWidth : html.clientHeight);
715
+ scheduleScroll(() => {
716
+ return offset + calcOffsetToViewport(containerElement, document.body, window, isHorizontal) + store.$getItemOffset(index) + (align === "end" ? store.$getItemSize(index) - (store.$getViewportSize() - getScrollbarSize()) : align === "center" ? (store.$getItemSize(index) - (store.$getViewportSize() - getScrollbarSize())) / 2 : 0);
717
+ }, smooth);
718
+ }
719
+ };
1019
720
  };
1020
- /**
1021
- * @internal
1022
- */
1023
- const createGridScroller = (rowStore, colStore) => {
1024
- const rowScroller = createScroller(rowStore, false);
1025
- const colScroller = createScroller(colStore, true);
1026
- return {
1027
- $observe(container, viewport) {
1028
- rowScroller.$observe(container, viewport);
1029
- colScroller.$observe(container, viewport);
1030
- },
1031
- $dispose() {
1032
- rowScroller.$dispose();
1033
- colScroller.$dispose();
1034
- },
1035
- $isNegative: colScroller.$isNegative,
1036
- $scrollTo(row, col) {
1037
- if (row != null) {
1038
- rowScroller.$scrollTo(row);
1039
- }
1040
- if (col != null) {
1041
- colScroller.$scrollTo(col);
1042
- }
1043
- },
1044
- $scrollBy(row, col) {
1045
- if (row != null) {
1046
- rowScroller.$scrollBy(row);
1047
- }
1048
- if (col != null) {
1049
- colScroller.$scrollBy(col);
1050
- }
1051
- },
1052
- $scrollToIndex(row, col) {
1053
- if (row != null) {
1054
- rowScroller.$scrollToIndex(row);
1055
- }
1056
- if (col != null) {
1057
- colScroller.$scrollToIndex(col);
1058
- }
1059
- },
1060
- $fixScrollJump() {
1061
- rowScroller.$fixScrollJump();
1062
- colScroller.$fixScrollJump();
1063
- },
1064
- };
721
+ var createGridScroller = (rowStore, colStore) => {
722
+ const rowScroller = createScroller(rowStore, false);
723
+ const colScroller = createScroller(colStore, true);
724
+ return {
725
+ $observe(container, viewport) {
726
+ rowScroller.$observe(container, viewport);
727
+ colScroller.$observe(container, viewport);
728
+ },
729
+ $dispose() {
730
+ rowScroller.$dispose();
731
+ colScroller.$dispose();
732
+ },
733
+ $isNegative: colScroller.$isNegative,
734
+ $scrollTo(row, col) {
735
+ if (row != null) {
736
+ rowScroller.$scrollTo(row);
737
+ }
738
+ if (col != null) {
739
+ colScroller.$scrollTo(col);
740
+ }
741
+ },
742
+ $scrollBy(row, col) {
743
+ if (row != null) {
744
+ rowScroller.$scrollBy(row);
745
+ }
746
+ if (col != null) {
747
+ colScroller.$scrollBy(col);
748
+ }
749
+ },
750
+ $scrollToIndex(row, col) {
751
+ if (row != null) {
752
+ rowScroller.$scrollToIndex(row);
753
+ }
754
+ if (col != null) {
755
+ colScroller.$scrollToIndex(col);
756
+ }
757
+ },
758
+ $fixScrollJump() {
759
+ rowScroller.$fixScrollJump();
760
+ colScroller.$fixScrollJump();
761
+ }
762
+ };
1065
763
  };
1066
764
 
1067
- const createResizeObserver = (cb) => {
1068
- let ro;
1069
- return {
1070
- _observe(e) {
1071
- // Initialize ResizeObserver lazily for SSR
1072
- // https://www.w3.org/TR/resize-observer/#intro
1073
- (ro ||
1074
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1491739
1075
- (ro = new (getCurrentWindow(getCurrentDocument(e)).ResizeObserver)(cb))).observe(e);
1076
- },
1077
- _unobserve(e) {
1078
- ro.unobserve(e);
1079
- },
1080
- _dispose() {
1081
- ro && ro.disconnect();
1082
- },
1083
- };
765
+ //#endregion
766
+ //#region src/core/resizer.ts
767
+ var createResizeObserver = (cb) => {
768
+ let ro;
769
+ return {
770
+ _observe(e) {
771
+ (ro || (ro = new (getCurrentWindow(getCurrentDocument(e))).ResizeObserver(cb))).observe(e);
772
+ },
773
+ _unobserve(e) {
774
+ ro.unobserve(e);
775
+ },
776
+ _dispose() {
777
+ ro && ro.disconnect();
778
+ }
779
+ };
1084
780
  };
1085
- /**
1086
- * @internal
1087
- */
1088
- const createResizer = (store, isHorizontal) => {
1089
- let viewportElement;
1090
- const sizeKey = isHorizontal ? "width" : "height";
1091
- const mountedIndexes = new WeakMap();
1092
- const resizeObserver = createResizeObserver((entries) => {
1093
- const resizes = [];
1094
- for (const { target, contentRect } of entries) {
1095
- // Skip zero-sized rects that may be observed under `display: none` style
1096
- if (!target.offsetParent)
1097
- continue;
1098
- if (target === viewportElement) {
1099
- store.$update(ACTION_VIEWPORT_RESIZE, contentRect[sizeKey]);
1100
- }
1101
- else {
1102
- const index = mountedIndexes.get(target);
1103
- if (index != NULL) {
1104
- resizes.push([index, contentRect[sizeKey]]);
1105
- }
1106
- }
1107
- }
1108
- if (resizes.length) {
1109
- store.$update(ACTION_ITEM_RESIZE, resizes);
1110
- }
1111
- });
1112
- return {
1113
- $observeRoot(viewport) {
1114
- resizeObserver._observe((viewportElement = viewport));
1115
- },
1116
- $observeItem: (el, i) => {
1117
- mountedIndexes.set(el, i);
1118
- resizeObserver._observe(el);
1119
- return () => {
1120
- mountedIndexes.delete(el);
1121
- resizeObserver._unobserve(el);
1122
- };
1123
- },
1124
- $dispose: resizeObserver._dispose,
1125
- };
781
+ var createResizer = (store, isHorizontal) => {
782
+ let viewportElement;
783
+ const sizeKey = isHorizontal ? "width" : "height";
784
+ const mountedIndexes = new WeakMap();
785
+ const resizeObserver = createResizeObserver((entries) => {
786
+ const resizes = [];
787
+ for (const { target, contentRect } of entries) {
788
+ if (!target.offsetParent) continue;
789
+ if (target === viewportElement) {
790
+ store.$update(4, contentRect[sizeKey]);
791
+ } else {
792
+ const index = mountedIndexes.get(target);
793
+ if (index != null) {
794
+ resizes.push([index, contentRect[sizeKey]]);
795
+ }
796
+ }
797
+ }
798
+ if (resizes.length) {
799
+ store.$update(3, resizes);
800
+ }
801
+ });
802
+ return {
803
+ $observeRoot(viewport) {
804
+ resizeObserver._observe(viewportElement = viewport);
805
+ },
806
+ $observeItem: (el, i) => {
807
+ mountedIndexes.set(el, i);
808
+ resizeObserver._observe(el);
809
+ return () => {
810
+ mountedIndexes.delete(el);
811
+ resizeObserver._unobserve(el);
812
+ };
813
+ },
814
+ $dispose: resizeObserver._dispose
815
+ };
1126
816
  };
1127
- /**
1128
- * @internal
1129
- */
1130
- const createWindowResizer = (store, isHorizontal) => {
1131
- const sizeKey = isHorizontal ? "width" : "height";
1132
- const windowSizeKey = isHorizontal ? "innerWidth" : "innerHeight";
1133
- const mountedIndexes = new WeakMap();
1134
- const resizeObserver = createResizeObserver((entries) => {
1135
- const resizes = [];
1136
- for (const { target, contentRect } of entries) {
1137
- // Skip zero-sized rects that may be observed under `display: none` style
1138
- if (!target.offsetParent)
1139
- continue;
1140
- const index = mountedIndexes.get(target);
1141
- if (index != NULL) {
1142
- resizes.push([index, contentRect[sizeKey]]);
1143
- }
1144
- }
1145
- if (resizes.length) {
1146
- store.$update(ACTION_ITEM_RESIZE, resizes);
1147
- }
1148
- });
1149
- let cleanupOnWindowResize;
1150
- return {
1151
- $observeRoot(container) {
1152
- const window = getCurrentWindow(getCurrentDocument(container));
1153
- const onWindowResize = () => {
1154
- store.$update(ACTION_VIEWPORT_RESIZE, window[windowSizeKey]);
1155
- };
1156
- window.addEventListener("resize", onWindowResize);
1157
- // https://github.com/inokawa/virtua/issues/792
1158
- microtask(onWindowResize);
1159
- cleanupOnWindowResize = () => {
1160
- window.removeEventListener("resize", onWindowResize);
1161
- };
1162
- },
1163
- $observeItem: (el, i) => {
1164
- mountedIndexes.set(el, i);
1165
- resizeObserver._observe(el);
1166
- return () => {
1167
- mountedIndexes.delete(el);
1168
- resizeObserver._unobserve(el);
1169
- };
1170
- },
1171
- $dispose() {
1172
- cleanupOnWindowResize && cleanupOnWindowResize();
1173
- resizeObserver._dispose();
1174
- },
1175
- };
817
+ var createWindowResizer = (store, isHorizontal) => {
818
+ const sizeKey = isHorizontal ? "width" : "height";
819
+ const windowSizeKey = isHorizontal ? "innerWidth" : "innerHeight";
820
+ const mountedIndexes = new WeakMap();
821
+ const resizeObserver = createResizeObserver((entries) => {
822
+ const resizes = [];
823
+ for (const { target, contentRect } of entries) {
824
+ if (!target.offsetParent) continue;
825
+ const index = mountedIndexes.get(target);
826
+ if (index != null) {
827
+ resizes.push([index, contentRect[sizeKey]]);
828
+ }
829
+ }
830
+ if (resizes.length) {
831
+ store.$update(3, resizes);
832
+ }
833
+ });
834
+ let cleanupOnWindowResize;
835
+ return {
836
+ $observeRoot(container) {
837
+ const window = getCurrentWindow(getCurrentDocument(container));
838
+ const onWindowResize = () => {
839
+ store.$update(4, window[windowSizeKey]);
840
+ };
841
+ window.addEventListener("resize", onWindowResize);
842
+ microtask(onWindowResize);
843
+ cleanupOnWindowResize = () => {
844
+ window.removeEventListener("resize", onWindowResize);
845
+ };
846
+ },
847
+ $observeItem: (el, i) => {
848
+ mountedIndexes.set(el, i);
849
+ resizeObserver._observe(el);
850
+ return () => {
851
+ mountedIndexes.delete(el);
852
+ resizeObserver._unobserve(el);
853
+ };
854
+ },
855
+ $dispose() {
856
+ cleanupOnWindowResize && cleanupOnWindowResize();
857
+ resizeObserver._dispose();
858
+ }
859
+ };
1176
860
  };
1177
- /**
1178
- * @internal
1179
- */
1180
- const createGridResizer = (rowStore, colStore) => {
1181
- let viewportElement;
1182
- const mountedIndexes = new WeakMap();
1183
- const maybeCachedRowIndexes = new Set();
1184
- const maybeCachedColIndexes = new Set();
1185
- const sizeCache = new Map();
1186
- const getKey = (rowIndex, colIndex) => `${rowIndex}-${colIndex}`;
1187
- const resizeObserver = createResizeObserver((entries) => {
1188
- const resizedRows = new Set();
1189
- const resizedCols = new Set();
1190
- for (const { target, contentRect: { width, height }, } of entries) {
1191
- // Skip zero-sized rects that may be observed under `display: none` style
1192
- if (!target.offsetParent)
1193
- continue;
1194
- if (target === viewportElement) {
1195
- rowStore.$update(ACTION_VIEWPORT_RESIZE, height);
1196
- colStore.$update(ACTION_VIEWPORT_RESIZE, width);
1197
- }
1198
- else {
1199
- const cell = mountedIndexes.get(target);
1200
- if (cell) {
1201
- const [rowIndex, colIndex] = cell;
1202
- const key = getKey(rowIndex, colIndex);
1203
- const prevSize = sizeCache.get(key);
1204
- let rowResized;
1205
- let colResized;
1206
- if (!prevSize) {
1207
- rowResized = colResized = true;
1208
- }
1209
- else {
1210
- if (prevSize[0] !== height) {
1211
- rowResized = true;
1212
- }
1213
- if (prevSize[1] !== width) {
1214
- colResized = true;
1215
- }
1216
- }
1217
- if (rowResized) {
1218
- resizedRows.add(rowIndex);
1219
- }
1220
- if (colResized) {
1221
- resizedCols.add(colIndex);
1222
- }
1223
- if (rowResized || colResized) {
1224
- sizeCache.set(key, [height, width]);
1225
- }
1226
- }
1227
- }
1228
- }
1229
- if (resizedRows.size) {
1230
- const heightResizes = [];
1231
- resizedRows.forEach((rowIndex) => {
1232
- let maxHeight = 0;
1233
- maybeCachedColIndexes.forEach((colIndex) => {
1234
- const size = sizeCache.get(getKey(rowIndex, colIndex));
1235
- if (size) {
1236
- maxHeight = max(maxHeight, size[0]);
1237
- }
1238
- });
1239
- if (maxHeight) {
1240
- heightResizes.push([rowIndex, maxHeight]);
1241
- }
1242
- });
1243
- rowStore.$update(ACTION_ITEM_RESIZE, heightResizes);
1244
- }
1245
- if (resizedCols.size) {
1246
- const widthResizes = [];
1247
- resizedCols.forEach((colIndex) => {
1248
- let maxWidth = 0;
1249
- maybeCachedRowIndexes.forEach((rowIndex) => {
1250
- const size = sizeCache.get(getKey(rowIndex, colIndex));
1251
- if (size) {
1252
- maxWidth = max(maxWidth, size[1]);
1253
- }
1254
- });
1255
- if (maxWidth) {
1256
- widthResizes.push([colIndex, maxWidth]);
1257
- }
1258
- });
1259
- colStore.$update(ACTION_ITEM_RESIZE, widthResizes);
1260
- }
1261
- });
1262
- return {
1263
- $observeRoot(viewport) {
1264
- resizeObserver._observe((viewportElement = viewport));
1265
- },
1266
- $observeItem(el, rowIndex, colIndex) {
1267
- mountedIndexes.set(el, [rowIndex, colIndex]);
1268
- maybeCachedRowIndexes.add(rowIndex);
1269
- maybeCachedColIndexes.add(colIndex);
1270
- resizeObserver._observe(el);
1271
- return () => {
1272
- mountedIndexes.delete(el);
1273
- resizeObserver._unobserve(el);
1274
- };
1275
- },
1276
- $resizeCols(cols) {
1277
- for (const [c] of cols) {
1278
- for (let r = 0; r < rowStore.$getItemsLength(); r++) {
1279
- sizeCache.delete(getKey(r, c));
1280
- }
1281
- }
1282
- colStore.$update(ACTION_ITEM_RESIZE, cols);
1283
- },
1284
- $resizeRows(rows) {
1285
- for (const [r] of rows) {
1286
- for (let c = 0; c < colStore.$getItemsLength(); c++) {
1287
- sizeCache.delete(getKey(r, c));
1288
- }
1289
- }
1290
- rowStore.$update(ACTION_ITEM_RESIZE, rows);
1291
- },
1292
- $dispose: resizeObserver._dispose,
1293
- };
861
+ var createGridResizer = (rowStore, colStore) => {
862
+ let viewportElement;
863
+ const mountedIndexes = new WeakMap();
864
+ const maybeCachedRowIndexes = new Set();
865
+ const maybeCachedColIndexes = new Set();
866
+ const sizeCache = new Map();
867
+ const getKey = (rowIndex, colIndex) => `${rowIndex}-${colIndex}`;
868
+ const resizeObserver = createResizeObserver((entries) => {
869
+ const resizedRows = new Set();
870
+ const resizedCols = new Set();
871
+ for (const { target, contentRect: { width, height } } of entries) {
872
+ if (!target.offsetParent) continue;
873
+ if (target === viewportElement) {
874
+ rowStore.$update(4, height);
875
+ colStore.$update(4, width);
876
+ } else {
877
+ const cell = mountedIndexes.get(target);
878
+ if (cell) {
879
+ const [rowIndex, colIndex] = cell;
880
+ const key = getKey(rowIndex, colIndex);
881
+ const prevSize = sizeCache.get(key);
882
+ let rowResized;
883
+ let colResized;
884
+ if (!prevSize) {
885
+ rowResized = colResized = true;
886
+ } else {
887
+ if (prevSize[0] !== height) {
888
+ rowResized = true;
889
+ }
890
+ if (prevSize[1] !== width) {
891
+ colResized = true;
892
+ }
893
+ }
894
+ if (rowResized) {
895
+ resizedRows.add(rowIndex);
896
+ }
897
+ if (colResized) {
898
+ resizedCols.add(colIndex);
899
+ }
900
+ if (rowResized || colResized) {
901
+ sizeCache.set(key, [height, width]);
902
+ }
903
+ }
904
+ }
905
+ }
906
+ if (resizedRows.size) {
907
+ const heightResizes = [];
908
+ resizedRows.forEach((rowIndex) => {
909
+ let maxHeight = 0;
910
+ maybeCachedColIndexes.forEach((colIndex) => {
911
+ const size = sizeCache.get(getKey(rowIndex, colIndex));
912
+ if (size) {
913
+ maxHeight = max(maxHeight, size[0]);
914
+ }
915
+ });
916
+ if (maxHeight) {
917
+ heightResizes.push([rowIndex, maxHeight]);
918
+ }
919
+ });
920
+ rowStore.$update(3, heightResizes);
921
+ }
922
+ if (resizedCols.size) {
923
+ const widthResizes = [];
924
+ resizedCols.forEach((colIndex) => {
925
+ let maxWidth = 0;
926
+ maybeCachedRowIndexes.forEach((rowIndex) => {
927
+ const size = sizeCache.get(getKey(rowIndex, colIndex));
928
+ if (size) {
929
+ maxWidth = max(maxWidth, size[1]);
930
+ }
931
+ });
932
+ if (maxWidth) {
933
+ widthResizes.push([colIndex, maxWidth]);
934
+ }
935
+ });
936
+ colStore.$update(3, widthResizes);
937
+ }
938
+ });
939
+ return {
940
+ $observeRoot(viewport) {
941
+ resizeObserver._observe(viewportElement = viewport);
942
+ },
943
+ $observeItem(el, rowIndex, colIndex) {
944
+ mountedIndexes.set(el, [rowIndex, colIndex]);
945
+ maybeCachedRowIndexes.add(rowIndex);
946
+ maybeCachedColIndexes.add(colIndex);
947
+ resizeObserver._observe(el);
948
+ return () => {
949
+ mountedIndexes.delete(el);
950
+ resizeObserver._unobserve(el);
951
+ };
952
+ },
953
+ $resizeCols(cols) {
954
+ for (const [c] of cols) {
955
+ for (let r = 0; r < rowStore.$getItemsLength(); r++) {
956
+ sizeCache.delete(getKey(r, c));
957
+ }
958
+ }
959
+ colStore.$update(3, cols);
960
+ },
961
+ $resizeRows(rows) {
962
+ for (const [r] of rows) {
963
+ for (let c = 0; c < colStore.$getItemsLength(); c++) {
964
+ sizeCache.delete(getKey(r, c));
965
+ }
966
+ }
967
+ rowStore.$update(3, rows);
968
+ },
969
+ $dispose: resizeObserver._dispose
970
+ };
1294
971
  };
1295
972
 
1296
- /**
1297
- * @internal
1298
- */
1299
- const ListItem = (props) => {
1300
- let elementRef;
1301
- props = mergeProps({ _as: "div" }, props);
1302
- // The index may be changed if elements are inserted to or removed from the start of props.children
1303
- createEffect(() => {
1304
- if (!elementRef)
1305
- return;
1306
- onCleanup(props._resizer(elementRef, props._index));
1307
- });
1308
- const style = createMemo(() => {
1309
- const isHorizontal = props._isHorizontal;
1310
- const style = {
1311
- contain: "layout style",
1312
- position: "absolute",
1313
- [isHorizontal ? "height" : "width"]: "100%",
1314
- [isHorizontal ? "top" : "left"]: "0px",
1315
- [isHorizontal ? "left" : "top"]: props._offset + "px",
1316
- visibility: props._hide ? "hidden" : undefined,
1317
- };
1318
- if (isHorizontal) {
1319
- style.display = "inline-flex";
1320
- }
1321
- return style;
1322
- });
1323
- return (<Dynamic component={props._as} index={props._index} ref={elementRef} style={style()}>
973
+ //#endregion
974
+ //#region src/solid/ListItem.tsx
975
+ var ListItem = (props) => {
976
+ let elementRef;
977
+ props = mergeProps({ _as: "div" }, props);
978
+ createEffect(() => {
979
+ if (!elementRef) return;
980
+ onCleanup(props._resizer(elementRef, props._index));
981
+ });
982
+ const style = createMemo(() => {
983
+ const isHorizontal = props._isHorizontal;
984
+ const style = {
985
+ contain: "layout style",
986
+ position: "absolute",
987
+ [isHorizontal ? "height" : "width"]: "100%",
988
+ [isHorizontal ? "top" : "left"]: "0px",
989
+ [isHorizontal ? "left" : "top"]: props._offset + "px",
990
+ visibility: props._hide ? "hidden" : undefined
991
+ };
992
+ if (isHorizontal) {
993
+ style.display = "inline-flex";
994
+ }
995
+ return style;
996
+ });
997
+ return <Dynamic component={props._as} index={props._index} ref={elementRef} style={style()}>
1324
998
  {props._children}
1325
- </Dynamic>);
999
+ </Dynamic>;
1326
1000
  };
1327
1001
 
1328
- /**
1329
- * @internal
1330
- */
1331
- const isSameRange = (prev, next) => {
1332
- return prev[0] === next[0] && prev[1] === next[1];
1002
+ //#endregion
1003
+ //#region src/solid/utils.ts
1004
+ var isSameRange = (prev, next) => {
1005
+ return prev[0] === next[0] && prev[1] === next[1];
1333
1006
  };
1334
1007
 
1335
- /**
1336
- * @jsxImportSource solid-js
1337
- */
1338
- /**
1339
- * Customizable list virtualizer for advanced usage. See {@link VirtualizerProps} and {@link VirtualizerHandle}.
1340
- */
1341
- const Virtualizer = (props) => {
1342
- let containerRef;
1343
- const { itemSize, horizontal = false, cache } = props;
1344
- props = mergeProps({ as: "div" }, props);
1345
- const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1346
- const resizer = createResizer(store, horizontal);
1347
- const scroller = createScroller(store, horizontal);
1348
- const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
1349
- store.$subscribe(UPDATE_VIRTUAL_STATE, () => {
1350
- setRerender(store.$getStateVersion());
1351
- });
1352
- store.$subscribe(UPDATE_SCROLL_EVENT, () => {
1353
- props.onScroll?.(store.$getScrollOffset());
1354
- });
1355
- store.$subscribe(UPDATE_SCROLL_END_EVENT, () => {
1356
- props.onScrollEnd?.();
1357
- });
1358
- const range = createMemo((prev) => {
1359
- stateVersion();
1360
- const next = store.$getRange(props.bufferSize);
1361
- if (prev && isSameRange(prev, next)) {
1362
- return prev;
1363
- }
1364
- return next;
1365
- });
1366
- const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1367
- const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1368
- const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1369
- onMount(() => {
1370
- if (props.ref) {
1371
- props.ref({
1372
- get cache() {
1373
- return store.$getCacheSnapshot();
1374
- },
1375
- get scrollOffset() {
1376
- return store.$getScrollOffset();
1377
- },
1378
- get scrollSize() {
1379
- return getScrollSize(store);
1380
- },
1381
- get viewportSize() {
1382
- return store.$getViewportSize();
1383
- },
1384
- findItemIndex: store.$findItemIndex,
1385
- getItemOffset: store.$getItemOffset,
1386
- getItemSize: store.$getItemSize,
1387
- scrollToIndex: scroller.$scrollToIndex,
1388
- scrollTo: scroller.$scrollTo,
1389
- scrollBy: scroller.$scrollBy,
1390
- });
1391
- }
1392
- const container = containerRef;
1393
- const scrollable = props.scrollRef || container.parentElement;
1394
- resizer.$observeRoot(scrollable);
1395
- scroller.$observe(container, scrollable);
1396
- onCleanup(() => {
1397
- if (props.ref) {
1398
- props.ref();
1399
- }
1400
- store.$dispose();
1401
- resizer.$dispose();
1402
- scroller.$dispose();
1403
- });
1404
- });
1405
- createComputed(on(() => props.startMargin || 0, (value) => {
1406
- if (value !== store.$getStartSpacerSize()) {
1407
- store.$update(ACTION_START_OFFSET_CHANGE, value);
1408
- }
1409
- }));
1410
- createEffect(on(stateVersion, () => {
1411
- scroller.$fixScrollJump();
1412
- }));
1413
- const dataSlice = createMemo(() => {
1414
- const count = props.data.length;
1415
- untrack(() => {
1416
- if (count !== store.$getItemsLength()) {
1417
- store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1418
- }
1419
- });
1420
- const items = [];
1421
- const indexes = [];
1422
- if (props.keepMounted) {
1423
- const mounted = new Set(props.keepMounted);
1424
- for (let [i, j] = range(); i <= j; i++) {
1425
- mounted.add(i);
1426
- }
1427
- sort([...mounted]).forEach((index) => {
1428
- items.push(props.data[index]);
1429
- indexes.push(index);
1430
- });
1431
- }
1432
- else {
1433
- for (let [i, j] = range(); i <= j; i++) {
1434
- items.push(props.data[i]);
1435
- indexes.push(i);
1436
- }
1437
- }
1438
- return { _items: items, _indexes: indexes };
1439
- });
1440
- const renderItem = (data, index) => {
1441
- const offset = createMemo(() => {
1442
- stateVersion();
1443
- return store.$getItemOffset(index(), isNegative());
1444
- });
1445
- const hide = createMemo(() => {
1446
- stateVersion();
1447
- return store.$isUnmeasuredItem(index());
1448
- });
1449
- const children = createMemo(() => {
1450
- return untrack(() => props.children(data, index));
1451
- });
1452
- return (<ListItem _as={props.item} _index={index()} _resizer={resizer.$observeItem} _offset={offset()} _hide={hide()} _children={children()} _isHorizontal={horizontal}/>);
1453
- };
1454
- return (<Dynamic component={props.as} ref={containerRef} style={{
1455
- contain: "size style", // https://github.com/inokawa/virtua/pull/775 https://github.com/inokawa/virtua/issues/800
1456
- "overflow-anchor": "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
1457
- flex: "none", // flex style can break layout
1458
- position: "relative",
1459
- width: horizontal ? totalSize() + "px" : "100%",
1460
- height: horizontal ? "100%" : totalSize() + "px",
1461
- "pointer-events": isScrolling() ? "none" : undefined,
1462
- }}>
1008
+ //#endregion
1009
+ //#region src/solid/Virtualizer.tsx
1010
+ var Virtualizer = (props) => {
1011
+ let containerRef;
1012
+ const { itemSize, horizontal = false, cache } = props;
1013
+ props = mergeProps({ as: "div" }, props);
1014
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1015
+ const resizer = createResizer(store, horizontal);
1016
+ const scroller = createScroller(store, horizontal);
1017
+ const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
1018
+ store.$subscribe(1, () => {
1019
+ setRerender(store.$getStateVersion());
1020
+ });
1021
+ store.$subscribe(4, () => {
1022
+ props.onScroll?.(store.$getScrollOffset());
1023
+ });
1024
+ store.$subscribe(8, () => {
1025
+ props.onScrollEnd?.();
1026
+ });
1027
+ const range = createMemo((prev) => {
1028
+ stateVersion();
1029
+ const next = store.$getRange(props.bufferSize);
1030
+ if (prev && isSameRange(prev, next)) {
1031
+ return prev;
1032
+ }
1033
+ return next;
1034
+ });
1035
+ const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1036
+ const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1037
+ const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1038
+ onMount(() => {
1039
+ if (props.ref) {
1040
+ props.ref({
1041
+ get cache() {
1042
+ return store.$getCacheSnapshot();
1043
+ },
1044
+ get scrollOffset() {
1045
+ return store.$getScrollOffset();
1046
+ },
1047
+ get scrollSize() {
1048
+ return getScrollSize(store);
1049
+ },
1050
+ get viewportSize() {
1051
+ return store.$getViewportSize();
1052
+ },
1053
+ findItemIndex: store.$findItemIndex,
1054
+ getItemOffset: store.$getItemOffset,
1055
+ getItemSize: store.$getItemSize,
1056
+ scrollToIndex: scroller.$scrollToIndex,
1057
+ scrollTo: scroller.$scrollTo,
1058
+ scrollBy: scroller.$scrollBy
1059
+ });
1060
+ }
1061
+ const container = containerRef;
1062
+ const scrollable = props.scrollRef || container.parentElement;
1063
+ resizer.$observeRoot(scrollable);
1064
+ scroller.$observe(container, scrollable);
1065
+ onCleanup(() => {
1066
+ if (props.ref) {
1067
+ props.ref();
1068
+ }
1069
+ store.$dispose();
1070
+ resizer.$dispose();
1071
+ scroller.$dispose();
1072
+ });
1073
+ });
1074
+ createComputed(on(() => props.startMargin || 0, (value) => {
1075
+ if (value !== store.$getStartSpacerSize()) {
1076
+ store.$update(6, value);
1077
+ }
1078
+ }));
1079
+ createEffect(on(stateVersion, () => {
1080
+ scroller.$fixScrollJump();
1081
+ }));
1082
+ const dataSlice = createMemo(() => {
1083
+ const count = props.data.length;
1084
+ untrack(() => {
1085
+ if (count !== store.$getItemsLength()) {
1086
+ store.$update(5, [count, props.shift]);
1087
+ }
1088
+ });
1089
+ const items = [];
1090
+ const indexes = [];
1091
+ if (props.keepMounted) {
1092
+ const mounted = new Set(props.keepMounted);
1093
+ for (let [i, j] = range(); i <= j; i++) {
1094
+ mounted.add(i);
1095
+ }
1096
+ sort([...mounted]).forEach((index) => {
1097
+ items.push(props.data[index]);
1098
+ indexes.push(index);
1099
+ });
1100
+ } else {
1101
+ for (let [i, j] = range(); i <= j; i++) {
1102
+ items.push(props.data[i]);
1103
+ indexes.push(i);
1104
+ }
1105
+ }
1106
+ return {
1107
+ _items: items,
1108
+ _indexes: indexes
1109
+ };
1110
+ });
1111
+ const renderItem = (data, index) => {
1112
+ const offset = createMemo(() => {
1113
+ stateVersion();
1114
+ return store.$getItemOffset(index(), isNegative());
1115
+ });
1116
+ const hide = createMemo(() => {
1117
+ stateVersion();
1118
+ return store.$isUnmeasuredItem(index());
1119
+ });
1120
+ const children = createMemo(() => {
1121
+ return untrack(() => props.children(data, index));
1122
+ });
1123
+ return <ListItem _as={props.item} _index={index()} _resizer={resizer.$observeItem} _offset={offset()} _hide={hide()} _children={children()} _isHorizontal={horizontal} />;
1124
+ };
1125
+ return <Dynamic component={props.as} ref={containerRef} style={{
1126
+ contain: "size style",
1127
+ "overflow-anchor": "none",
1128
+ flex: "none",
1129
+ position: "relative",
1130
+ width: horizontal ? totalSize() + "px" : "100%",
1131
+ height: horizontal ? "100%" : totalSize() + "px",
1132
+ "pointer-events": isScrolling() ? "none" : undefined
1133
+ }}>
1463
1134
  <For each={dataSlice()._items}>
1464
1135
  {(data, index) => {
1465
- const itemIndex = createMemo(() => dataSlice()._indexes[index()]);
1466
- // eslint-disable-next-line solid/reactivity
1467
- return renderItem(data, itemIndex);
1468
- }}
1136
+ const itemIndex = createMemo(() => dataSlice()._indexes[index()]);
1137
+ return renderItem(data, itemIndex);
1138
+ }}
1469
1139
  </For>
1470
- </Dynamic>);
1140
+ </Dynamic>;
1471
1141
  };
1472
1142
 
1473
- /**
1474
- * @jsxImportSource solid-js
1475
- */
1476
- /**
1477
- * Virtualized list component. See {@link VListProps} and {@link VListHandle}.
1478
- */
1479
- const VList = (props) => {
1480
- const [local, others] = splitProps(props, [
1481
- "ref",
1482
- "data",
1483
- "children",
1484
- "bufferSize",
1485
- "itemSize",
1486
- "shift",
1487
- "horizontal",
1488
- "keepMounted",
1489
- "cache",
1490
- "item",
1491
- "onScroll",
1492
- "onScrollEnd",
1493
- "style",
1494
- ]);
1495
- return (<div {...others} style={{
1496
- display: local.horizontal ? "inline-block" : "block",
1497
- [local.horizontal ? "overflow-x" : "overflow-y"]: "auto",
1498
- contain: "strict",
1499
- width: "100%",
1500
- height: "100%",
1501
- ...local.style,
1502
- }}>
1143
+ //#endregion
1144
+ //#region src/solid/VList.tsx
1145
+ var VList = (props) => {
1146
+ const [local, others] = splitProps(props, [
1147
+ "ref",
1148
+ "data",
1149
+ "children",
1150
+ "bufferSize",
1151
+ "itemSize",
1152
+ "shift",
1153
+ "horizontal",
1154
+ "keepMounted",
1155
+ "cache",
1156
+ "item",
1157
+ "onScroll",
1158
+ "onScrollEnd",
1159
+ "style"
1160
+ ]);
1161
+ return <div {...others} style={{
1162
+ display: local.horizontal ? "inline-block" : "block",
1163
+ [local.horizontal ? "overflow-x" : "overflow-y"]: "auto",
1164
+ contain: "strict",
1165
+ width: "100%",
1166
+ height: "100%",
1167
+ ...local.style
1168
+ }}>
1503
1169
  <Virtualizer ref={local.ref} data={local.data} bufferSize={local.bufferSize} itemSize={local.itemSize} shift={local.shift} horizontal={local.horizontal} keepMounted={local.keepMounted} cache={local.cache} item={local.item} onScroll={local.onScroll} onScrollEnd={local.onScrollEnd}>
1504
1170
  {local.children}
1505
1171
  </Virtualizer>
1506
- </div>);
1172
+ </div>;
1507
1173
  };
1508
1174
 
1509
- /**
1510
- * @jsxImportSource solid-js
1511
- */
1512
- /**
1513
- * {@link Virtualizer} controlled by the window scrolling. See {@link WindowVirtualizerProps} and {@link WindowVirtualizerHandle}.
1514
- */
1515
- const WindowVirtualizer = (props) => {
1516
- let containerRef;
1517
- const { ref: _ref, data: _data, children: _children, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd, } = props;
1518
- const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1519
- const resizer = createWindowResizer(store, horizontal);
1520
- const scroller = createWindowScroller(store, horizontal);
1521
- const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
1522
- store.$subscribe(UPDATE_VIRTUAL_STATE, () => {
1523
- setRerender(store.$getStateVersion());
1524
- });
1525
- store.$subscribe(UPDATE_SCROLL_EVENT, () => {
1526
- // https://github.com/inokawa/virtua/discussions/580
1527
- props.onScroll?.();
1528
- });
1529
- store.$subscribe(UPDATE_SCROLL_END_EVENT, () => {
1530
- props.onScrollEnd?.();
1531
- });
1532
- const range = createMemo((prev) => {
1533
- stateVersion();
1534
- const next = store.$getRange(props.bufferSize);
1535
- if (prev && isSameRange(prev, next)) {
1536
- return prev;
1537
- }
1538
- return next;
1539
- });
1540
- const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1541
- const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1542
- const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1543
- onMount(() => {
1544
- if (props.ref) {
1545
- props.ref({
1546
- get cache() {
1547
- return store.$getCacheSnapshot();
1548
- },
1549
- get scrollOffset() {
1550
- return store.$getScrollOffset();
1551
- },
1552
- get viewportSize() {
1553
- return store.$getViewportSize();
1554
- },
1555
- findItemIndex: store.$findItemIndex,
1556
- getItemOffset: store.$getItemOffset,
1557
- getItemSize: store.$getItemSize,
1558
- scrollToIndex: scroller.$scrollToIndex,
1559
- });
1560
- }
1561
- resizer.$observeRoot(containerRef);
1562
- scroller.$observe(containerRef);
1563
- onCleanup(() => {
1564
- if (props.ref) {
1565
- props.ref();
1566
- }
1567
- store.$dispose();
1568
- resizer.$dispose();
1569
- scroller.$dispose();
1570
- });
1571
- });
1572
- createEffect(on(stateVersion, () => {
1573
- scroller.$fixScrollJump();
1574
- }));
1575
- const dataSlice = createMemo(() => {
1576
- const count = props.data.length;
1577
- untrack(() => {
1578
- if (count !== store.$getItemsLength()) {
1579
- store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1580
- }
1581
- });
1582
- const items = [];
1583
- for (let [i, j] = range(); i <= j; i++) {
1584
- items.push(props.data[i]);
1585
- }
1586
- return items;
1587
- });
1588
- return (<div ref={containerRef} style={{
1589
- contain: "size style", // https://github.com/inokawa/virtua/pull/775 https://github.com/inokawa/virtua/issues/800
1590
- "overflow-anchor": "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
1591
- flex: "none", // flex style can break layout
1592
- position: "relative",
1593
- width: horizontal ? totalSize() + "px" : "100%",
1594
- height: horizontal ? "100%" : totalSize() + "px",
1595
- "pointer-events": isScrolling() ? "none" : undefined,
1596
- }}>
1175
+ //#endregion
1176
+ //#region src/solid/WindowVirtualizer.tsx
1177
+ var WindowVirtualizer = (props) => {
1178
+ let containerRef;
1179
+ const { ref: _ref, data: _data, children: _children, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd } = props;
1180
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1181
+ const resizer = createWindowResizer(store, horizontal);
1182
+ const scroller = createWindowScroller(store, horizontal);
1183
+ const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
1184
+ store.$subscribe(1, () => {
1185
+ setRerender(store.$getStateVersion());
1186
+ });
1187
+ store.$subscribe(4, () => {
1188
+ props.onScroll?.();
1189
+ });
1190
+ store.$subscribe(8, () => {
1191
+ props.onScrollEnd?.();
1192
+ });
1193
+ const range = createMemo((prev) => {
1194
+ stateVersion();
1195
+ const next = store.$getRange(props.bufferSize);
1196
+ if (prev && isSameRange(prev, next)) {
1197
+ return prev;
1198
+ }
1199
+ return next;
1200
+ });
1201
+ const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1202
+ const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1203
+ const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1204
+ onMount(() => {
1205
+ if (props.ref) {
1206
+ props.ref({
1207
+ get cache() {
1208
+ return store.$getCacheSnapshot();
1209
+ },
1210
+ get scrollOffset() {
1211
+ return store.$getScrollOffset();
1212
+ },
1213
+ get viewportSize() {
1214
+ return store.$getViewportSize();
1215
+ },
1216
+ findItemIndex: store.$findItemIndex,
1217
+ getItemOffset: store.$getItemOffset,
1218
+ getItemSize: store.$getItemSize,
1219
+ scrollToIndex: scroller.$scrollToIndex
1220
+ });
1221
+ }
1222
+ resizer.$observeRoot(containerRef);
1223
+ scroller.$observe(containerRef);
1224
+ onCleanup(() => {
1225
+ if (props.ref) {
1226
+ props.ref();
1227
+ }
1228
+ store.$dispose();
1229
+ resizer.$dispose();
1230
+ scroller.$dispose();
1231
+ });
1232
+ });
1233
+ createEffect(on(stateVersion, () => {
1234
+ scroller.$fixScrollJump();
1235
+ }));
1236
+ const dataSlice = createMemo(() => {
1237
+ const count = props.data.length;
1238
+ untrack(() => {
1239
+ if (count !== store.$getItemsLength()) {
1240
+ store.$update(5, [count, props.shift]);
1241
+ }
1242
+ });
1243
+ const items = [];
1244
+ for (let [i, j] = range(); i <= j; i++) {
1245
+ items.push(props.data[i]);
1246
+ }
1247
+ return items;
1248
+ });
1249
+ return <div ref={containerRef} style={{
1250
+ contain: "size style",
1251
+ "overflow-anchor": "none",
1252
+ flex: "none",
1253
+ position: "relative",
1254
+ width: horizontal ? totalSize() + "px" : "100%",
1255
+ height: horizontal ? "100%" : totalSize() + "px",
1256
+ "pointer-events": isScrolling() ? "none" : undefined
1257
+ }}>
1597
1258
  <For each={dataSlice()}>
1598
1259
  {(data, index) => {
1599
- const itemIndex = createMemo(() => range()[0] + index());
1600
- const offset = createMemo(() => {
1601
- stateVersion();
1602
- return store.$getItemOffset(itemIndex(), isNegative());
1603
- });
1604
- const hide = createMemo(() => {
1605
- stateVersion();
1606
- return store.$isUnmeasuredItem(itemIndex());
1607
- });
1608
- const children = createMemo(() => {
1609
- return untrack(() => props.children(data, itemIndex));
1610
- });
1611
- return (<ListItem _index={itemIndex()} _resizer={resizer.$observeItem} _offset={offset()} _hide={hide()} _children={children()} _isHorizontal={horizontal}/>);
1612
- }}
1260
+ const itemIndex = createMemo(() => range()[0] + index());
1261
+ const offset = createMemo(() => {
1262
+ stateVersion();
1263
+ return store.$getItemOffset(itemIndex(), isNegative());
1264
+ });
1265
+ const hide = createMemo(() => {
1266
+ stateVersion();
1267
+ return store.$isUnmeasuredItem(itemIndex());
1268
+ });
1269
+ const children = createMemo(() => {
1270
+ return untrack(() => props.children(data, itemIndex));
1271
+ });
1272
+ return <ListItem _index={itemIndex()} _resizer={resizer.$observeItem} _offset={offset()} _hide={hide()} _children={children()} _isHorizontal={horizontal} />;
1273
+ }}
1613
1274
  </For>
1614
- </div>);
1275
+ </div>;
1615
1276
  };
1616
1277
 
1617
- /**
1618
- * @module solid
1619
- */
1620
-
1278
+ //#endregion
1621
1279
  export { VList, Virtualizer, WindowVirtualizer };
1622
- //# sourceMappingURL=index.jsx.map
1280
+ //# sourceMappingURL=index.jsx.map