virtua 0.45.2 → 0.46.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.
@@ -132,20 +132,20 @@ const findIndex = (cache, offset, low = 0, high = cache._length - 1) => {
132
132
  /**
133
133
  * @internal
134
134
  */
135
- const computeRange = (cache, scrollOffset, viewportSize, prevStartIndex) => {
135
+ const computeRange = (cache, startOffset, endOffset, prevStartIndex) => {
136
136
  // Clamp because prevStartIndex may exceed the limit when children decreased a lot after scrolling
137
137
  prevStartIndex = min(prevStartIndex, cache._length - 1);
138
- if (computeOffset(cache, prevStartIndex) <= scrollOffset) {
138
+ if (computeOffset(cache, prevStartIndex) <= startOffset) {
139
139
  // search forward
140
140
  // start <= end, prevStartIndex <= start
141
- const end = findIndex(cache, scrollOffset + viewportSize, prevStartIndex);
142
- return [findIndex(cache, scrollOffset, prevStartIndex, end), end];
141
+ const end = findIndex(cache, endOffset, prevStartIndex);
142
+ return [findIndex(cache, startOffset, prevStartIndex, end), end];
143
143
  }
144
144
  else {
145
145
  // search backward
146
146
  // start <= end, start <= prevStartIndex
147
- const start = findIndex(cache, scrollOffset, undefined, prevStartIndex);
148
- return [start, findIndex(cache, scrollOffset + viewportSize, start)];
147
+ const start = findIndex(cache, startOffset, undefined, prevStartIndex);
148
+ return [start, findIndex(cache, endOffset, start)];
149
149
  }
150
150
  };
151
151
  /**
@@ -302,7 +302,7 @@ const getScrollSize = (store) => {
302
302
  /**
303
303
  * @internal
304
304
  */
305
- const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount = 0, cacheSnapshot, shouldAutoEstimateItemSize = false) => {
305
+ const createVirtualStore = (elementsCount, itemSize = 40, ssrCount = 0, cacheSnapshot, shouldAutoEstimateItemSize = false) => {
306
306
  let isSSR = !!ssrCount;
307
307
  let stateVersion = 1;
308
308
  let viewportSize = 0;
@@ -313,17 +313,15 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
313
313
  let _flushedJump = 0;
314
314
  let _scrollDirection = SCROLL_IDLE;
315
315
  let _scrollMode = SCROLL_BY_NATIVE;
316
- let _frozenRange = isSSR
317
- ? [0, max(ssrCount - 1, 0)]
318
- : NULL;
319
- let _prevRange = [0, 0];
316
+ let _frozenRange = NULL;
317
+ let _prevRange = [0, isSSR ? max(ssrCount - 1, 0) : -1];
320
318
  let _totalMeasuredSize = 0;
321
319
  const cache = initCache(elementsCount, itemSize, cacheSnapshot);
322
320
  const subscribers = new Set();
323
321
  const getRelativeScrollOffset = () => scrollOffset - startSpacerSize;
324
322
  const getVisibleOffset = () => getRelativeScrollOffset() + pendingJump + jump;
325
- const getRange = (offset) => {
326
- return computeRange(cache, offset, viewportSize, _prevRange[0]);
323
+ const getRange = (startOffset, endOffset) => {
324
+ return computeRange(cache, startOffset, endOffset, _prevRange[0]);
327
325
  };
328
326
  const getTotalSize = () => computeTotalSize(cache);
329
327
  const getItemOffset = (index) => {
@@ -332,6 +330,9 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
332
330
  const getItemSize$1 = (index) => {
333
331
  return getItemSize(cache, index);
334
332
  };
333
+ const isSizeEqual = (index, value = UNCACHED) => {
334
+ return cache._sizes[index] === value;
335
+ };
335
336
  const applyJump = (j) => {
336
337
  if (j) {
337
338
  if (
@@ -352,7 +353,13 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
352
353
  $getCacheSnapshot: () => {
353
354
  return takeCacheSnapshot(cache);
354
355
  },
355
- $getRange: () => {
356
+ $getRange: (bufferSize = 200) => {
357
+ if (!viewportSize || isSSR) {
358
+ // Empty viewportSize means the first render.
359
+ // We return range for SSR here, or return [0, -1] to render nothing, until the scroll offset and viewport size are determined.
360
+ // https://github.com/inokawa/virtua/issues/415
361
+ return _prevRange;
362
+ }
356
363
  let startIndex;
357
364
  let endIndex;
358
365
  if (_flushedJump) {
@@ -361,23 +368,29 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
361
368
  [startIndex, endIndex] = _prevRange;
362
369
  }
363
370
  else {
364
- [startIndex, endIndex] = _prevRange = getRange(max(0, getVisibleOffset()));
371
+ bufferSize = max(0, bufferSize);
372
+ let startOffset = max(0, getVisibleOffset());
373
+ let endOffset = startOffset + viewportSize;
374
+ // For faster initial render pass, returns without buffer if measurement seems to be in progress.
375
+ if (!shouldAutoEstimateItemSize) {
376
+ if (_scrollDirection !== SCROLL_DOWN) {
377
+ startOffset -= bufferSize;
378
+ }
379
+ if (_scrollDirection !== SCROLL_UP) {
380
+ endOffset += bufferSize;
381
+ }
382
+ }
383
+ [startIndex, endIndex] = _prevRange = getRange(max(0, startOffset), max(0, endOffset));
365
384
  if (_frozenRange) {
366
385
  startIndex = min(startIndex, _frozenRange[0]);
367
386
  endIndex = max(endIndex, _frozenRange[1]);
368
387
  }
369
388
  }
370
- if (_scrollDirection !== SCROLL_DOWN) {
371
- startIndex -= max(0, overscan);
372
- }
373
- if (_scrollDirection !== SCROLL_UP) {
374
- endIndex += max(0, overscan);
375
- }
376
389
  return [max(startIndex, 0), min(endIndex, cache._length - 1)];
377
390
  },
378
391
  $findStartIndex: () => findIndex(cache, getVisibleOffset()),
379
392
  $findEndIndex: () => findIndex(cache, getVisibleOffset() + viewportSize),
380
- $isUnmeasuredItem: (index) => cache._sizes[index] === UNCACHED,
393
+ $isUnmeasuredItem: isSizeEqual,
381
394
  $getItemOffset: getItemOffset,
382
395
  $getItemSize: getItemSize$1,
383
396
  $getItemsLength: () => cache._length,
@@ -430,7 +443,6 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
430
443
  // shouldFlushPendingJump = true;
431
444
  // }
432
445
  if (isSSR) {
433
- _frozenRange = NULL;
434
446
  isSSR = false;
435
447
  }
436
448
  scrollOffset = payload;
@@ -458,7 +470,7 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
458
470
  break;
459
471
  }
460
472
  case ACTION_ITEM_RESIZE: {
461
- const updated = payload.filter(([index, size]) => cache._sizes[index] !== size);
473
+ const updated = payload.filter(([index, size]) => !isSizeEqual(index, size));
462
474
  // Skip if all items are cached and not updated
463
475
  if (!updated.length) {
464
476
  break;
@@ -542,7 +554,7 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
542
554
  break;
543
555
  }
544
556
  case ACTION_BEFORE_MANUAL_SMOOTH_SCROLL: {
545
- _frozenRange = getRange(payload);
557
+ _frozenRange = getRange(payload, payload + viewportSize);
546
558
  mutated = UPDATE_VIRTUAL_STATE;
547
559
  break;
548
560
  }
@@ -911,13 +923,20 @@ const createWindowScroller = (store, isHorizontal) => {
911
923
  const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
912
924
  const document = getCurrentDocument(container);
913
925
  const window = getCurrentWindow(document);
914
- scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump) => {
915
- // Use absolute position not to exceed scrollable bounds
916
- // https://github.com/inokawa/virtua/discussions/475
926
+ scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump, shift) => {
917
927
  // TODO support case two window scrollers exist in the same view
918
- window.scroll({
919
- [scrollToKey]: store.$getScrollOffset() + jump,
920
- });
928
+ if (shift) {
929
+ // Use absolute position not to exceed scrollable bounds
930
+ window.scroll({
931
+ [scrollToKey]: store.$getScrollOffset() + jump,
932
+ });
933
+ }
934
+ else {
935
+ // Use window.scrollBy here, which causes less layout shift for some reason.
936
+ window.scrollBy({
937
+ [scrollToKey]: jump,
938
+ });
939
+ }
921
940
  }, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
922
941
  initialized[1](true);
923
942
  },
@@ -1284,9 +1303,9 @@ const isSameRange = (prev, next) => {
1284
1303
  */
1285
1304
  const Virtualizer = (props) => {
1286
1305
  let containerRef;
1287
- const { itemSize, horizontal = false, overscan, cache } = props;
1306
+ const { itemSize, horizontal = false, cache } = props;
1288
1307
  props = mergeProps({ as: "div" }, props);
1289
- const store = createVirtualStore(props.data.length, itemSize, overscan, undefined, cache, !itemSize);
1308
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1290
1309
  const resizer = createResizer(store, horizontal);
1291
1310
  const scroller = createScroller(store, horizontal);
1292
1311
  const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
@@ -1303,7 +1322,7 @@ const Virtualizer = (props) => {
1303
1322
  });
1304
1323
  const range = createMemo((prev) => {
1305
1324
  stateVersion();
1306
- const next = store.$getRange();
1325
+ const next = store.$getRange(props.bufferSize);
1307
1326
  if (prev && isSameRange(prev, next)) {
1308
1327
  return prev;
1309
1328
  }
@@ -1364,12 +1383,11 @@ const Virtualizer = (props) => {
1364
1383
  store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1365
1384
  }
1366
1385
  });
1367
- const [startIndex, endIndex] = range();
1368
1386
  const items = [];
1369
1387
  const indexes = [];
1370
1388
  if (props.keepMounted) {
1371
1389
  const mounted = new Set(props.keepMounted);
1372
- for (let i = startIndex, j = endIndex; i <= j; i++) {
1390
+ for (let [i, j] = range(); i <= j; i++) {
1373
1391
  mounted.add(i);
1374
1392
  }
1375
1393
  sort([...mounted]).forEach((index) => {
@@ -1378,7 +1396,7 @@ const Virtualizer = (props) => {
1378
1396
  });
1379
1397
  }
1380
1398
  else {
1381
- for (let i = startIndex, j = endIndex; i <= j; i++) {
1399
+ for (let [i, j] = range(); i <= j; i++) {
1382
1400
  items.push(props.data[i]);
1383
1401
  indexes.push(i);
1384
1402
  }
@@ -1430,7 +1448,7 @@ const VList = (props) => {
1430
1448
  "ref",
1431
1449
  "data",
1432
1450
  "children",
1433
- "overscan",
1451
+ "bufferSize",
1434
1452
  "itemSize",
1435
1453
  "shift",
1436
1454
  "horizontal",
@@ -1449,7 +1467,7 @@ const VList = (props) => {
1449
1467
  height: "100%",
1450
1468
  ...local.style,
1451
1469
  }}>
1452
- <Virtualizer ref={local.ref} data={local.data} overscan={local.overscan} itemSize={local.itemSize} shift={local.shift} horizontal={local.horizontal} keepMounted={local.keepMounted} cache={local.cache} item={local.item} onScroll={local.onScroll} onScrollEnd={local.onScrollEnd}>
1470
+ <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}>
1453
1471
  {local.children}
1454
1472
  </Virtualizer>
1455
1473
  </div>);
@@ -1463,8 +1481,8 @@ const VList = (props) => {
1463
1481
  */
1464
1482
  const WindowVirtualizer = (props) => {
1465
1483
  let containerRef;
1466
- const { ref: _ref, data: _data, children: _children, overscan, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd, } = props;
1467
- const store = createVirtualStore(props.data.length, itemSize, overscan, undefined, cache, !itemSize);
1484
+ const { ref: _ref, data: _data, children: _children, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd, } = props;
1485
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1468
1486
  const resizer = createWindowResizer(store, horizontal);
1469
1487
  const scroller = createWindowScroller(store, horizontal);
1470
1488
  const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
@@ -1482,7 +1500,7 @@ const WindowVirtualizer = (props) => {
1482
1500
  });
1483
1501
  const range = createMemo((prev) => {
1484
1502
  stateVersion();
1485
- const next = store.$getRange();
1503
+ const next = store.$getRange(props.bufferSize);
1486
1504
  if (prev && isSameRange(prev, next)) {
1487
1505
  return prev;
1488
1506
  }
@@ -1524,9 +1542,8 @@ const WindowVirtualizer = (props) => {
1524
1542
  store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1525
1543
  }
1526
1544
  });
1527
- const [startIndex, endIndex] = range();
1528
1545
  const items = [];
1529
- for (let i = startIndex, j = endIndex; i <= j; i++) {
1546
+ for (let [i, j] = range(); i <= j; i++) {
1530
1547
  items.push(props.data[i]);
1531
1548
  }
1532
1549
  return items;