@pdanpdan/virtual-scroll 0.2.0 → 0.2.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.
@@ -144,9 +144,6 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
144
144
  options: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions | undefined;
145
145
  } | null>(null);
146
146
 
147
- const maxWidth = ref(0);
148
- const maxHeight = ref(0);
149
-
150
147
  // Track if sizes are initialized
151
148
  const sizesInitialized = ref(false);
152
149
  let lastItems: T[] = [];
@@ -184,6 +181,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
184
181
  if (props.value.direction === 'both' && colCount > 0) {
185
182
  return columnSizes.query(colEnd || colCount) - columnSizes.query(colStart);
186
183
  }
184
+ /* v8 ignore else -- @preserve */
187
185
  if (props.value.direction === 'horizontal') {
188
186
  if (fixedItemSize.value !== null) {
189
187
  return (end - start) * (fixedItemSize.value + (props.value.columnGap || 0));
@@ -213,6 +211,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
213
211
 
214
212
  if (!isHydrated.value && props.value.ssrRange && !isMounted.value) {
215
213
  const { start, end } = props.value.ssrRange;
214
+ /* v8 ignore else -- @preserve */
216
215
  if (props.value.direction === 'vertical' || props.value.direction === 'both') {
217
216
  if (fixedItemSize.value !== null) {
218
217
  return (end - start) * (fixedItemSize.value + (props.value.gap || 0));
@@ -367,6 +366,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
367
366
  } else {
368
367
  const isVisibleX = targetX >= relativeScrollX.value && (targetX + itemWidth) <= (relativeScrollX.value + usableWidth);
369
368
  if (!isVisibleX) {
369
+ /* v8 ignore if -- @preserve */
370
370
  if (targetX < relativeScrollX.value) {
371
371
  // keep targetX at start
372
372
  } else {
@@ -396,6 +396,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
396
396
  let viewW = 0;
397
397
  let viewH = 0;
398
398
 
399
+ /* v8 ignore else -- @preserve */
399
400
  if (typeof window !== 'undefined') {
400
401
  if (container === window) {
401
402
  curX = window.scrollX;
@@ -416,6 +417,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
416
417
  if (!reachedX && colIndex !== null && colIndex !== undefined) {
417
418
  const atLeft = curX <= tolerance && finalX <= tolerance;
418
419
  const atRight = curX >= maxW - viewW - tolerance && finalX >= maxW - viewW - tolerance;
420
+ /* v8 ignore else -- @preserve */
419
421
  if (atLeft || atRight) {
420
422
  reachedX = true;
421
423
  }
@@ -495,12 +497,24 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
495
497
 
496
498
  const paddingStartX = getPaddingX(props.value.scrollPaddingStart, props.value.direction);
497
499
  const paddingStartY = getPaddingY(props.value.scrollPaddingStart, props.value.direction);
500
+ const paddingEndX = getPaddingX(props.value.scrollPaddingEnd, props.value.direction);
501
+ const paddingEndY = getPaddingY(props.value.scrollPaddingEnd, props.value.direction);
502
+
503
+ const usableWidth = viewportWidth.value - (isHorizontal ? (paddingStartX + paddingEndX) : 0);
504
+ const usableHeight = viewportHeight.value - (isVertical ? (paddingStartY + paddingEndY) : 0);
505
+
506
+ const clampedX = (x !== null && x !== undefined)
507
+ ? (isHorizontal ? Math.max(0, Math.min(x, Math.max(0, totalWidth.value - usableWidth))) : Math.max(0, x))
508
+ : null;
509
+ const clampedY = (y !== null && y !== undefined)
510
+ ? (isVertical ? Math.max(0, Math.min(y, Math.max(0, totalHeight.value - usableHeight))) : Math.max(0, y))
511
+ : null;
498
512
 
499
513
  const currentX = (typeof window !== 'undefined' && container === window ? window.scrollX : (container as HTMLElement).scrollLeft);
500
514
  const currentY = (typeof window !== 'undefined' && container === window ? window.scrollY : (container as HTMLElement).scrollTop);
501
515
 
502
- const targetX = (x !== null && x !== undefined) ? x + hostOffset.x - (isHorizontal ? paddingStartX : 0) : currentX;
503
- const targetY = (y !== null && y !== undefined) ? y + hostOffset.y - (isVertical ? paddingStartY : 0) : currentY;
516
+ const targetX = (clampedX !== null) ? clampedX + hostOffset.x - (isHorizontal ? paddingStartX : 0) : currentX;
517
+ const targetY = (clampedY !== null) ? clampedY + hostOffset.y - (isVertical ? paddingStartY : 0) : currentY;
504
518
 
505
519
  if (typeof window !== 'undefined' && container === window) {
506
520
  window.scrollTo({
@@ -571,6 +585,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
571
585
  let prependCount = 0;
572
586
  if (props.value.restoreScrollOnPrepend && lastItems.length > 0 && len > lastItems.length) {
573
587
  const oldFirstItem = lastItems[ 0 ];
588
+ /* v8 ignore else -- @preserve */
574
589
  if (oldFirstItem !== undefined) {
575
590
  for (let i = 1; i <= len - lastItems.length; i++) {
576
591
  if (newItems[ i ] === oldFirstItem) {
@@ -614,6 +629,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
614
629
  }
615
630
  }
616
631
 
632
+ /* v8 ignore else -- @preserve */
617
633
  if (addedX > 0 || addedY > 0) {
618
634
  nextTick(() => {
619
635
  scrollToOffset(
@@ -637,6 +653,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
637
653
  // OR if it's dynamic but we don't have a measurement yet.
638
654
  if (!isDynamicColumnWidth.value || currentW === 0) {
639
655
  const targetW = width + columnGap;
656
+ /* v8 ignore else -- @preserve */
640
657
  if (Math.abs(currentW - targetW) > 0.5) {
641
658
  columnSizes.set(i, targetW);
642
659
  colNeedsRebuild = true;
@@ -681,17 +698,6 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
681
698
  itemSizesY.set(i, targetY);
682
699
  itemsNeedRebuild = true;
683
700
  }
684
-
685
- // Max dimension tracking: determines scrollable area size
686
- const w = isHorizontal ? size : (isBoth ? Math.max(size, viewportWidth.value) : 0);
687
- const h = (isVertical || isBoth) ? size : 0;
688
-
689
- if (w > maxWidth.value) {
690
- maxWidth.value = w;
691
- }
692
- if (h > maxHeight.value) {
693
- maxHeight.value = h;
694
- }
695
701
  }
696
702
 
697
703
  if (itemsNeedRebuild) {
@@ -955,6 +961,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
955
961
  if (nextStickyIdx !== undefined) {
956
962
  const nextStickyY = fixedSize !== null ? nextStickyIdx * (fixedSize + gap) : itemSizesY.query(nextStickyIdx);
957
963
  const distance = nextStickyY - relativeScrollY.value;
964
+ /* v8 ignore else -- @preserve */
958
965
  if (distance < height) {
959
966
  stickyOffset.y = -(height - distance);
960
967
  }
@@ -980,6 +987,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
980
987
  if (nextStickyIdx !== undefined) {
981
988
  const nextStickyX = fixedSize !== null ? nextStickyIdx * (fixedSize + columnGap) : itemSizesX.query(nextStickyIdx);
982
989
  const distance = nextStickyX - relativeScrollX.value;
990
+ /* v8 ignore else -- @preserve */
983
991
  if (distance < width) {
984
992
  stickyOffset.x = -(width - distance);
985
993
  }
@@ -1131,13 +1139,6 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
1131
1139
 
1132
1140
  for (const { index, inlineSize, blockSize, element } of updates) {
1133
1141
  if (isDynamicItemSize.value) {
1134
- if (inlineSize > maxWidth.value) {
1135
- maxWidth.value = inlineSize;
1136
- }
1137
- if (blockSize > maxHeight.value) {
1138
- maxHeight.value = blockSize;
1139
- }
1140
-
1141
1142
  if (props.value.direction === 'horizontal') {
1142
1143
  const oldWidth = itemSizesX.get(index);
1143
1144
  const targetWidth = inlineSize + columnGap;
@@ -1179,10 +1180,12 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
1179
1180
  for (const child of cells) {
1180
1181
  const colIndex = Number.parseInt(child.dataset.colIndex!, 10);
1181
1182
 
1183
+ /* v8 ignore else -- @preserve */
1182
1184
  if (colIndex >= 0 && colIndex < (props.value.columnCount || 0)) {
1183
1185
  const w = child.offsetWidth;
1184
1186
  const oldW = columnSizes.get(colIndex);
1185
1187
  const targetW = w + columnGap;
1188
+ /* v8 ignore else -- @preserve */
1186
1189
  if (targetW > oldW || !measuredColumns[ colIndex ]) {
1187
1190
  columnSizes.update(colIndex, targetW - oldW);
1188
1191
  measuredColumns[ colIndex ] = 1;
@@ -1262,6 +1265,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
1262
1265
 
1263
1266
  resizeObserver = new ResizeObserver((entries) => {
1264
1267
  for (const entry of entries) {
1268
+ /* v8 ignore else -- @preserve */
1265
1269
  if (entry.target === container) {
1266
1270
  viewportWidth.value = (container as HTMLElement).clientWidth;
1267
1271
  viewportHeight.value = (container as HTMLElement).clientHeight;
@@ -1298,6 +1302,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
1298
1302
  : props.value.ssrRange?.start;
1299
1303
  const initialAlign = props.value.initialScrollAlign || 'start';
1300
1304
 
1305
+ /* v8 ignore else -- @preserve */
1301
1306
  if (initialIndex !== undefined && initialIndex !== null) {
1302
1307
  scrollToIndex(initialIndex, props.value.ssrRange?.colStart, { align: initialAlign, behavior: 'auto' });
1303
1308
  }
@@ -1328,8 +1333,6 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
1328
1333
  measuredColumns.fill(0);
1329
1334
  measuredItemsX.fill(0);
1330
1335
  measuredItemsY.fill(0);
1331
- maxWidth.value = 0;
1332
- maxHeight.value = 0;
1333
1336
  initializeSizes();
1334
1337
  };
1335
1338
 
@@ -3,84 +3,99 @@ import { describe, expect, it } from 'vitest';
3
3
  import { FenwickTree } from './fenwick-tree';
4
4
 
5
5
  describe('fenwickTree', () => {
6
- it('should initialize with correct size', () => {
7
- const tree = new FenwickTree(5);
8
- expect(tree.query(5)).toBe(0);
6
+ describe('initialization', () => {
7
+ it('should initialize with correct size', () => {
8
+ const tree = new FenwickTree(5);
9
+ expect(tree.query(5)).toBe(0);
10
+ expect(tree.length).toBe(5);
11
+ });
9
12
  });
10
13
 
11
- it('should update and query values', () => {
12
- const tree = new FenwickTree(5);
13
- tree.update(0, 10);
14
- tree.update(1, 20);
15
- tree.update(2, 30);
14
+ describe('query and update', () => {
15
+ it('should update and query values', () => {
16
+ const tree = new FenwickTree(5);
17
+ tree.update(0, 10);
18
+ tree.update(1, 20);
19
+ tree.update(2, 30);
16
20
 
17
- expect(tree.query(0)).toBe(0);
18
- expect(tree.query(1)).toBe(10);
19
- expect(tree.query(2)).toBe(30);
20
- expect(tree.query(3)).toBe(60);
21
- });
21
+ expect(tree.query(0)).toBe(0);
22
+ expect(tree.query(1)).toBe(10);
23
+ expect(tree.query(2)).toBe(30);
24
+ expect(tree.query(3)).toBe(60);
25
+ });
26
+
27
+ it('should handle updates to existing indices', () => {
28
+ const tree = new FenwickTree(3);
29
+ tree.update(1, 10);
30
+ expect(tree.query(2)).toBe(10);
31
+ tree.update(1, 5); // Add 5 to index 1
32
+ expect(tree.query(2)).toBe(15);
33
+ });
22
34
 
23
- it('should handle updates to existing indices', () => {
24
- const tree = new FenwickTree(3);
25
- tree.update(1, 10);
26
- expect(tree.query(2)).toBe(10);
27
- tree.update(1, 5); // Add 5 to index 1
28
- expect(tree.query(2)).toBe(15);
35
+ it('should ignore updates for out of bounds indices', () => {
36
+ const tree = new FenwickTree(5);
37
+ tree.update(-1, 10);
38
+ tree.update(5, 10);
39
+ expect(tree.query(5)).toBe(0);
40
+ });
29
41
  });
30
42
 
31
- it('should find lower bound correctly', () => {
32
- const tree = new FenwickTree(5);
33
- tree.update(0, 10); // sum up to 1: 10
34
- tree.update(1, 10); // sum up to 2: 20
35
- tree.update(2, 10); // sum up to 3: 30
43
+ describe('search and bounds', () => {
44
+ it('should find lower bound correctly', () => {
45
+ const tree = new FenwickTree(5);
46
+ tree.update(0, 10); // sum up to 1: 10
47
+ tree.update(1, 10); // sum up to 2: 20
48
+ tree.update(2, 10); // sum up to 3: 30
36
49
 
37
- expect(tree.findLowerBound(5)).toBe(0);
38
- expect(tree.findLowerBound(15)).toBe(1);
39
- expect(tree.findLowerBound(25)).toBe(2);
40
- expect(tree.findLowerBound(35)).toBe(5); // Returns size when not found
50
+ expect(tree.findLowerBound(5)).toBe(0);
51
+ expect(tree.findLowerBound(15)).toBe(1);
52
+ expect(tree.findLowerBound(25)).toBe(2);
53
+ expect(tree.findLowerBound(35)).toBe(5); // Returns size when not found
54
+ });
41
55
  });
42
56
 
43
- it('should resize and preserve existing values', () => {
44
- const tree = new FenwickTree(5);
45
- tree.update(0, 10);
46
- tree.resize(10);
47
- expect(tree.query(1)).toBe(10);
48
- expect(tree.query(10)).toBe(10);
49
- tree.resize(10); // same size
50
- });
57
+ describe('rebuild and resize', () => {
58
+ it('should set and rebuild correctly', () => {
59
+ const tree = new FenwickTree(5);
60
+ tree.set(0, 10);
61
+ tree.set(1, 20);
62
+ tree.set(2, 30);
63
+ tree.set(-1, 40); // ignore
64
+ tree.set(5, 50); // ignore
65
+ expect(tree.query(3)).toBe(0); // not rebuilt yet
66
+ tree.rebuild();
67
+ expect(tree.query(3)).toBe(60);
68
+ });
51
69
 
52
- it('should ignore updates for out of bounds indices', () => {
53
- const tree = new FenwickTree(5);
54
- tree.update(-1, 10);
55
- tree.update(5, 10);
56
- expect(tree.query(5)).toBe(0);
70
+ it('should resize and preserve existing values', () => {
71
+ const tree = new FenwickTree(5);
72
+ tree.update(0, 10);
73
+ tree.resize(10);
74
+ expect(tree.query(1)).toBe(10);
75
+ expect(tree.query(10)).toBe(10);
76
+ tree.resize(10); // same size
77
+ });
57
78
  });
58
79
 
59
- it('should set and rebuild correctly', () => {
60
- const tree = new FenwickTree(5);
61
- tree.set(0, 10);
62
- tree.set(1, 20);
63
- tree.set(2, 30);
64
- tree.set(-1, 40); // ignore
65
- tree.set(5, 50); // ignore
66
- expect(tree.query(3)).toBe(0); // not rebuilt yet
67
- tree.rebuild();
68
- expect(tree.query(3)).toBe(60);
69
- });
80
+ describe('values access', () => {
81
+ it('should return the individual value at an index', () => {
82
+ const tree = new FenwickTree(3);
83
+ tree.update(0, 10);
84
+ expect(tree.get(0)).toBe(10);
85
+ expect(tree.get(-1)).toBe(0);
86
+ expect(tree.get(10)).toBe(0);
87
+ });
70
88
 
71
- it('should return the underlying values array', () => {
72
- const tree = new FenwickTree(3);
73
- expect(tree.length).toBe(3);
74
- tree.update(0, 10);
75
- tree.update(1, 20);
76
- const values = tree.getValues();
77
- expect(values).toBeInstanceOf(Float64Array);
78
- expect(values[ 0 ]).toBe(10);
79
- expect(values[ 1 ]).toBe(20);
80
- expect(values[ 2 ]).toBe(0);
81
- expect(tree.get(0)).toBe(10);
82
- expect(tree.get(-1)).toBe(0);
83
- expect(tree.get(10)).toBe(0);
89
+ it('should return the underlying values array', () => {
90
+ const tree = new FenwickTree(3);
91
+ tree.update(0, 10);
92
+ tree.update(1, 20);
93
+ const values = tree.getValues();
94
+ expect(values).toBeInstanceOf(Float64Array);
95
+ expect(values[ 0 ]).toBe(10);
96
+ expect(values[ 1 ]).toBe(20);
97
+ expect(values[ 2 ]).toBe(0);
98
+ });
84
99
  });
85
100
 
86
101
  describe('shift', () => {