@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.
@@ -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 = 50;
10
- export const DEFAULT_COLUMN_WIDTH = 150;
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 of widths for alternating columns */
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 (e.g. for sticky headers) */
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 (e.g. showing offsets) */
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(() => fixedItemSize.value || props.value.defaultItemSize || DEFAULT_ITEM_SIZE);
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' && colCount > 0) {
182
- return columnSizes.query(colEnd || colCount) - columnSizes.query(colStart);
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
- return (end - start) * (fixedItemSize.value + (props.value.columnGap || 0));
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
- return itemSizesX.query(end) - itemSizesX.query(start);
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' && props.value.columnCount) {
194
- return columnSizes.query(props.value.columnCount);
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
- return props.value.items.length * (fixedItemSize.value + (props.value.columnGap || 0));
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
- return itemSizesX.query(props.value.items.length);
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
- return (end - start) * (fixedItemSize.value + (props.value.gap || 0));
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
- return itemSizesY.query(end) - itemSizesY.query(start);
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
- return props.value.items.length * (fixedItemSize.value + (props.value.gap || 0));
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
- return itemSizesY.query(props.value.items.length);
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
- return cw[ index % cw.length ] || DEFAULT_COLUMN_WIDTH;
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
- // Only initialize from getColumnWidth if it's not dynamic,
653
- // OR if it's dynamic but we don't have a measurement yet.
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
- if (Math.abs(currentX - targetX) > 0.5) {
694
- itemSizesX.set(i, targetX);
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
- if (Math.abs(currentY - targetY) > 0.5) {
698
- itemSizesY.set(i, targetY);
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.length,
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: columnSizes.query(safeStart),
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
- if (isDynamicItemSize.value) {
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
- if (Math.abs(oldWidth - targetWidth) > 0.5 && (targetWidth > oldWidth || !measuredItemsX[ index ])) {
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
- // For grid, keep max height encountered to avoid shrinking on horizontal scroll
1237
+
1155
1238
  if (props.value.direction === 'both') {
1156
- if (targetHeight > oldHeight || !measuredItemsY[ index ]) {
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(oldHeight - targetHeight) > 0.5 && (targetHeight > oldHeight || !measuredItemsY[ index ])) {
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
- && isDynamicColumnWidth.value
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
- /* v8 ignore else -- @preserve */
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*/