virtua 0.45.3 → 0.46.1

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) => {
@@ -355,7 +353,13 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
355
353
  $getCacheSnapshot: () => {
356
354
  return takeCacheSnapshot(cache);
357
355
  },
358
- $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
+ }
359
363
  let startIndex;
360
364
  let endIndex;
361
365
  if (_flushedJump) {
@@ -364,18 +368,24 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
364
368
  [startIndex, endIndex] = _prevRange;
365
369
  }
366
370
  else {
367
- [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));
368
384
  if (_frozenRange) {
369
385
  startIndex = min(startIndex, _frozenRange[0]);
370
386
  endIndex = max(endIndex, _frozenRange[1]);
371
387
  }
372
388
  }
373
- if (_scrollDirection !== SCROLL_DOWN) {
374
- startIndex -= max(0, overscan);
375
- }
376
- if (_scrollDirection !== SCROLL_UP) {
377
- endIndex += max(0, overscan);
378
- }
379
389
  return [max(startIndex, 0), min(endIndex, cache._length - 1)];
380
390
  },
381
391
  $findStartIndex: () => findIndex(cache, getVisibleOffset()),
@@ -433,7 +443,6 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
433
443
  // shouldFlushPendingJump = true;
434
444
  // }
435
445
  if (isSSR) {
436
- _frozenRange = NULL;
437
446
  isSSR = false;
438
447
  }
439
448
  scrollOffset = payload;
@@ -517,6 +526,9 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
517
526
  }
518
527
  case ACTION_VIEWPORT_RESIZE: {
519
528
  if (viewportSize !== payload) {
529
+ if (!viewportSize) {
530
+ shouldSync = true;
531
+ }
520
532
  viewportSize = payload;
521
533
  mutated = UPDATE_VIRTUAL_STATE + UPDATE_SIZE_EVENT;
522
534
  }
@@ -545,7 +557,7 @@ const createVirtualStore = (elementsCount, itemSize = 40, overscan = 4, ssrCount
545
557
  break;
546
558
  }
547
559
  case ACTION_BEFORE_MANUAL_SMOOTH_SCROLL: {
548
- _frozenRange = getRange(payload);
560
+ _frozenRange = getRange(payload, payload + viewportSize);
549
561
  mutated = UPDATE_VIRTUAL_STATE;
550
562
  break;
551
563
  }
@@ -914,13 +926,20 @@ const createWindowScroller = (store, isHorizontal) => {
914
926
  const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
915
927
  const document = getCurrentDocument(container);
916
928
  const window = getCurrentWindow(document);
917
- scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump) => {
918
- // Use absolute position not to exceed scrollable bounds
919
- // https://github.com/inokawa/virtua/discussions/475
929
+ scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump, shift) => {
920
930
  // TODO support case two window scrollers exist in the same view
921
- window.scroll({
922
- [scrollToKey]: store.$getScrollOffset() + jump,
923
- });
931
+ if (shift) {
932
+ // Use absolute position not to exceed scrollable bounds
933
+ window.scroll({
934
+ [scrollToKey]: store.$getScrollOffset() + jump,
935
+ });
936
+ }
937
+ else {
938
+ // Use window.scrollBy here, which causes less layout shift for some reason.
939
+ window.scrollBy({
940
+ [scrollToKey]: jump,
941
+ });
942
+ }
924
943
  }, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
925
944
  initialized[1](true);
926
945
  },
@@ -1287,9 +1306,9 @@ const isSameRange = (prev, next) => {
1287
1306
  */
1288
1307
  const Virtualizer = (props) => {
1289
1308
  let containerRef;
1290
- const { itemSize, horizontal = false, overscan, cache } = props;
1309
+ const { itemSize, horizontal = false, cache } = props;
1291
1310
  props = mergeProps({ as: "div" }, props);
1292
- const store = createVirtualStore(props.data.length, itemSize, overscan, undefined, cache, !itemSize);
1311
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1293
1312
  const resizer = createResizer(store, horizontal);
1294
1313
  const scroller = createScroller(store, horizontal);
1295
1314
  const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
@@ -1306,7 +1325,7 @@ const Virtualizer = (props) => {
1306
1325
  });
1307
1326
  const range = createMemo((prev) => {
1308
1327
  stateVersion();
1309
- const next = store.$getRange();
1328
+ const next = store.$getRange(props.bufferSize);
1310
1329
  if (prev && isSameRange(prev, next)) {
1311
1330
  return prev;
1312
1331
  }
@@ -1432,7 +1451,7 @@ const VList = (props) => {
1432
1451
  "ref",
1433
1452
  "data",
1434
1453
  "children",
1435
- "overscan",
1454
+ "bufferSize",
1436
1455
  "itemSize",
1437
1456
  "shift",
1438
1457
  "horizontal",
@@ -1451,7 +1470,7 @@ const VList = (props) => {
1451
1470
  height: "100%",
1452
1471
  ...local.style,
1453
1472
  }}>
1454
- <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}>
1473
+ <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}>
1455
1474
  {local.children}
1456
1475
  </Virtualizer>
1457
1476
  </div>);
@@ -1465,8 +1484,8 @@ const VList = (props) => {
1465
1484
  */
1466
1485
  const WindowVirtualizer = (props) => {
1467
1486
  let containerRef;
1468
- const { ref: _ref, data: _data, children: _children, overscan, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd, } = props;
1469
- const store = createVirtualStore(props.data.length, itemSize, overscan, undefined, cache, !itemSize);
1487
+ const { ref: _ref, data: _data, children: _children, itemSize, shift: _shift, horizontal = false, cache, onScrollEnd: _onScrollEnd, } = props;
1488
+ const store = createVirtualStore(props.data.length, itemSize, undefined, cache, !itemSize);
1470
1489
  const resizer = createWindowResizer(store, horizontal);
1471
1490
  const scroller = createWindowScroller(store, horizontal);
1472
1491
  const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
@@ -1484,7 +1503,7 @@ const WindowVirtualizer = (props) => {
1484
1503
  });
1485
1504
  const range = createMemo((prev) => {
1486
1505
  stateVersion();
1487
- const next = store.$getRange();
1506
+ const next = store.$getRange(props.bufferSize);
1488
1507
  if (prev && isSameRange(prev, next)) {
1489
1508
  return prev;
1490
1509
  }