@pdanpdan/virtual-scroll 0.2.1 → 0.3.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.
- package/README.md +182 -88
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +100 -35
- package/dist/index.js +1 -844
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +902 -0
- package/dist/index.mjs.map +1 -0
- package/dist/virtual-scroll.css +2 -0
- package/package.json +8 -5
- package/src/components/VirtualScroll.test.ts +62 -15
- package/src/components/VirtualScroll.vue +83 -19
- package/src/composables/useVirtualScroll.test.ts +501 -68
- package/src/composables/useVirtualScroll.ts +150 -65
- package/dist/index.css +0 -2
|
@@ -6,102 +6,129 @@ import { computed, getCurrentInstance, nextTick, onMounted, onUnmounted, reactiv
|
|
|
6
6
|
import { FenwickTree } from '../utils/fenwick-tree';
|
|
7
7
|
import { getPaddingX, getPaddingY, isElement, isScrollableElement, isScrollToIndexOptions } from '../utils/scroll';
|
|
8
8
|
|
|
9
|
-
export const DEFAULT_ITEM_SIZE =
|
|
10
|
-
export const DEFAULT_COLUMN_WIDTH =
|
|
9
|
+
export const DEFAULT_ITEM_SIZE = 40;
|
|
10
|
+
export const DEFAULT_COLUMN_WIDTH = 100;
|
|
11
11
|
export const DEFAULT_BUFFER = 5;
|
|
12
12
|
|
|
13
13
|
export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
|
|
14
14
|
export type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
|
|
15
15
|
|
|
16
|
+
/** Options for scroll alignment in a single axis or both axes. */
|
|
16
17
|
export interface ScrollAlignmentOptions {
|
|
18
|
+
/** Alignment on the X axis. */
|
|
17
19
|
x?: ScrollAlignment;
|
|
20
|
+
/** Alignment on the Y axis. */
|
|
18
21
|
y?: ScrollAlignment;
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
/** Options for the scrollToIndex method. */
|
|
21
25
|
export interface ScrollToIndexOptions {
|
|
26
|
+
/** Where to align the item in the viewport. */
|
|
22
27
|
align?: ScrollAlignment | ScrollAlignmentOptions;
|
|
28
|
+
/** Scroll behavior. */
|
|
23
29
|
behavior?: 'auto' | 'smooth';
|
|
30
|
+
/** Internal flag for recursive correction calls. */
|
|
24
31
|
isCorrection?: boolean;
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/** Configuration properties for the useVirtualScroll composable. */
|
|
27
35
|
export interface VirtualScrollProps<T = unknown> {
|
|
28
|
-
/** Array of items to be virtualized */
|
|
36
|
+
/** Array of items to be virtualized. */
|
|
29
37
|
items: T[];
|
|
30
|
-
/** Fixed size of each item or a function that returns the size of an item */
|
|
38
|
+
/** Fixed size of each item or a function that returns the size of an item. */
|
|
31
39
|
itemSize?: number | ((item: T, index: number) => number) | undefined;
|
|
32
|
-
/** Direction of the scroll: 'vertical', 'horizontal', or 'both' */
|
|
40
|
+
/** Direction of the scroll: 'vertical', 'horizontal', or 'both'. */
|
|
33
41
|
direction?: ScrollDirection | undefined;
|
|
34
|
-
/** Number of items to render before the visible viewport */
|
|
42
|
+
/** Number of items to render before the visible viewport. */
|
|
35
43
|
bufferBefore?: number | undefined;
|
|
36
|
-
/** Number of items to render after the visible viewport */
|
|
44
|
+
/** Number of items to render after the visible viewport. */
|
|
37
45
|
bufferAfter?: number | undefined;
|
|
38
|
-
/** The scrollable container element or window */
|
|
46
|
+
/** The scrollable container element or window. */
|
|
39
47
|
container?: HTMLElement | Window | null | undefined;
|
|
40
|
-
/** The host element that contains the items */
|
|
48
|
+
/** The host element that contains the items. */
|
|
41
49
|
hostElement?: HTMLElement | null | undefined;
|
|
42
|
-
/** Range of items to render for SSR */
|
|
50
|
+
/** Range of items to render for SSR. */
|
|
43
51
|
ssrRange?: {
|
|
44
52
|
start: number;
|
|
45
53
|
end: number;
|
|
46
54
|
colStart?: number;
|
|
47
55
|
colEnd?: number;
|
|
48
56
|
} | undefined;
|
|
49
|
-
/** Number of columns for bidirectional scroll */
|
|
57
|
+
/** Number of columns for bidirectional scroll. */
|
|
50
58
|
columnCount?: number | undefined;
|
|
51
|
-
/** Fixed width of columns or an array
|
|
59
|
+
/** Fixed width of columns or an array/function for column widths. */
|
|
52
60
|
columnWidth?: number | number[] | ((index: number) => number) | undefined;
|
|
53
|
-
/** Padding at the start of the scroll container
|
|
61
|
+
/** Padding at the start of the scroll container. */
|
|
54
62
|
scrollPaddingStart?: number | { x?: number; y?: number; } | undefined;
|
|
55
|
-
/** Padding at the end of the scroll container */
|
|
63
|
+
/** Padding at the end of the scroll container. */
|
|
56
64
|
scrollPaddingEnd?: number | { x?: number; y?: number; } | undefined;
|
|
57
|
-
/** Gap between items in pixels (vertical) */
|
|
65
|
+
/** Gap between items in pixels (vertical). */
|
|
58
66
|
gap?: number | undefined;
|
|
59
|
-
/** Gap between columns in pixels (horizontal/grid) */
|
|
67
|
+
/** Gap between columns in pixels (horizontal/grid). */
|
|
60
68
|
columnGap?: number | undefined;
|
|
61
|
-
/** Indices of items that should stick to the top/start */
|
|
69
|
+
/** Indices of items that should stick to the top/start. */
|
|
62
70
|
stickyIndices?: number[] | undefined;
|
|
63
|
-
/** Distance from the end of the scrollable area to trigger 'load' event */
|
|
71
|
+
/** Distance from the end of the scrollable area to trigger 'load' event. */
|
|
64
72
|
loadDistance?: number | undefined;
|
|
65
|
-
/** Whether items are currently being loaded */
|
|
73
|
+
/** Whether items are currently being loaded. */
|
|
66
74
|
loading?: boolean | undefined;
|
|
67
|
-
/** Whether to restore scroll position when items are prepended */
|
|
75
|
+
/** Whether to restore scroll position when items are prepended. */
|
|
68
76
|
restoreScrollOnPrepend?: boolean | undefined;
|
|
69
|
-
/** Initial scroll index to jump to on mount */
|
|
77
|
+
/** Initial scroll index to jump to on mount. */
|
|
70
78
|
initialScrollIndex?: number | undefined;
|
|
71
|
-
/** Alignment for the initial scroll index */
|
|
79
|
+
/** Alignment for the initial scroll index. */
|
|
72
80
|
initialScrollAlign?: ScrollAlignment | ScrollAlignmentOptions | undefined;
|
|
73
|
-
/** Default size for items before they are measured */
|
|
81
|
+
/** Default size for items before they are measured. */
|
|
74
82
|
defaultItemSize?: number | undefined;
|
|
75
|
-
/** Default width for columns before they are measured */
|
|
83
|
+
/** Default width for columns before they are measured. */
|
|
76
84
|
defaultColumnWidth?: number | undefined;
|
|
77
|
-
/** Whether to enable debug mode
|
|
85
|
+
/** Whether to enable debug mode. */
|
|
78
86
|
debug?: boolean | undefined;
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
/** Represents an item currently rendered in the virtual scroll area. */
|
|
81
90
|
export interface RenderedItem<T = unknown> {
|
|
91
|
+
/** The original data item. */
|
|
82
92
|
item: T;
|
|
93
|
+
/** The index of the item in the original array. */
|
|
83
94
|
index: number;
|
|
95
|
+
/** The calculated offset relative to the host element. */
|
|
84
96
|
offset: { x: number; y: number; };
|
|
97
|
+
/** The current measured or estimated size. */
|
|
85
98
|
size: { width: number; height: number; };
|
|
99
|
+
/** The original X offset before sticky adjustments. */
|
|
86
100
|
originalX: number;
|
|
101
|
+
/** The original Y offset before sticky adjustments. */
|
|
87
102
|
originalY: number;
|
|
103
|
+
/** Whether this item is configured to be sticky. */
|
|
88
104
|
isSticky?: boolean;
|
|
105
|
+
/** Whether this item is currently stuck at the threshold. */
|
|
89
106
|
isStickyActive?: boolean;
|
|
107
|
+
/** The offset applied for the sticky pushing effect. */
|
|
90
108
|
stickyOffset: { x: number; y: number; };
|
|
91
109
|
}
|
|
92
110
|
|
|
111
|
+
/** Comprehensive state of the virtual scroll system. */
|
|
93
112
|
export interface ScrollDetails<T = unknown> {
|
|
113
|
+
/** List of items currently rendered. */
|
|
94
114
|
items: RenderedItem<T>[];
|
|
115
|
+
/** Index of the first item partially or fully visible in the viewport. */
|
|
95
116
|
currentIndex: number;
|
|
117
|
+
/** Index of the first column partially or fully visible. */
|
|
96
118
|
currentColIndex: number;
|
|
119
|
+
/** Current scroll position relative to content start. */
|
|
97
120
|
scrollOffset: { x: number; y: number; };
|
|
121
|
+
/** Dimensions of the visible viewport. */
|
|
98
122
|
viewportSize: { width: number; height: number; };
|
|
123
|
+
/** Total calculated size of all items and gaps. */
|
|
99
124
|
totalSize: { width: number; height: number; };
|
|
125
|
+
/** Whether the container is currently being scrolled. */
|
|
100
126
|
isScrolling: boolean;
|
|
127
|
+
/** Whether the current scroll was initiated by a method call. */
|
|
101
128
|
isProgrammaticScroll: boolean;
|
|
102
|
-
/** Range of items currently being rendered */
|
|
129
|
+
/** Range of items currently being rendered. */
|
|
103
130
|
range: { start: number; end: number; };
|
|
104
|
-
/** Range of columns currently being rendered (for grid mode) */
|
|
131
|
+
/** Range of columns currently being rendered (for grid mode). */
|
|
105
132
|
columnRange: { start: number; end: number; padStart: number; padEnd: number; };
|
|
106
133
|
}
|
|
107
134
|
|
|
@@ -161,7 +188,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
161
188
|
(typeof props.value.itemSize === 'number' && props.value.itemSize > 0) ? props.value.itemSize : null,
|
|
162
189
|
);
|
|
163
190
|
|
|
164
|
-
const defaultSize = computed(() =>
|
|
191
|
+
const defaultSize = computed(() => props.value.defaultItemSize || fixedItemSize.value || DEFAULT_ITEM_SIZE);
|
|
165
192
|
|
|
166
193
|
const sortedStickyIndices = computed(() =>
|
|
167
194
|
[ ...(props.value.stickyIndices || []) ].sort((a, b) => a - b),
|
|
@@ -178,28 +205,42 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
178
205
|
if (!isHydrated.value && props.value.ssrRange && !isMounted.value) {
|
|
179
206
|
const { start = 0, end = 0, colStart = 0, colEnd = 0 } = props.value.ssrRange;
|
|
180
207
|
const colCount = props.value.columnCount || 0;
|
|
181
|
-
if (props.value.direction === 'both'
|
|
182
|
-
|
|
208
|
+
if (props.value.direction === 'both') {
|
|
209
|
+
if (colCount <= 0) {
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
const effectiveColEnd = colEnd || colCount;
|
|
213
|
+
const total = columnSizes.query(effectiveColEnd) - columnSizes.query(colStart);
|
|
214
|
+
return Math.max(0, total - (effectiveColEnd > colStart ? (props.value.columnGap || 0) : 0));
|
|
183
215
|
}
|
|
184
216
|
/* v8 ignore else -- @preserve */
|
|
185
217
|
if (props.value.direction === 'horizontal') {
|
|
186
218
|
if (fixedItemSize.value !== null) {
|
|
187
|
-
|
|
219
|
+
const len = end - start;
|
|
220
|
+
return Math.max(0, len * (fixedItemSize.value + (props.value.columnGap || 0)) - (len > 0 ? (props.value.columnGap || 0) : 0));
|
|
188
221
|
}
|
|
189
|
-
|
|
222
|
+
const total = itemSizesX.query(end) - itemSizesX.query(start);
|
|
223
|
+
return Math.max(0, total - (end > start ? (props.value.columnGap || 0) : 0));
|
|
190
224
|
}
|
|
191
225
|
}
|
|
192
226
|
|
|
193
|
-
if (props.value.direction === 'both'
|
|
194
|
-
|
|
227
|
+
if (props.value.direction === 'both') {
|
|
228
|
+
const colCount = props.value.columnCount || 0;
|
|
229
|
+
if (colCount <= 0) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
const total = columnSizes.query(colCount);
|
|
233
|
+
return Math.max(0, total - (props.value.columnGap || 0));
|
|
195
234
|
}
|
|
196
235
|
if (props.value.direction === 'vertical') {
|
|
197
236
|
return 0;
|
|
198
237
|
}
|
|
199
238
|
if (fixedItemSize.value !== null) {
|
|
200
|
-
|
|
239
|
+
const len = props.value.items.length;
|
|
240
|
+
return Math.max(0, len * (fixedItemSize.value + (props.value.columnGap || 0)) - (len > 0 ? (props.value.columnGap || 0) : 0));
|
|
201
241
|
}
|
|
202
|
-
|
|
242
|
+
const total = itemSizesX.query(props.value.items.length);
|
|
243
|
+
return Math.max(0, total - (props.value.items.length > 0 ? (props.value.columnGap || 0) : 0));
|
|
203
244
|
});
|
|
204
245
|
|
|
205
246
|
/**
|
|
@@ -214,9 +255,11 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
214
255
|
/* v8 ignore else -- @preserve */
|
|
215
256
|
if (props.value.direction === 'vertical' || props.value.direction === 'both') {
|
|
216
257
|
if (fixedItemSize.value !== null) {
|
|
217
|
-
|
|
258
|
+
const len = end - start;
|
|
259
|
+
return Math.max(0, len * (fixedItemSize.value + (props.value.gap || 0)) - (len > 0 ? (props.value.gap || 0) : 0));
|
|
218
260
|
}
|
|
219
|
-
|
|
261
|
+
const total = itemSizesY.query(end) - itemSizesY.query(start);
|
|
262
|
+
return Math.max(0, total - (end > start ? (props.value.gap || 0) : 0));
|
|
220
263
|
}
|
|
221
264
|
}
|
|
222
265
|
|
|
@@ -224,9 +267,11 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
224
267
|
return 0;
|
|
225
268
|
}
|
|
226
269
|
if (fixedItemSize.value !== null) {
|
|
227
|
-
|
|
270
|
+
const len = props.value.items.length;
|
|
271
|
+
return Math.max(0, len * (fixedItemSize.value + (props.value.gap || 0)) - (len > 0 ? (props.value.gap || 0) : 0));
|
|
228
272
|
}
|
|
229
|
-
|
|
273
|
+
const total = itemSizesY.query(props.value.items.length);
|
|
274
|
+
return Math.max(0, total - (props.value.items.length > 0 ? (props.value.gap || 0) : 0));
|
|
230
275
|
});
|
|
231
276
|
|
|
232
277
|
const relativeScrollX = computed(() => {
|
|
@@ -250,8 +295,10 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
250
295
|
return cw;
|
|
251
296
|
}
|
|
252
297
|
if (Array.isArray(cw) && cw.length > 0) {
|
|
253
|
-
|
|
298
|
+
const val = cw[ index % cw.length ];
|
|
299
|
+
return (val != null && val > 0) ? val : (props.value.defaultColumnWidth || DEFAULT_COLUMN_WIDTH);
|
|
254
300
|
}
|
|
301
|
+
/* v8 ignore else -- @preserve */
|
|
255
302
|
if (typeof cw === 'function') {
|
|
256
303
|
return cw(index);
|
|
257
304
|
}
|
|
@@ -353,7 +400,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
353
400
|
itemWidth = fixedSize !== null ? fixedSize : itemSizesX.get(colIndex) - columnGap;
|
|
354
401
|
} else {
|
|
355
402
|
targetX = columnSizes.query(colIndex);
|
|
356
|
-
itemWidth = columnSizes.get(colIndex);
|
|
403
|
+
itemWidth = columnSizes.get(colIndex) - columnGap;
|
|
357
404
|
}
|
|
358
405
|
|
|
359
406
|
// Apply X Alignment
|
|
@@ -648,15 +695,17 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
648
695
|
for (let i = 0; i < colCount; i++) {
|
|
649
696
|
const width = getColumnWidth(i);
|
|
650
697
|
const currentW = columnSizes.get(i);
|
|
698
|
+
const isMeasured = measuredColumns[ i ] === 1;
|
|
651
699
|
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
if (!isDynamicColumnWidth.value || currentW === 0) {
|
|
700
|
+
// If fixed/function, or if dynamic but not measured yet
|
|
701
|
+
if (!isDynamicColumnWidth.value || !isMeasured || currentW === 0) {
|
|
655
702
|
const targetW = width + columnGap;
|
|
656
|
-
/* v8 ignore else -- @preserve */
|
|
657
703
|
if (Math.abs(currentW - targetW) > 0.5) {
|
|
658
704
|
columnSizes.set(i, targetW);
|
|
705
|
+
measuredColumns[ i ] = isDynamicColumnWidth.value ? 0 : 1;
|
|
659
706
|
colNeedsRebuild = true;
|
|
707
|
+
} else if (!isDynamicColumnWidth.value) {
|
|
708
|
+
measuredColumns[ i ] = 1;
|
|
660
709
|
}
|
|
661
710
|
}
|
|
662
711
|
}
|
|
@@ -674,11 +723,6 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
674
723
|
const currentX = itemSizesX.get(i);
|
|
675
724
|
const currentY = itemSizesY.get(i);
|
|
676
725
|
|
|
677
|
-
// If it's dynamic and already has a measurement, keep it.
|
|
678
|
-
if (isDynamicItemSize.value && (currentX > 0 || currentY > 0)) {
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
726
|
const size = typeof props.value.itemSize === 'function'
|
|
683
727
|
? props.value.itemSize(item as T, i)
|
|
684
728
|
: defaultSize.value;
|
|
@@ -690,12 +734,41 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
690
734
|
const targetX = isHorizontal ? size + columnGap : 0;
|
|
691
735
|
const targetY = (isVertical || isBoth) ? size + gap : 0;
|
|
692
736
|
|
|
693
|
-
|
|
694
|
-
|
|
737
|
+
const isMeasuredX = measuredItemsX[ i ] === 1;
|
|
738
|
+
const isMeasuredY = measuredItemsY[ i ] === 1;
|
|
739
|
+
|
|
740
|
+
// Logic for X
|
|
741
|
+
if (isHorizontal) {
|
|
742
|
+
// If fixed/function, or if dynamic but not measured yet
|
|
743
|
+
if (!isDynamicItemSize.value || !isMeasuredX || currentX === 0) {
|
|
744
|
+
if (Math.abs(currentX - targetX) > 0.5) {
|
|
745
|
+
itemSizesX.set(i, targetX);
|
|
746
|
+
measuredItemsX[ i ] = isDynamicItemSize.value ? 0 : 1;
|
|
747
|
+
itemsNeedRebuild = true;
|
|
748
|
+
} else if (!isDynamicItemSize.value) {
|
|
749
|
+
measuredItemsX[ i ] = 1;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
} else if (currentX !== 0) {
|
|
753
|
+
itemSizesX.set(i, 0);
|
|
754
|
+
measuredItemsX[ i ] = 0;
|
|
695
755
|
itemsNeedRebuild = true;
|
|
696
756
|
}
|
|
697
|
-
|
|
698
|
-
|
|
757
|
+
|
|
758
|
+
// Logic for Y
|
|
759
|
+
if (isVertical || isBoth) {
|
|
760
|
+
if (!isDynamicItemSize.value || !isMeasuredY || currentY === 0) {
|
|
761
|
+
if (Math.abs(currentY - targetY) > 0.5) {
|
|
762
|
+
itemSizesY.set(i, targetY);
|
|
763
|
+
measuredItemsY[ i ] = isDynamicItemSize.value ? 0 : 1;
|
|
764
|
+
itemsNeedRebuild = true;
|
|
765
|
+
} else if (!isDynamicItemSize.value) {
|
|
766
|
+
measuredItemsY[ i ] = 1;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
} else if (currentY !== 0) {
|
|
770
|
+
itemSizesY.set(i, 0);
|
|
771
|
+
measuredItemsY[ i ] = 0;
|
|
699
772
|
itemsNeedRebuild = true;
|
|
700
773
|
}
|
|
701
774
|
}
|
|
@@ -741,12 +814,15 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
741
814
|
};
|
|
742
815
|
|
|
743
816
|
watch([
|
|
744
|
-
() => props.value.items
|
|
817
|
+
() => props.value.items,
|
|
818
|
+
() => props.value.direction,
|
|
745
819
|
() => props.value.columnCount,
|
|
746
820
|
() => props.value.columnWidth,
|
|
747
821
|
() => props.value.itemSize,
|
|
748
822
|
() => props.value.gap,
|
|
749
823
|
() => props.value.columnGap,
|
|
824
|
+
() => props.value.defaultItemSize,
|
|
825
|
+
() => props.value.defaultColumnWidth,
|
|
750
826
|
], initializeSizes, { immediate: true });
|
|
751
827
|
|
|
752
828
|
watch(() => [ props.value.container, props.value.hostElement ], () => {
|
|
@@ -1047,10 +1123,12 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
1047
1123
|
const safeStart = Math.max(0, start - colBuffer);
|
|
1048
1124
|
const safeEnd = Math.min(totalCols, end + colBuffer);
|
|
1049
1125
|
|
|
1126
|
+
const padStart = columnSizes.query(safeStart);
|
|
1127
|
+
|
|
1050
1128
|
return {
|
|
1051
1129
|
start: safeStart,
|
|
1052
1130
|
end: safeEnd,
|
|
1053
|
-
padStart
|
|
1131
|
+
padStart,
|
|
1054
1132
|
padEnd: columnSizes.query(totalCols) - columnSizes.query(safeEnd),
|
|
1055
1133
|
};
|
|
1056
1134
|
});
|
|
@@ -1138,11 +1216,16 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
1138
1216
|
const columnGap = props.value.columnGap || 0;
|
|
1139
1217
|
|
|
1140
1218
|
for (const { index, inlineSize, blockSize, element } of updates) {
|
|
1141
|
-
|
|
1219
|
+
const isMeasurable = isDynamicItemSize.value || typeof props.value.itemSize === 'function';
|
|
1220
|
+
if (isMeasurable && index >= 0) {
|
|
1142
1221
|
if (props.value.direction === 'horizontal') {
|
|
1143
1222
|
const oldWidth = itemSizesX.get(index);
|
|
1144
1223
|
const targetWidth = inlineSize + columnGap;
|
|
1145
|
-
|
|
1224
|
+
// Apply if:
|
|
1225
|
+
// 1. It's the first measurement (measuredItemsX[index] is 0)
|
|
1226
|
+
// 2. It's a significant change (> 0.5px)
|
|
1227
|
+
/* v8 ignore else -- @preserve */
|
|
1228
|
+
if (!measuredItemsX[ index ] || Math.abs(targetWidth - oldWidth) > 0.5) {
|
|
1146
1229
|
itemSizesX.update(index, targetWidth - oldWidth);
|
|
1147
1230
|
measuredItemsX[ index ] = 1;
|
|
1148
1231
|
needUpdate = true;
|
|
@@ -1151,14 +1234,16 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
1151
1234
|
if (props.value.direction === 'vertical' || props.value.direction === 'both') {
|
|
1152
1235
|
const oldHeight = itemSizesY.get(index);
|
|
1153
1236
|
const targetHeight = blockSize + gap;
|
|
1154
|
-
|
|
1237
|
+
|
|
1155
1238
|
if (props.value.direction === 'both') {
|
|
1156
|
-
|
|
1239
|
+
// For grid, we should be careful with decreases because a row height is the max of all its cells.
|
|
1240
|
+
/* v8 ignore else -- @preserve */
|
|
1241
|
+
if (!measuredItemsY[ index ] || Math.abs(targetHeight - oldHeight) > 0.5) {
|
|
1157
1242
|
itemSizesY.update(index, targetHeight - oldHeight);
|
|
1158
1243
|
measuredItemsY[ index ] = 1;
|
|
1159
1244
|
needUpdate = true;
|
|
1160
1245
|
}
|
|
1161
|
-
} else if (Math.abs(
|
|
1246
|
+
} else if (!measuredItemsY[ index ] || Math.abs(targetHeight - oldHeight) > 0.5) {
|
|
1162
1247
|
itemSizesY.update(index, targetHeight - oldHeight);
|
|
1163
1248
|
measuredItemsY[ index ] = 1;
|
|
1164
1249
|
needUpdate = true;
|
|
@@ -1167,11 +1252,12 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
1167
1252
|
}
|
|
1168
1253
|
|
|
1169
1254
|
// Dynamic column width measurement
|
|
1255
|
+
const isColMeasurable = isDynamicColumnWidth.value || typeof props.value.columnWidth === 'function';
|
|
1170
1256
|
if (
|
|
1171
1257
|
props.value.direction === 'both'
|
|
1172
1258
|
&& element
|
|
1173
1259
|
&& props.value.columnCount
|
|
1174
|
-
&&
|
|
1260
|
+
&& isColMeasurable
|
|
1175
1261
|
) {
|
|
1176
1262
|
const cells = element.dataset.colIndex !== undefined
|
|
1177
1263
|
? [ element ]
|
|
@@ -1185,8 +1271,7 @@ export function useVirtualScroll<T = unknown>(props: Ref<VirtualScrollProps<T>>)
|
|
|
1185
1271
|
const w = child.offsetWidth;
|
|
1186
1272
|
const oldW = columnSizes.get(colIndex);
|
|
1187
1273
|
const targetW = w + columnGap;
|
|
1188
|
-
|
|
1189
|
-
if (targetW > oldW || !measuredColumns[ colIndex ]) {
|
|
1274
|
+
if (Math.abs(oldW - targetW) > 0.5) {
|
|
1190
1275
|
columnSizes.update(colIndex, targetW - oldW);
|
|
1191
1276
|
measuredColumns[ colIndex ] = 1;
|
|
1192
1277
|
needUpdate = true;
|
package/dist/index.css
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
.virtual-scroll-container[data-v-105a0cd5]{outline-offset:1px;block-size:100%;inline-size:100%;position:relative}.virtual-scroll-container[data-v-105a0cd5]:not(.virtual-scroll--window){overscroll-behavior:contain;overflow:auto}.virtual-scroll-container.virtual-scroll--table[data-v-105a0cd5]{display:block}.virtual-scroll--horizontal[data-v-105a0cd5]{white-space:nowrap}.virtual-scroll-wrapper[data-v-105a0cd5]{contain:layout;position:relative}:where(.virtual-scroll--hydrated>.virtual-scroll-wrapper>.virtual-scroll-item[data-v-105a0cd5]){position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-item[data-v-105a0cd5]{box-sizing:border-box;will-change:transform}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-105a0cd5]{background-color:#ff00000d;outline:1px dashed #ff000080}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-105a0cd5]:where(:hover){z-index:100;background-color:#ff00001a}.virtual-scroll-debug-info[data-v-105a0cd5]{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-105a0cd5]{pointer-events:none}.virtual-scroll-header[data-v-105a0cd5],.virtual-scroll-footer[data-v-105a0cd5]{z-index:20;position:relative}.virtual-scroll--sticky[data-v-105a0cd5]{position:sticky}.virtual-scroll--sticky[data-v-105a0cd5]: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-105a0cd5]: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-105a0cd5]:where(.virtual-scroll-item){z-index:10}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-105a0cd5]{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-105a0cd5]>tr{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-105a0cd5]>tr>:is(td,th){align-items:center;display:inline-block}
|
|
2
|
-
/*$vite$:1*/
|