@pdanpdan/virtual-scroll 0.10.1 → 0.10.3

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.
@@ -1,2 +1,2 @@
1
- @layer components{.virtual-scrollbar-track{--vsi-scrollbar-bg:var(--vs-scrollbar-bg,light-dark(#e6e6e6e6,#1e1e1ee6));--vsi-scrollbar-thumb-bg:var(--vs-scrollbar-thumb-bg,light-dark(#0000004d,#ffffff4d));--vsi-scrollbar-thumb-hover-bg:var(--vs-scrollbar-thumb-hover-bg,light-dark(#0009,#fff9));--vsi-scrollbar-radius:var(--vs-scrollbar-radius,4px);--vsi-scrollbar-size:var(--vs-scrollbar-size,8px);contain:layout;background-color:var(--vsi-scrollbar-bg);border-radius:var(--vsi-scrollbar-radius);z-index:30;-webkit-user-select:none;user-select:none;pointer-events:auto;transition:opacity .2s;position:absolute}.virtual-scrollbar-track.virtual-scrollbar-track--vertical{inline-size:var(--vsi-scrollbar-size);inset-block-start:2px;inset-inline-end:2px}.virtual-scrollbar-track.virtual-scrollbar-track--horizontal{block-size:var(--vsi-scrollbar-size);inset-block-end:2px;inset-inline-start:2px}.virtual-scrollbar-thumb{background-color:var(--vsi-scrollbar-thumb-bg);border-radius:var(--vsi-scrollbar-radius);touch-action:none;pointer-events:auto;cursor:pointer;position:absolute}.virtual-scrollbar-thumb:hover,.virtual-scrollbar-thumb:active,.virtual-scrollbar-thumb.virtual-scrollbar-thumb--active{background-color:var(--vsi-scrollbar-thumb-hover-bg)}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--vertical{inline-size:100%}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--horizontal{block-size:100%}.virtual-scroll-container[data-v-a75851a5]{outline-offset:1px;block-size:100%;inline-size:100%;position:relative}.virtual-scroll-container[data-v-a75851a5]:not(.virtual-scroll--window){overscroll-behavior:contain;overflow:auto}.virtual-scroll-container.virtual-scroll--table[data-v-a75851a5]{display:block}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-a75851a5]{scrollbar-width:none;-ms-overflow-style:none}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-a75851a5]::-webkit-scrollbar{display:none}.virtual-scroll-container.virtual-scroll--horizontal[data-v-a75851a5],.virtual-scroll-container.virtual-scroll--both[data-v-a75851a5]{white-space:nowrap}.virtual-scroll-scrollbar-container[data-v-a75851a5]{z-index:30;pointer-events:none;block-size:0;inline-size:100%;position:sticky;inset-block-start:0;inset-inline-start:0;overflow:visible}.virtual-scroll-scrollbar-viewport[data-v-a75851a5]{pointer-events:none;position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-wrapper[data-v-a75851a5]{contain:layout;position:relative}:where(.virtual-scroll--hydrated>.virtual-scroll-wrapper>.virtual-scroll-item[data-v-a75851a5]){position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-item[data-v-a75851a5]{box-sizing:border-box;will-change:transform;display:grid}.virtual-scroll-item[data-v-a75851a5]:where(.virtual-scroll--debug){background-color:#ff00000d;outline:1px dashed #ff000080}.virtual-scroll-item[data-v-a75851a5]:where(.virtual-scroll--debug):where(:hover){z-index:100;background-color:#ff00001a}.virtual-scroll-debug-info[data-v-a75851a5]{color:#fff;pointer-events:none;z-index:100;background:#000000b3;border-radius:4px;padding:2px 4px;font-family:monospace;font-size:10px;position:absolute;inset-block-start:2px;inset-inline-end:2px}.virtual-scroll-spacer[data-v-a75851a5]{pointer-events:none}.virtual-scroll-header[data-v-a75851a5],.virtual-scroll-footer[data-v-a75851a5]{z-index:20;position:relative}.virtual-scroll--sticky[data-v-a75851a5]{position:sticky}.virtual-scroll--sticky[data-v-a75851a5]:where(.virtual-scroll-header){box-sizing:border-box;min-inline-size:100%;inset-block-start:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-a75851a5]:where(.virtual-scroll-footer){box-sizing:border-box;min-inline-size:100%;inset-block-end:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-a75851a5]:where(.virtual-scroll-item){z-index:10}:is(tbody.virtual-scroll-wrapper[data-v-a75851a5],thead.virtual-scroll-header[data-v-a75851a5],tfoot.virtual-scroll-footer[data-v-a75851a5]),:is(tbody.virtual-scroll-wrapper[data-v-a75851a5],thead.virtual-scroll-header[data-v-a75851a5],tfoot.virtual-scroll-footer[data-v-a75851a5])>tr{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper[data-v-a75851a5],thead.virtual-scroll-header[data-v-a75851a5],tfoot.virtual-scroll-footer[data-v-a75851a5])>tr>:is(td,th){align-items:center;display:inline-block}}
1
+ @layer components{.virtual-scrollbar-track{--vsi-scrollbar-bg:var(--vs-scrollbar-bg,light-dark(#e6e6e6e6,#1e1e1ee6));--vsi-scrollbar-thumb-bg:var(--vs-scrollbar-thumb-bg,light-dark(#0000004d,#ffffff4d));--vsi-scrollbar-thumb-hover-bg:var(--vs-scrollbar-thumb-hover-bg,light-dark(#0009,#fff9));--vsi-scrollbar-radius:var(--vs-scrollbar-radius,4px);--vsi-scrollbar-size:var(--vs-scrollbar-size,8px);contain:layout;background-color:var(--vsi-scrollbar-bg);border-radius:var(--vsi-scrollbar-radius);z-index:30;-webkit-user-select:none;user-select:none;pointer-events:auto;transition:opacity .2s;position:absolute}.virtual-scrollbar-track.virtual-scrollbar-track--vertical{inline-size:var(--vsi-scrollbar-size);inset-block-start:2px;inset-inline-end:2px}.virtual-scrollbar-track.virtual-scrollbar-track--horizontal{block-size:var(--vsi-scrollbar-size);inset-block-end:2px;inset-inline-start:2px}.virtual-scrollbar-thumb{background-color:var(--vsi-scrollbar-thumb-bg);border-radius:var(--vsi-scrollbar-radius);touch-action:none;pointer-events:auto;cursor:pointer;position:absolute}.virtual-scrollbar-thumb:hover,.virtual-scrollbar-thumb:active,.virtual-scrollbar-thumb.virtual-scrollbar-thumb--active{background-color:var(--vsi-scrollbar-thumb-hover-bg)}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--vertical{inline-size:100%}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--horizontal{block-size:100%}.virtual-scroll-container[data-v-200891f5]{outline-offset:1px;block-size:100%;inline-size:100%;position:relative}.virtual-scroll-container[data-v-200891f5]:not(.virtual-scroll--window){overscroll-behavior:contain;overflow:auto}.virtual-scroll-container.virtual-scroll--table[data-v-200891f5]{display:block}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-200891f5]{scrollbar-width:none;-ms-overflow-style:none}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-200891f5]::-webkit-scrollbar{display:none}.virtual-scroll-container.virtual-scroll--horizontal[data-v-200891f5],.virtual-scroll-container.virtual-scroll--both[data-v-200891f5]{white-space:nowrap}.virtual-scroll-scrollbar-container[data-v-200891f5]{z-index:30;pointer-events:none;block-size:0;inline-size:100%;position:sticky;inset-block-start:0;inset-inline-start:0;overflow:visible}.virtual-scroll-scrollbar-viewport[data-v-200891f5]{pointer-events:none;position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-wrapper[data-v-200891f5]{contain:layout;position:relative}:where(.virtual-scroll--hydrated>.virtual-scroll-wrapper>.virtual-scroll-item[data-v-200891f5]){position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-item[data-v-200891f5]{box-sizing:border-box;will-change:transform;display:grid}.virtual-scroll-item[data-v-200891f5]:where(.virtual-scroll--debug){background-color:#ff00000d;outline:1px dashed #ff000080}.virtual-scroll-item[data-v-200891f5]:where(.virtual-scroll--debug):where(:hover){z-index:100;background-color:#ff00001a}.virtual-scroll-debug-info[data-v-200891f5]{color:#fff;pointer-events:none;z-index:100;background:#000000b3;border-radius:4px;padding:2px 4px;font-family:monospace;font-size:10px;position:absolute;inset-block-start:2px;inset-inline-end:2px}.virtual-scroll-spacer[data-v-200891f5]{pointer-events:none}.virtual-scroll-header[data-v-200891f5],.virtual-scroll-footer[data-v-200891f5]{z-index:20;position:relative}.virtual-scroll--sticky[data-v-200891f5]{position:sticky}.virtual-scroll--sticky[data-v-200891f5]:where(.virtual-scroll-header){box-sizing:border-box;min-inline-size:100%;inset-block-start:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-200891f5]:where(.virtual-scroll-footer){box-sizing:border-box;min-inline-size:100%;inset-block-end:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-200891f5]:where(.virtual-scroll-item){z-index:10}:is(tbody.virtual-scroll-wrapper[data-v-200891f5],thead.virtual-scroll-header[data-v-200891f5],tfoot.virtual-scroll-footer[data-v-200891f5]),:is(tbody.virtual-scroll-wrapper[data-v-200891f5],thead.virtual-scroll-header[data-v-200891f5],tfoot.virtual-scroll-footer[data-v-200891f5])>tr{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper[data-v-200891f5],thead.virtual-scroll-header[data-v-200891f5],tfoot.virtual-scroll-footer[data-v-200891f5])>tr>:is(td,th){align-items:center;display:inline-block}}
2
2
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pdanpdan/virtual-scroll",
3
3
  "type": "module",
4
- "version": "0.10.1",
4
+ "version": "0.10.3",
5
5
  "description": "A high-performance virtual scroll component for Vue 3",
6
6
  "author": "",
7
7
  "license": "MIT",
@@ -1100,7 +1100,7 @@ const internalItemRole = computed(() => {
1100
1100
  }
1101
1101
  return 'listitem';
1102
1102
  });
1103
- const itemRole = computed(() => props.itemRole != null ? props.itemRole : internalItemRole.value);
1103
+ const itemRole = computed(() => props.itemRole ?? internalItemRole.value);
1104
1104
  const cellRole = computed(() => {
1105
1105
  if (props.role === 'grid' || (!props.role && props.direction === 'both')) {
1106
1106
  return 'gridcell';
@@ -417,16 +417,19 @@ export function useVirtualScroll<T = unknown>(
417
417
  const scrollBehavior = isCorrection ? 'auto' : (behavior || 'smooth');
418
418
 
419
419
  if (!dryRun) {
420
- if (scrollBehavior === 'smooth' || !isCorrection) {
421
- isProgrammaticScroll.value = true;
422
- clearTimeout(programmaticScrollTimer);
423
- if (scrollBehavior === 'smooth') {
424
- programmaticScrollTimer = setTimeout(() => {
425
- isProgrammaticScroll.value = false;
426
- programmaticScrollTimer = undefined;
427
- checkPendingScroll();
428
- }, 1000);
429
- }
420
+ isProgrammaticScroll.value = true;
421
+ clearTimeout(programmaticScrollTimer);
422
+ if (scrollBehavior === 'smooth') {
423
+ programmaticScrollTimer = setTimeout(() => {
424
+ isProgrammaticScroll.value = false;
425
+ programmaticScrollTimer = undefined;
426
+ checkPendingScroll();
427
+ }, 1000);
428
+ } else {
429
+ programmaticScrollTimer = setTimeout(() => {
430
+ isProgrammaticScroll.value = false;
431
+ programmaticScrollTimer = undefined;
432
+ }, 150);
430
433
  }
431
434
  }
432
435
 
@@ -438,7 +441,7 @@ export function useVirtualScroll<T = unknown>(
438
441
  scrollOptions.top = Math.max(0, finalY);
439
442
  }
440
443
 
441
- if (!isCorrection && !dryRun) {
444
+ if (!dryRun) {
442
445
  scrollTo(container, scrollOptions);
443
446
  }
444
447
 
@@ -466,6 +469,11 @@ export function useVirtualScroll<T = unknown>(
466
469
  programmaticScrollTimer = undefined;
467
470
  checkPendingScroll();
468
471
  }, 1000);
472
+ } else {
473
+ programmaticScrollTimer = setTimeout(() => {
474
+ isProgrammaticScroll.value = false;
475
+ programmaticScrollTimer = undefined;
476
+ }, 150);
469
477
  }
470
478
  pendingScroll.value = null;
471
479
 
@@ -620,7 +628,7 @@ export function useVirtualScroll<T = unknown>(
620
628
  treeUpdateFlag.value;
621
629
  const { start, end } = range.value;
622
630
  const items: RenderedItem<T>[] = [];
623
- const stickyIndices = [ ...(props.value.stickyIndices || []) ].sort((a, b) => a - b);
631
+ const stickyIndices = (props.value.stickyIndices || []).toSorted((a, b) => a - b);
624
632
  const stickySet = new Set(stickyIndices);
625
633
  const sortedIndices: number[] = [];
626
634
  if (isHydrated.value || !props.value.ssrRange) {
@@ -830,11 +838,47 @@ export function useVirtualScroll<T = unknown>(
830
838
  const scrollValueY = actualScrollY;
831
839
  const currentRelX = displayToVirtual(scrollValueX, 0, scaleX.value);
832
840
  const currentRelY = displayToVirtual(scrollValueY, 0, scaleY.value);
833
- const { targetX, targetY } = calculateScrollTarget({ rowIndex, colIndex, options, direction: direction.value, viewportWidth: viewportWidth.value, viewportHeight: viewportHeight.value, totalWidth: virtualWidth.value, totalHeight: virtualHeight.value, gap: props.value.gap || 0, columnGap: props.value.columnGap || 0, fixedSize: fixedItemSize.value, fixedWidth: fixedColumnWidth.value, relativeScrollX: currentRelX, relativeScrollY: currentRelY, getItemSizeY: (idx) => itemSizesY.get(idx), getItemSizeX: (idx) => itemSizesX.get(idx), getItemQueryY: (idx) => itemSizesY.query(idx), getItemQueryX: (idx) => itemSizesX.query(idx), getColumnSize: (idx) => columnSizes.get(idx), getColumnQuery: (idx) => columnSizes.query(idx), scaleX: scaleX.value, scaleY: scaleY.value, hostOffsetX: componentOffset.x, hostOffsetY: componentOffset.y, stickyIndices: (props.value.stickyIndices || []), stickyStartX: stickyStartX.value, stickyStartY: stickyStartY.value, stickyEndX: stickyEndX.value, stickyEndY: stickyEndY.value, flowPaddingStartX: flowStartX.value, flowPaddingStartY: flowStartY.value, paddingStartX: paddingStartX.value, paddingStartY: paddingStartY.value, paddingEndX: paddingEndX.value, paddingEndY: paddingEndY.value });
841
+ const { targetX, targetY } = calculateScrollTarget({
842
+ rowIndex,
843
+ colIndex,
844
+ options,
845
+ direction: direction.value,
846
+ viewportWidth: viewportWidth.value,
847
+ viewportHeight: viewportHeight.value,
848
+ totalWidth: totalWidth.value,
849
+ totalHeight: totalHeight.value,
850
+ gap: props.value.gap || 0,
851
+ columnGap: props.value.columnGap || 0,
852
+ fixedSize: fixedItemSize.value,
853
+ fixedWidth: fixedColumnWidth.value,
854
+ relativeScrollX: currentRelX,
855
+ relativeScrollY: currentRelY,
856
+ getItemSizeY: (idx) => itemSizesY.get(idx),
857
+ getItemSizeX: (idx) => itemSizesX.get(idx),
858
+ getItemQueryY: (idx) => itemSizesY.query(idx),
859
+ getItemQueryX: (idx) => itemSizesX.query(idx),
860
+ getColumnSize: (idx) => columnSizes.get(idx),
861
+ getColumnQuery: (idx) => columnSizes.query(idx),
862
+ scaleX: scaleX.value,
863
+ scaleY: scaleY.value,
864
+ hostOffsetX: componentOffset.x,
865
+ hostOffsetY: componentOffset.y,
866
+ stickyIndices: props.value.stickyIndices || [],
867
+ stickyStartX: stickyStartX.value,
868
+ stickyStartY: stickyStartY.value,
869
+ stickyEndX: stickyEndX.value,
870
+ stickyEndY: stickyEndY.value,
871
+ flowPaddingStartX: flowStartX.value,
872
+ flowPaddingStartY: flowStartY.value,
873
+ paddingStartX: paddingStartX.value,
874
+ paddingStartY: paddingStartY.value,
875
+ paddingEndX: paddingEndX.value,
876
+ paddingEndY: paddingEndY.value,
877
+ });
834
878
  const toleranceX = 2 * scaleX.value;
835
879
  const toleranceY = 2 * scaleY.value;
836
- const reachedX = (colIndex === null || colIndex === undefined) || Math.abs(currentRelX - targetX) < toleranceX;
837
- const reachedY = (rowIndex === null || rowIndex === undefined) || Math.abs(currentRelY - targetY) < toleranceY;
880
+ const reachedX = (colIndex === null || colIndex === undefined) || (viewportWidth.value > 0 && Math.abs(currentRelX - targetX) < toleranceX);
881
+ const reachedY = (rowIndex === null || rowIndex === undefined) || (viewportHeight.value > 0 && Math.abs(currentRelY - targetY) < toleranceY);
838
882
  if (reachedX && reachedY) {
839
883
  const isMeasuredX = colIndex == null || colIndex === undefined || measuredColumns.value[ colIndex ] === 1;
840
884
  const isMeasuredY = rowIndex == null || rowIndex === undefined || measuredItemsY.value[ rowIndex ] === 1;
@@ -848,7 +892,7 @@ export function useVirtualScroll<T = unknown>(
848
892
  }
849
893
  }
850
894
 
851
- watch([ treeUpdateFlag, viewportWidth, viewportHeight ], checkPendingScroll);
895
+ watch([ treeUpdateFlag, viewportWidth, viewportHeight, isHydrating ], checkPendingScroll);
852
896
  watch(isScrolling, (scrolling) => {
853
897
  if (!scrolling) {
854
898
  checkPendingScroll();
@@ -388,7 +388,7 @@ export function useVirtualScrollSizes<T>(
388
388
  tryUpdateColumn(Number.parseInt(colIndexAttr, 10), inlineSize);
389
389
  } else {
390
390
  // If the element is a row, try to find cells with data-col-index
391
- const cells = Array.from(element.querySelectorAll('[data-col-index]')) as HTMLElement[];
391
+ const cells = [ ...element.querySelectorAll('[data-col-index]') ] as HTMLElement[];
392
392
 
393
393
  for (const child of cells) {
394
394
  const colIndex = Number.parseInt(child.dataset.colIndex!, 10);
@@ -11,7 +11,7 @@ import { findPrevStickyIndex } from '../utils/virtual-scroll-logic';
11
11
  */
12
12
  export function useStickyExtension<T = unknown>(): VirtualScrollExtension<T> {
13
13
  const sortedStickyIndices = (ctx: ExtensionContext<T>) =>
14
- computed(() => [ ...(ctx.props.value.stickyIndices || []) ].sort((a, b) => a - b));
14
+ computed(() => (ctx.props.value.stickyIndices || []).toSorted((a, b) => a - b));
15
15
 
16
16
  return {
17
17
  name: 'sticky',