virtua 0.47.2 → 0.48.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.
@@ -212,24 +212,18 @@ const updateCacheLength = (cache, length, isShift) => {
212
212
  * @internal
213
213
  */
214
214
  const isBrowser = typeof window !== "undefined";
215
- const getDocumentElement = () => document.documentElement;
216
215
  /**
217
216
  * @internal
218
217
  */
219
- const getCurrentDocument = (node) => node.ownerDocument;
218
+ const getDocumentElement = (doc) => doc.documentElement;
220
219
  /**
221
220
  * @internal
222
221
  */
223
- const getCurrentWindow = (doc) => doc.defaultView;
222
+ const getCurrentDocument = (node) => node.ownerDocument;
224
223
  /**
225
224
  * @internal
226
225
  */
227
- const isRTLDocument = /*#__PURE__*/ once(() => {
228
- // TODO support SSR in rtl
229
- return isBrowser
230
- ? getComputedStyle(getDocumentElement()).direction === "rtl"
231
- : false;
232
- });
226
+ const getCurrentWindow = (doc) => doc.defaultView;
233
227
  /**
234
228
  * Currently, all browsers on iOS/iPadOS are WebKit, including WebView.
235
229
  * @internal
@@ -248,7 +242,7 @@ const isIOSWebKit = /*#__PURE__*/ once(() => {
248
242
  * @internal
249
243
  */
250
244
  const isSmoothScrollSupported = /*#__PURE__*/ once(() => {
251
- return "scrollBehavior" in getDocumentElement().style;
245
+ return "scrollBehavior" in getDocumentElement(document).style;
252
246
  });
253
247
 
254
248
  const MAX_INT_32 = 0x7fffffff;
@@ -316,8 +310,12 @@ const createVirtualStore = (elementsCount, itemSize = 40, ssrCount = 0, cacheSna
316
310
  return computeRange(cache, startOffset, endOffset, _prevRange[0]);
317
311
  };
318
312
  const getTotalSize = () => getItemOffset(cache, cache._length);
319
- const getItemOffset$1 = (index) => {
320
- return getItemOffset(cache, index) - pendingJump;
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;
321
319
  };
322
320
  const getItemSize$1 = (index) => {
323
321
  return getItemSize(cache, index);
@@ -598,20 +596,18 @@ const debounce = (fn, ms) => {
598
596
  return debouncedFn;
599
597
  };
600
598
  /**
601
- * scrollLeft is negative value in rtl direction.
599
+ * scrollTop/scrollLeft can be negative value under certain styles.
600
+ * - direction: rtl https://github.com/othree/jquery.rtl-scroll-type
601
+ * - writing-mode https://people.igalia.com/fwang/scrollable-elements-in-non-default-writing-modes/
602
+ * - flex-direction: column-reverse/row-reverse
602
603
  *
603
- * left right
604
- * 0 100 spec compliant (ltr)
605
- * -100 0 spec compliant (rtl)
606
- * https://github.com/othree/jquery.rtl-scroll-type
604
+ * top/left bottom/right
605
+ * 0 100 spec compliant bottom/right overflow, or possibly top/left overflow in Chrome earlier than v85
606
+ * -100 0 spec compliant top/left overflow
607
+ * https://drafts.csswg.org/cssom-view/#scroll-an-element
607
608
  */
608
- const normalizeOffset = (offset, isHorizontal) => {
609
- if (isHorizontal && isRTLDocument()) {
610
- return -offset;
611
- }
612
- else {
613
- return offset;
614
- }
609
+ const normalizeScrollOffset = (offset, isNegative) => {
610
+ return isNegative ? -offset : offset;
615
611
  };
616
612
  const createScrollObserver = (store, viewport, isHorizontal, getScrollOffset, updateScrollOffset, getStartOffset) => {
617
613
  const now = Date.now;
@@ -795,10 +791,11 @@ const createScroller = (store, isHorizontal) => {
795
791
  let viewportElement;
796
792
  let scrollObserver;
797
793
  let initialized = createPromise();
794
+ let isNegative = false;
798
795
  const scrollOffsetKey = isHorizontal ? "scrollLeft" : "scrollTop";
799
796
  const overflowKey = isHorizontal ? "overflowX" : "overflowY";
800
797
  const [scheduleScroll, cancelScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
801
- offset = normalizeOffset(offset, isHorizontal);
798
+ offset = normalizeScrollOffset(offset, isNegative);
802
799
  if (smooth) {
803
800
  viewportElement.scrollTo({
804
801
  [isHorizontal ? "left" : "top"]: offset,
@@ -812,7 +809,25 @@ const createScroller = (store, isHorizontal) => {
812
809
  return {
813
810
  $observe(viewport) {
814
811
  viewportElement = viewport;
815
- scrollObserver = createScrollObserver(store, viewport, isHorizontal, () => normalizeOffset(viewport[scrollOffsetKey], isHorizontal), (jump, shift, isMomentumScrolling) => {
812
+ const clean = store.$subscribe(UPDATE_SIZE_EVENT, () => {
813
+ const viewportSize = store.$getViewportSize();
814
+ if (viewportSize) {
815
+ const prev = viewport[scrollOffsetKey];
816
+ // Detect overflowed direction after the initial viewport measurement
817
+ const dummy = getCurrentDocument(viewport).createElement("div");
818
+ dummy.style.cssText = `visibility:hidden;min-${isHorizontal ? "width" : "height"}:${
819
+ // Set larger value than the viewportSize to force overflow. And the value must take subpixels into account.
820
+ viewportSize + 9}px`;
821
+ viewport.appendChild(dummy);
822
+ viewport[scrollOffsetKey] = 1;
823
+ // It can be positive under some specific situations even if negative mode, so we use `<` for now.
824
+ isNegative = viewport[scrollOffsetKey] < 1;
825
+ viewport.removeChild(dummy);
826
+ viewport[scrollOffsetKey] = prev;
827
+ clean();
828
+ }
829
+ });
830
+ scrollObserver = createScrollObserver(store, viewport, isHorizontal, () => normalizeScrollOffset(viewport[scrollOffsetKey], isNegative), (jump, shift, isMomentumScrolling) => {
816
831
  // If we update scroll position while touching on iOS, the position will be reverted.
817
832
  // However iOS WebKit fires touch events only once at the beginning of momentum scrolling.
818
833
  // That means we have no reliable way to confirm still touched or not if user touches more than once during momentum scrolling...
@@ -827,7 +842,7 @@ const createScroller = (store, isHorizontal) => {
827
842
  }
828
843
  // Use absolute position not to exceed scrollable bounds
829
844
  // https://github.com/inokawa/virtua/discussions/475
830
- viewport[scrollOffsetKey] = normalizeOffset(store.$getScrollOffset() + jump, isHorizontal);
845
+ viewport[scrollOffsetKey] = normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative);
831
846
  if (shift) {
832
847
  // https://github.com/inokawa/virtua/issues/357
833
848
  cancelScroll();
@@ -841,6 +856,7 @@ const createScroller = (store, isHorizontal) => {
841
856
  // https://github.com/inokawa/virtua/pull/765
842
857
  initialized = createPromise();
843
858
  },
859
+ $isNegative: () => isNegative,
844
860
  $scrollTo(offset) {
845
861
  scheduleScroll(() => offset);
846
862
  },
@@ -888,9 +904,10 @@ const createWindowScroller = (store, isHorizontal) => {
888
904
  let containerElement;
889
905
  let scrollObserver;
890
906
  let initialized = createPromise();
907
+ let isNegative = false;
891
908
  const scrollToKey = isHorizontal ? "left" : "top";
892
909
  const [scheduleScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
893
- offset = normalizeOffset(offset, isHorizontal);
910
+ offset = normalizeScrollOffset(offset, isNegative);
894
911
  const window = getCurrentWindow(getCurrentDocument(containerElement));
895
912
  if (smooth) {
896
913
  window.scroll({
@@ -908,7 +925,7 @@ const createWindowScroller = (store, isHorizontal) => {
908
925
  // TODO calc offset only when it changes (maybe impossible)
909
926
  const offsetKey = isHorizontal ? "offsetLeft" : "offsetTop";
910
927
  const offsetSum = offset +
911
- (isHorizontal && isRTLDocument()
928
+ (isHorizontal && isNegative
912
929
  ? window.innerWidth - node[offsetKey] - node.offsetWidth
913
930
  : node[offsetKey]);
914
931
  const parent = node.offsetParent;
@@ -923,18 +940,23 @@ const createWindowScroller = (store, isHorizontal) => {
923
940
  const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
924
941
  const document = getCurrentDocument(container);
925
942
  const window = getCurrentWindow(document);
926
- scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump, shift) => {
943
+ if (isHorizontal) {
944
+ // Detect RTL document
945
+ isNegative =
946
+ getComputedStyle(getDocumentElement(document)).direction === "rtl";
947
+ }
948
+ scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeScrollOffset(window[scrollOffsetKey], isNegative), (jump, shift) => {
927
949
  // TODO support case two window scrollers exist in the same view
928
950
  if (shift) {
929
951
  // Use absolute position not to exceed scrollable bounds
930
952
  window.scroll({
931
- [scrollToKey]: normalizeOffset(store.$getScrollOffset() + jump, isHorizontal),
953
+ [scrollToKey]: normalizeScrollOffset(store.$getScrollOffset() + jump, isNegative),
932
954
  });
933
955
  }
934
956
  else {
935
957
  // Use window.scrollBy here, which causes less layout shift for some reason.
936
958
  window.scrollBy({
937
- [scrollToKey]: normalizeOffset(jump, isHorizontal),
959
+ [scrollToKey]: normalizeScrollOffset(jump, isNegative),
938
960
  });
939
961
  }
940
962
  }, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
@@ -947,6 +969,7 @@ const createWindowScroller = (store, isHorizontal) => {
947
969
  // https://github.com/inokawa/virtua/pull/765
948
970
  initialized = createPromise();
949
971
  },
972
+ $isNegative: () => isNegative,
950
973
  $fixScrollJump: () => {
951
974
  scrollObserver && scrollObserver._fixScrollJump();
952
975
  },
@@ -970,7 +993,7 @@ const createWindowScroller = (store, isHorizontal) => {
970
993
  }
971
994
  const document = getCurrentDocument(containerElement);
972
995
  const window = getCurrentWindow(document);
973
- const html = document.documentElement;
996
+ const html = getDocumentElement(document);
974
997
  const getScrollbarSize = () => store.$getViewportSize() -
975
998
  (isHorizontal ? html.clientWidth : html.clientHeight);
976
999
  scheduleScroll(() => {
@@ -1006,6 +1029,7 @@ const createGridScroller = (rowStore, colStore) => {
1006
1029
  rowScroller.$dispose();
1007
1030
  colScroller.$dispose();
1008
1031
  },
1032
+ $isNegative: colScroller.$isNegative,
1009
1033
  $scrollTo(row, col) {
1010
1034
  if (row != null) {
1011
1035
  rowScroller.$scrollTo(row);
@@ -1266,9 +1290,6 @@ const createGridResizer = (rowStore, colStore) => {
1266
1290
  };
1267
1291
  };
1268
1292
 
1269
- /**
1270
- * @jsxImportSource solid-js
1271
- */
1272
1293
  /**
1273
1294
  * @internal
1274
1295
  */
@@ -1288,7 +1309,7 @@ const ListItem = (props) => {
1288
1309
  position: "absolute",
1289
1310
  [isHorizontal ? "height" : "width"]: "100%",
1290
1311
  [isHorizontal ? "top" : "left"]: "0px",
1291
- [isHorizontal ? (isRTLDocument() ? "right" : "left") : "top"]: props._offset + "px",
1312
+ [isHorizontal ? "left" : "top"]: props._offset + "px",
1292
1313
  visibility: props._hide ? "hidden" : undefined,
1293
1314
  };
1294
1315
  if (isHorizontal) {
@@ -1343,6 +1364,7 @@ const Virtualizer = (props) => {
1343
1364
  });
1344
1365
  const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1345
1366
  const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1367
+ const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1346
1368
  onMount(() => {
1347
1369
  if (props.ref) {
1348
1370
  props.ref({
@@ -1393,6 +1415,7 @@ const Virtualizer = (props) => {
1393
1415
  store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1394
1416
  }
1395
1417
  });
1418
+ const pushKey = !horizontal && isNegative() ? "unshift" : "push";
1396
1419
  const items = [];
1397
1420
  const indexes = [];
1398
1421
  if (props.keepMounted) {
@@ -1401,14 +1424,14 @@ const Virtualizer = (props) => {
1401
1424
  mounted.add(i);
1402
1425
  }
1403
1426
  sort([...mounted]).forEach((index) => {
1404
- items.push(props.data[index]);
1405
- indexes.push(index);
1427
+ items[pushKey](props.data[index]);
1428
+ indexes[pushKey](index);
1406
1429
  });
1407
1430
  }
1408
1431
  else {
1409
1432
  for (let [i, j] = range(); i <= j; i++) {
1410
- items.push(props.data[i]);
1411
- indexes.push(i);
1433
+ items[pushKey](props.data[i]);
1434
+ indexes[pushKey](i);
1412
1435
  }
1413
1436
  }
1414
1437
  return { _items: items, _indexes: indexes };
@@ -1416,7 +1439,7 @@ const Virtualizer = (props) => {
1416
1439
  const renderItem = (data, index) => {
1417
1440
  const offset = createMemo(() => {
1418
1441
  stateVersion();
1419
- return store.$getItemOffset(index());
1442
+ return store.$getItemOffset(index(), isNegative());
1420
1443
  });
1421
1444
  const hide = createMemo(() => {
1422
1445
  stateVersion();
@@ -1517,6 +1540,7 @@ const WindowVirtualizer = (props) => {
1517
1540
  });
1518
1541
  const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
1519
1542
  const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
1543
+ const isNegative = createMemo(() => stateVersion() && scroller.$isNegative());
1520
1544
  onMount(() => {
1521
1545
  if (props.ref) {
1522
1546
  props.ref({
@@ -1556,9 +1580,10 @@ const WindowVirtualizer = (props) => {
1556
1580
  store.$update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
1557
1581
  }
1558
1582
  });
1583
+ const pushKey = !horizontal && isNegative() ? "unshift" : "push";
1559
1584
  const items = [];
1560
1585
  for (let [i, j] = range(); i <= j; i++) {
1561
- items.push(props.data[i]);
1586
+ items[pushKey](props.data[i]);
1562
1587
  }
1563
1588
  return items;
1564
1589
  });
@@ -1576,7 +1601,7 @@ const WindowVirtualizer = (props) => {
1576
1601
  const itemIndex = createMemo(() => range()[0] + index());
1577
1602
  const offset = createMemo(() => {
1578
1603
  stateVersion();
1579
- return store.$getItemOffset(itemIndex());
1604
+ return store.$getItemOffset(itemIndex(), isNegative());
1580
1605
  });
1581
1606
  const hide = createMemo(() => {
1582
1607
  stateVersion();