@pdanpdan/virtual-scroll 0.5.0 → 0.6.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 +73 -174
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +192 -348
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1691 -2198
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +2 -1
- package/package.json +4 -2
- package/src/components/VirtualScroll.test.ts +36 -0
- package/src/components/VirtualScroll.vue +25 -55
- package/src/composables/useVirtualScroll.ts +81 -145
- package/src/composables/useVirtualScrollbar.test.ts +14 -14
- package/src/composables/useVirtualScrollbar.ts +5 -0
- package/src/index.ts +7 -0
- package/src/types.ts +132 -170
- package/src/utils/scroll.test.ts +64 -10
- package/src/utils/scroll.ts +31 -0
- package/src/utils/virtual-scroll-logic.test.ts +48 -31
- package/src/utils/virtual-scroll-logic.ts +82 -49
package/src/types.ts
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
/** Default fallback size for items (VU). */
|
|
2
|
+
export const DEFAULT_ITEM_SIZE = 40;
|
|
3
|
+
/** Default fallback width for columns (VU). */
|
|
4
|
+
export const DEFAULT_COLUMN_WIDTH = 100;
|
|
5
|
+
/** Default number of items to render outside the viewport. */
|
|
6
|
+
export const DEFAULT_BUFFER = 5;
|
|
7
|
+
|
|
8
|
+
/** Represents a point in 2D space. */
|
|
9
|
+
export interface Point {
|
|
10
|
+
/** X coordinate. */
|
|
11
|
+
x: number;
|
|
12
|
+
/** Y coordinate. */
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Represents dimensions in 2D space. */
|
|
17
|
+
export interface Size {
|
|
18
|
+
/** Width dimension. */
|
|
19
|
+
width: number;
|
|
20
|
+
/** Height dimension. */
|
|
21
|
+
height: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Initial empty state for scroll details. */
|
|
25
|
+
export const EMPTY_SCROLL_DETAILS: ScrollDetails<unknown> = {
|
|
26
|
+
items: [],
|
|
27
|
+
currentIndex: 0,
|
|
28
|
+
currentColIndex: 0,
|
|
29
|
+
currentEndIndex: 0,
|
|
30
|
+
currentEndColIndex: 0,
|
|
31
|
+
scrollOffset: { x: 0, y: 0 },
|
|
32
|
+
displayScrollOffset: { x: 0, y: 0 },
|
|
33
|
+
viewportSize: { width: 0, height: 0 },
|
|
34
|
+
displayViewportSize: { width: 0, height: 0 },
|
|
35
|
+
totalSize: { width: 0, height: 0 },
|
|
36
|
+
isScrolling: false,
|
|
37
|
+
isProgrammaticScroll: false,
|
|
38
|
+
range: { start: 0, end: 0 },
|
|
39
|
+
columnRange: { start: 0, end: 0, padStart: 0, padEnd: 0 },
|
|
40
|
+
};
|
|
41
|
+
|
|
1
42
|
/**
|
|
2
43
|
* The direction of the virtual scroll.
|
|
3
44
|
* - 'vertical': Single-column vertical scrolling.
|
|
@@ -55,19 +96,9 @@ export interface RenderedItem<T = unknown> {
|
|
|
55
96
|
/** The 0-based index of the item in the original array. */
|
|
56
97
|
index: number;
|
|
57
98
|
/** The calculated pixel offset relative to the items wrapper in display pixels (DU). */
|
|
58
|
-
offset:
|
|
59
|
-
/** Horizontal offset (left) in DU. */
|
|
60
|
-
x: number;
|
|
61
|
-
/** Vertical offset (top) in DU. */
|
|
62
|
-
y: number;
|
|
63
|
-
};
|
|
99
|
+
offset: Point;
|
|
64
100
|
/** The current measured or estimated size of the item in virtual units (VU). */
|
|
65
|
-
size:
|
|
66
|
-
/** Pixel width in VU. */
|
|
67
|
-
width: number;
|
|
68
|
-
/** Pixel height in VU. */
|
|
69
|
-
height: number;
|
|
70
|
-
};
|
|
101
|
+
size: Size;
|
|
71
102
|
/** The original horizontal pixel offset before any sticky adjustments in VU. */
|
|
72
103
|
originalX: number;
|
|
73
104
|
/** The original vertical pixel offset before any sticky adjustments in VU. */
|
|
@@ -76,13 +107,12 @@ export interface RenderedItem<T = unknown> {
|
|
|
76
107
|
isSticky?: boolean;
|
|
77
108
|
/** Whether this item is currently in a stuck state at the viewport edge. */
|
|
78
109
|
isStickyActive?: boolean;
|
|
110
|
+
/** Whether this item is currently in a stuck state at the horizontal viewport edge. */
|
|
111
|
+
isStickyActiveX?: boolean;
|
|
112
|
+
/** Whether this item is currently in a stuck state at the vertical viewport edge. */
|
|
113
|
+
isStickyActiveY?: boolean;
|
|
79
114
|
/** The relative translation applied to the item for the sticky pushing effect in DU. */
|
|
80
|
-
stickyOffset:
|
|
81
|
-
/** Horizontal translation in DU. */
|
|
82
|
-
x: number;
|
|
83
|
-
/** Vertical translation in DU. */
|
|
84
|
-
y: number;
|
|
85
|
-
};
|
|
115
|
+
stickyOffset: Point;
|
|
86
116
|
}
|
|
87
117
|
|
|
88
118
|
/** Information about the currently visible range of columns and their paddings. */
|
|
@@ -110,40 +140,15 @@ export interface ScrollDetails<T = unknown> {
|
|
|
110
140
|
/** Index of the last column visible before any sticky end column in the viewport (grid mode). */
|
|
111
141
|
currentEndColIndex: number;
|
|
112
142
|
/** Current relative pixel scroll position from the content start in VU. */
|
|
113
|
-
scrollOffset:
|
|
114
|
-
/** Horizontal position (X) in VU. */
|
|
115
|
-
x: number;
|
|
116
|
-
/** Vertical position (Y) in VU. */
|
|
117
|
-
y: number;
|
|
118
|
-
};
|
|
143
|
+
scrollOffset: Point;
|
|
119
144
|
/** Current display pixel scroll position (before scaling) in DU. */
|
|
120
|
-
displayScrollOffset:
|
|
121
|
-
/** Horizontal position (X) in DU. */
|
|
122
|
-
x: number;
|
|
123
|
-
/** Vertical position (Y) in DU. */
|
|
124
|
-
y: number;
|
|
125
|
-
};
|
|
145
|
+
displayScrollOffset: Point;
|
|
126
146
|
/** Current dimensions of the visible viewport area in VU. */
|
|
127
|
-
viewportSize:
|
|
128
|
-
/** Pixel width in VU. */
|
|
129
|
-
width: number;
|
|
130
|
-
/** Pixel height in VU. */
|
|
131
|
-
height: number;
|
|
132
|
-
};
|
|
147
|
+
viewportSize: Size;
|
|
133
148
|
/** Current dimensions of the visible viewport area in display pixels (DU). */
|
|
134
|
-
displayViewportSize:
|
|
135
|
-
/** Pixel width in DU. */
|
|
136
|
-
width: number;
|
|
137
|
-
/** Pixel height in DU. */
|
|
138
|
-
height: number;
|
|
139
|
-
};
|
|
149
|
+
displayViewportSize: Size;
|
|
140
150
|
/** Total calculated or estimated size of all items and gaps in VU. */
|
|
141
|
-
totalSize:
|
|
142
|
-
/** Total pixel width in VU. */
|
|
143
|
-
width: number;
|
|
144
|
-
/** Total pixel height in VU. */
|
|
145
|
-
height: number;
|
|
146
|
-
};
|
|
151
|
+
totalSize: Size;
|
|
147
152
|
/** Whether the container is currently being scrolled by the user or an animation. */
|
|
148
153
|
isScrolling: boolean;
|
|
149
154
|
/** Whether the current scroll operation was initiated programmatically. */
|
|
@@ -159,18 +164,34 @@ export interface ScrollDetails<T = unknown> {
|
|
|
159
164
|
columnRange: ColumnRange;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Configuration for Server-Side Rendering.
|
|
169
|
+
* Defines which items are rendered statically on the server.
|
|
170
|
+
*/
|
|
171
|
+
export interface SSRRange {
|
|
172
|
+
/** First row index (for list or grid). */
|
|
173
|
+
start: number;
|
|
174
|
+
/** Exclusive last row index (for list or grid). */
|
|
175
|
+
end: number;
|
|
176
|
+
/** First column index (for grid mode). */
|
|
177
|
+
colStart?: number;
|
|
178
|
+
/** Exclusive last column index (for grid mode). */
|
|
179
|
+
colEnd?: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Pixel padding configuration in display pixels (DU). */
|
|
183
|
+
export type PaddingValue = number | { x?: number; y?: number; };
|
|
184
|
+
|
|
185
|
+
/** Base configuration properties shared between the component and the composable. */
|
|
186
|
+
export interface VirtualScrollBaseProps<T = unknown> {
|
|
187
|
+
/** Array of data items to virtualize. */
|
|
167
188
|
items: T[];
|
|
168
189
|
|
|
169
190
|
/**
|
|
170
191
|
* Fixed size of each item in virtual units (VU) or a function that returns the size of an item.
|
|
171
192
|
* Pass `0`, `null` or `undefined` for automatic dynamic size detection via `ResizeObserver`.
|
|
172
193
|
*/
|
|
173
|
-
itemSize?: number | ((item: T, index: number) => number) | undefined;
|
|
194
|
+
itemSize?: number | ((item: T, index: number) => number) | null | undefined;
|
|
174
195
|
|
|
175
196
|
/**
|
|
176
197
|
* Direction of the virtual scroll.
|
|
@@ -196,32 +217,11 @@ export interface VirtualScrollProps<T = unknown> {
|
|
|
196
217
|
*/
|
|
197
218
|
container?: HTMLElement | Window | null | undefined;
|
|
198
219
|
|
|
199
|
-
/**
|
|
200
|
-
* The host element that directly wraps the absolute-positioned items.
|
|
201
|
-
* Used for calculating relative offsets in display pixels (DU).
|
|
202
|
-
*/
|
|
203
|
-
hostElement?: HTMLElement | null | undefined;
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* The root element of the VirtualScroll component.
|
|
207
|
-
* Used for calculating relative offsets in display pixels (DU).
|
|
208
|
-
*/
|
|
209
|
-
hostRef?: HTMLElement | null | undefined;
|
|
210
|
-
|
|
211
220
|
/**
|
|
212
221
|
* Configuration for Server-Side Rendering.
|
|
213
222
|
* Defines which items are rendered statically on the server.
|
|
214
223
|
*/
|
|
215
|
-
ssrRange?:
|
|
216
|
-
/** First row index. */
|
|
217
|
-
start: number;
|
|
218
|
-
/** Exclusive last row index. */
|
|
219
|
-
end: number;
|
|
220
|
-
/** First column index (grid mode). */
|
|
221
|
-
colStart?: number;
|
|
222
|
-
/** Exclusive last column index (grid mode). */
|
|
223
|
-
colEnd?: number;
|
|
224
|
-
} | undefined;
|
|
224
|
+
ssrRange?: SSRRange | undefined;
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
227
|
* Number of columns for bidirectional grid scrolling.
|
|
@@ -232,29 +232,17 @@ export interface VirtualScrollProps<T = unknown> {
|
|
|
232
232
|
* Fixed width of columns in VU, an array of widths, or a function returning widths.
|
|
233
233
|
* Pass `0`, `null` or `undefined` for dynamic column detection.
|
|
234
234
|
*/
|
|
235
|
-
columnWidth?: number | number[] | ((index: number) => number) | undefined;
|
|
235
|
+
columnWidth?: number | number[] | ((index: number) => number) | null | undefined;
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
238
|
* Pixel padding at the start of the scroll container in display pixels (DU).
|
|
239
239
|
*/
|
|
240
|
-
scrollPaddingStart?:
|
|
240
|
+
scrollPaddingStart?: PaddingValue | undefined;
|
|
241
241
|
|
|
242
242
|
/**
|
|
243
243
|
* Pixel padding at the end of the scroll container in DU.
|
|
244
244
|
*/
|
|
245
|
-
scrollPaddingEnd?:
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Size of sticky elements at the start of the viewport (top or left) in DU.
|
|
249
|
-
* Used to adjust the visible range and item positioning without increasing content size.
|
|
250
|
-
*/
|
|
251
|
-
stickyStart?: number | { x?: number; y?: number; } | undefined;
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Size of sticky elements at the end of the viewport (bottom or right) in DU.
|
|
255
|
-
* Used to adjust the visible range without increasing content size.
|
|
256
|
-
*/
|
|
257
|
-
stickyEnd?: number | { x?: number; y?: number; } | undefined;
|
|
245
|
+
scrollPaddingEnd?: PaddingValue | undefined;
|
|
258
246
|
|
|
259
247
|
/**
|
|
260
248
|
* Gap between items in virtual units (VU).
|
|
@@ -273,16 +261,6 @@ export interface VirtualScrollProps<T = unknown> {
|
|
|
273
261
|
*/
|
|
274
262
|
stickyIndices?: number[] | undefined;
|
|
275
263
|
|
|
276
|
-
/**
|
|
277
|
-
* Extra padding (display pixels - DU) at the start of the flow (e.g. non-sticky header).
|
|
278
|
-
*/
|
|
279
|
-
flowPaddingStart?: number | { x?: number; y?: number; } | undefined;
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Extra padding (DU) at the end of the flow (e.g. non-sticky footer).
|
|
283
|
-
*/
|
|
284
|
-
flowPaddingEnd?: number | { x?: number; y?: number; } | undefined;
|
|
285
|
-
|
|
286
264
|
/**
|
|
287
265
|
* Threshold distance from the end in display pixels (DU) to emit the 'load' event.
|
|
288
266
|
* @default 200
|
|
@@ -326,6 +304,43 @@ export interface VirtualScrollProps<T = unknown> {
|
|
|
326
304
|
debug?: boolean | undefined;
|
|
327
305
|
}
|
|
328
306
|
|
|
307
|
+
/** Configuration properties for the `useVirtualScroll` composable. */
|
|
308
|
+
export interface VirtualScrollProps<T = unknown> extends VirtualScrollBaseProps<T> {
|
|
309
|
+
/**
|
|
310
|
+
* The host element that directly wraps the absolute-positioned items.
|
|
311
|
+
* Used for calculating relative offsets in display pixels (DU).
|
|
312
|
+
*/
|
|
313
|
+
hostElement?: HTMLElement | null | undefined;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The root element of the VirtualScroll component.
|
|
317
|
+
* Used for calculating relative offsets in display pixels (DU).
|
|
318
|
+
*/
|
|
319
|
+
hostRef?: HTMLElement | null | undefined;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Size of sticky elements at the start of the viewport (top or left) in DU.
|
|
323
|
+
* Used to adjust the visible range and item positioning without increasing content size.
|
|
324
|
+
*/
|
|
325
|
+
stickyStart?: PaddingValue | undefined;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Size of sticky elements at the end of the viewport (bottom or right) in DU.
|
|
329
|
+
* Used to adjust the visible range without increasing content size.
|
|
330
|
+
*/
|
|
331
|
+
stickyEnd?: PaddingValue | undefined;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Extra padding (display pixels - DU) at the start of the flow (e.g. non-sticky header).
|
|
335
|
+
*/
|
|
336
|
+
flowPaddingStart?: PaddingValue | undefined;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Extra padding (DU) at the end of the flow (e.g. non-sticky footer).
|
|
340
|
+
*/
|
|
341
|
+
flowPaddingEnd?: PaddingValue | undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
329
344
|
/** Help provide axis specific information to the scrollbar. */
|
|
330
345
|
export type ScrollAxis = 'vertical' | 'horizontal';
|
|
331
346
|
|
|
@@ -374,6 +389,8 @@ export interface VirtualScrollbarProps {
|
|
|
374
389
|
|
|
375
390
|
/** Properties passed to the 'scrollbar' scoped slot. */
|
|
376
391
|
export interface ScrollbarSlotProps {
|
|
392
|
+
/** The axis for this scrollbar. */
|
|
393
|
+
axis: ScrollAxis;
|
|
377
394
|
/** Current scroll position as a percentage (0 to 1). */
|
|
378
395
|
positionPercent: number;
|
|
379
396
|
/** Viewport size as a percentage of total size (0 to 1). */
|
|
@@ -412,16 +429,7 @@ export interface ItemSlotProps<T = unknown> {
|
|
|
412
429
|
/** The 0-based index of the item. */
|
|
413
430
|
index: number;
|
|
414
431
|
/** Information about the currently visible range of columns. */
|
|
415
|
-
columnRange:
|
|
416
|
-
/** First rendered column. */
|
|
417
|
-
start: number;
|
|
418
|
-
/** Last rendered column (exclusive). */
|
|
419
|
-
end: number;
|
|
420
|
-
/** Pixel space before first column. */
|
|
421
|
-
padStart: number;
|
|
422
|
-
/** Pixel space after last column. */
|
|
423
|
-
padEnd: number;
|
|
424
|
-
};
|
|
432
|
+
columnRange: ColumnRange;
|
|
425
433
|
/** Helper to get the current calculated width of any column index. */
|
|
426
434
|
getColumnWidth: (index: number) => number;
|
|
427
435
|
/** Vertical gap between items. */
|
|
@@ -432,73 +440,31 @@ export interface ItemSlotProps<T = unknown> {
|
|
|
432
440
|
isSticky?: boolean | undefined;
|
|
433
441
|
/** Whether this item is currently in a sticky state at the edge. */
|
|
434
442
|
isStickyActive?: boolean | undefined;
|
|
443
|
+
/** Whether this item is currently in a sticky state at the horizontal edge. */
|
|
444
|
+
isStickyActiveX?: boolean | undefined;
|
|
445
|
+
/** Whether this item is currently in a sticky state at the vertical edge. */
|
|
446
|
+
isStickyActiveY?: boolean | undefined;
|
|
447
|
+
/** The calculated pixel offset relative to the items wrapper in display pixels (DU). */
|
|
448
|
+
offset: {
|
|
449
|
+
/** Horizontal offset (left) in DU. */
|
|
450
|
+
x: number;
|
|
451
|
+
/** Vertical offset (top) in DU. */
|
|
452
|
+
y: number;
|
|
453
|
+
};
|
|
435
454
|
}
|
|
436
455
|
|
|
437
456
|
/** Configuration properties for the `VirtualScroll` component. */
|
|
438
|
-
export interface VirtualScrollComponentProps<T = unknown> {
|
|
439
|
-
/** Array of items to be virtualized. */
|
|
440
|
-
items: T[];
|
|
441
|
-
/** Fixed size of each item (in pixels) or a function that returns the size of an item. */
|
|
442
|
-
itemSize?: number | ((item: T, index: number) => number) | null;
|
|
443
|
-
/** Direction of the scroll. */
|
|
444
|
-
direction?: ScrollDirection;
|
|
445
|
-
/** Number of items to render before the visible viewport. */
|
|
446
|
-
bufferBefore?: number;
|
|
447
|
-
/** Number of items to render after the visible viewport. */
|
|
448
|
-
bufferAfter?: number;
|
|
449
|
-
/** The scrollable container element or window. */
|
|
450
|
-
container?: HTMLElement | Window | null;
|
|
451
|
-
/** Range of items to render during Server-Side Rendering. */
|
|
452
|
-
ssrRange?: {
|
|
453
|
-
/** First row index to render. */
|
|
454
|
-
start: number;
|
|
455
|
-
/** Last row index to render (exclusive). */
|
|
456
|
-
end: number;
|
|
457
|
-
/** First column index to render (for grid mode). */
|
|
458
|
-
colStart?: number;
|
|
459
|
-
/** Last column index to render (exclusive, for grid mode). */
|
|
460
|
-
colEnd?: number;
|
|
461
|
-
};
|
|
462
|
-
/** Number of columns for bidirectional (grid) scroll. */
|
|
463
|
-
columnCount?: number;
|
|
464
|
-
/** Fixed width of columns (in pixels), an array of widths, or a function for column widths. */
|
|
465
|
-
columnWidth?: number | number[] | ((index: number) => number) | null;
|
|
457
|
+
export interface VirtualScrollComponentProps<T = unknown> extends VirtualScrollBaseProps<T> {
|
|
466
458
|
/** The HTML tag to use for the root container. */
|
|
467
459
|
containerTag?: string;
|
|
468
460
|
/** The HTML tag to use for the items wrapper. */
|
|
469
461
|
wrapperTag?: string;
|
|
470
462
|
/** The HTML tag to use for each item. */
|
|
471
463
|
itemTag?: string;
|
|
472
|
-
/** Additional padding at the start of the scroll container (top or left). */
|
|
473
|
-
scrollPaddingStart?: number | { x?: number; y?: number; };
|
|
474
|
-
/** Additional padding at the end of the scroll container (bottom or right). */
|
|
475
|
-
scrollPaddingEnd?: number | { x?: number; y?: number; };
|
|
476
464
|
/** Whether the content in the 'header' slot is sticky. */
|
|
477
465
|
stickyHeader?: boolean;
|
|
478
466
|
/** Whether the content in the 'footer' slot is sticky. */
|
|
479
467
|
stickyFooter?: boolean;
|
|
480
|
-
/** Gap between items in pixels. */
|
|
481
|
-
gap?: number;
|
|
482
|
-
/** Gap between columns in pixels. */
|
|
483
|
-
columnGap?: number;
|
|
484
|
-
/** Indices of items that should stick to the top/start of the viewport. */
|
|
485
|
-
stickyIndices?: number[];
|
|
486
|
-
/** Distance from the end of the scrollable area (in pixels) to trigger the 'load' event. */
|
|
487
|
-
loadDistance?: number;
|
|
488
|
-
/** Whether items are currently being loaded. */
|
|
489
|
-
loading?: boolean;
|
|
490
|
-
/** Whether to automatically restore and maintain scroll position when items are prepended to the list. */
|
|
491
|
-
restoreScrollOnPrepend?: boolean;
|
|
492
|
-
/** Initial scroll index to jump to immediately after mount. */
|
|
493
|
-
initialScrollIndex?: number;
|
|
494
|
-
/** Alignment for the initial scroll index. */
|
|
495
|
-
initialScrollAlign?: ScrollAlignment | ScrollAlignmentOptions;
|
|
496
|
-
/** Default size for items before they are measured by ResizeObserver. */
|
|
497
|
-
defaultItemSize?: number;
|
|
498
|
-
/** Default width for columns before they are measured by ResizeObserver. */
|
|
499
|
-
defaultColumnWidth?: number;
|
|
500
|
-
/** Whether to show debug information (visible offsets and indices) over items. */
|
|
501
|
-
debug?: boolean;
|
|
502
468
|
/** Whether to use virtual scrollbars for styling purposes. */
|
|
503
469
|
virtualScrollbar?: boolean;
|
|
504
470
|
}
|
|
@@ -544,7 +510,7 @@ export interface VirtualScrollInstance<T = unknown> extends VirtualScrollCompone
|
|
|
544
510
|
/** Physical height of the content in the DOM (clamped to browser limits). */
|
|
545
511
|
renderedHeight: number;
|
|
546
512
|
/** Absolute offset of the component within its container. */
|
|
547
|
-
componentOffset:
|
|
513
|
+
componentOffset: Point;
|
|
548
514
|
/** Properties for the vertical scrollbar. */
|
|
549
515
|
scrollbarPropsVertical: ScrollbarSlotProps | null;
|
|
550
516
|
/** Properties for the horizontal scrollbar. */
|
|
@@ -615,10 +581,6 @@ export interface ScrollTargetParams {
|
|
|
615
581
|
flowPaddingStartX?: number | undefined;
|
|
616
582
|
/** Flow padding start on Y axis. */
|
|
617
583
|
flowPaddingStartY?: number | undefined;
|
|
618
|
-
/** Flow padding end on X axis. */
|
|
619
|
-
flowPaddingEndX?: number | undefined;
|
|
620
|
-
/** Flow padding end on Y axis. */
|
|
621
|
-
flowPaddingEndY?: number | undefined;
|
|
622
584
|
/** Scroll padding start on X axis. */
|
|
623
585
|
paddingStartX?: number | undefined;
|
|
624
586
|
/** Scroll padding start on Y axis. */
|
package/src/utils/scroll.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { getPaddingX, getPaddingY, isBody, isElement, isScrollableElement, isScrollToIndexOptions, isWindow, isWindowLike } from './scroll';
|
|
3
|
+
import { getPaddingX, getPaddingY, isBody, isElement, isScrollableElement, isScrollToIndexOptions, isWindow, isWindowLike, scrollTo } from './scroll';
|
|
4
4
|
|
|
5
5
|
describe('scroll utils', () => {
|
|
6
6
|
describe('element type guards', () => {
|
|
7
|
-
describe('
|
|
7
|
+
describe('is window', () => {
|
|
8
8
|
it('returns true for null', () => {
|
|
9
9
|
expect(isWindow(null)).toBe(true);
|
|
10
10
|
});
|
|
@@ -27,7 +27,7 @@ describe('scroll utils', () => {
|
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
describe('
|
|
30
|
+
describe('is body', () => {
|
|
31
31
|
it('returns true for document.body', () => {
|
|
32
32
|
expect(isBody(document.body)).toBe(true);
|
|
33
33
|
});
|
|
@@ -64,7 +64,7 @@ describe('scroll utils', () => {
|
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
describe('
|
|
67
|
+
describe('is window like', () => {
|
|
68
68
|
it('returns true for window', () => {
|
|
69
69
|
expect(isWindowLike(window)).toBe(true);
|
|
70
70
|
});
|
|
@@ -87,7 +87,7 @@ describe('scroll utils', () => {
|
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
describe('
|
|
90
|
+
describe('is element', () => {
|
|
91
91
|
it('returns true for a div', () => {
|
|
92
92
|
const el = document.createElement('div');
|
|
93
93
|
expect(isElement(el)).toBe(true);
|
|
@@ -106,7 +106,7 @@ describe('scroll utils', () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
describe('
|
|
109
|
+
describe('is scrollable element', () => {
|
|
110
110
|
it('returns true for a div', () => {
|
|
111
111
|
const el = document.createElement('div');
|
|
112
112
|
expect(isScrollableElement(el)).toBe(true);
|
|
@@ -119,7 +119,7 @@ describe('scroll utils', () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
describe('options type guards', () => {
|
|
122
|
-
describe('
|
|
122
|
+
describe('is scroll to index options', () => {
|
|
123
123
|
it('returns true for valid options', () => {
|
|
124
124
|
expect(isScrollToIndexOptions({ align: 'start' })).toBe(true);
|
|
125
125
|
expect(isScrollToIndexOptions({ behavior: 'smooth' })).toBe(true);
|
|
@@ -135,7 +135,7 @@ describe('scroll utils', () => {
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
describe('padding utilities', () => {
|
|
138
|
-
describe('
|
|
138
|
+
describe('get padding x', () => {
|
|
139
139
|
it('handles numeric padding', () => {
|
|
140
140
|
expect(getPaddingX(10, 'horizontal')).toBe(10);
|
|
141
141
|
expect(getPaddingX(10, 'both')).toBe(10);
|
|
@@ -153,7 +153,7 @@ describe('scroll utils', () => {
|
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
describe('
|
|
156
|
+
describe('get padding y', () => {
|
|
157
157
|
it('handles numeric padding', () => {
|
|
158
158
|
expect(getPaddingY(10, 'vertical')).toBe(10);
|
|
159
159
|
expect(getPaddingY(10, 'both')).toBe(10);
|
|
@@ -171,4 +171,58 @@ describe('scroll utils', () => {
|
|
|
171
171
|
});
|
|
172
172
|
});
|
|
173
173
|
});
|
|
174
|
+
|
|
175
|
+
describe('scrollTo utility', () => {
|
|
176
|
+
it('does nothing if container is undefined', () => {
|
|
177
|
+
const spy = vi.spyOn(window, 'scrollTo');
|
|
178
|
+
scrollTo(undefined, { top: 100 });
|
|
179
|
+
expect(spy).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('scrolls the window if container is null', () => {
|
|
183
|
+
const spy = vi.spyOn(window, 'scrollTo');
|
|
184
|
+
scrollTo(null, { top: 100 });
|
|
185
|
+
expect(spy).toHaveBeenCalledWith({ top: 100 });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('scrolls the window if container is window', () => {
|
|
189
|
+
const spy = vi.spyOn(window, 'scrollTo');
|
|
190
|
+
scrollTo(window, { top: 100 });
|
|
191
|
+
expect(spy).toHaveBeenCalledWith({ top: 100 });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('scrolls the window if container is document.documentElement', () => {
|
|
195
|
+
const spy = vi.spyOn(window, 'scrollTo');
|
|
196
|
+
scrollTo(document.documentElement, { top: 100 });
|
|
197
|
+
expect(spy).toHaveBeenCalledWith({ top: 100 });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('scrolls an element using scrollTo if available', () => {
|
|
201
|
+
const el = document.createElement('div');
|
|
202
|
+
const spy = vi.fn();
|
|
203
|
+
el.scrollTo = spy;
|
|
204
|
+
scrollTo(el, { left: 50, top: 100 });
|
|
205
|
+
expect(spy).toHaveBeenCalledWith({ left: 50, top: 100 });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('scrolls an element using scrollLeft/scrollTop if scrollTo is missing', () => {
|
|
209
|
+
const el = document.createElement('div');
|
|
210
|
+
// @ts-expect-error forcing missing scrollTo
|
|
211
|
+
el.scrollTo = undefined;
|
|
212
|
+
scrollTo(el, { left: 50, top: 100 });
|
|
213
|
+
expect(el.scrollLeft).toBe(50);
|
|
214
|
+
expect(el.scrollTop).toBe(100);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('does not set undefined values on scrollLeft/scrollTop', () => {
|
|
218
|
+
const el = document.createElement('div');
|
|
219
|
+
el.scrollLeft = 10;
|
|
220
|
+
el.scrollTop = 20;
|
|
221
|
+
// @ts-expect-error forcing missing scrollTo
|
|
222
|
+
el.scrollTo = undefined;
|
|
223
|
+
scrollTo(el, { behavior: 'smooth' });
|
|
224
|
+
expect(el.scrollLeft).toBe(10);
|
|
225
|
+
expect(el.scrollTop).toBe(20);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
174
228
|
});
|
package/src/utils/scroll.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for scroll management and element type detection.
|
|
3
|
+
* Provides helper functions for checking Window and Body elements,
|
|
4
|
+
* and a universal scrollTo function.
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import type { ScrollDirection, ScrollToIndexOptions } from '../types';
|
|
2
8
|
|
|
9
|
+
/* global ScrollToOptions */
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
12
|
* Maximum size (in pixels) for an element that most browsers can handle reliably.
|
|
5
13
|
* Beyond this size, we use scaling for the scrollable area.
|
|
@@ -57,6 +65,29 @@ export function isScrollableElement(target: EventTarget | null): target is HTMLE
|
|
|
57
65
|
return target != null && 'scrollLeft' in target;
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Universal scroll function that handles both Window and HTMLElements.
|
|
70
|
+
*
|
|
71
|
+
* @param container - The container to scroll.
|
|
72
|
+
* @param options - Scroll options.
|
|
73
|
+
*/
|
|
74
|
+
export function scrollTo(container: HTMLElement | Window | null | undefined, options: ScrollToOptions) {
|
|
75
|
+
if (isWindow(container)) {
|
|
76
|
+
window.scrollTo(options);
|
|
77
|
+
} else if (container != null && isScrollableElement(container)) {
|
|
78
|
+
if (typeof container.scrollTo === 'function') {
|
|
79
|
+
container.scrollTo(options);
|
|
80
|
+
} else {
|
|
81
|
+
if (options.left !== undefined) {
|
|
82
|
+
container.scrollLeft = options.left;
|
|
83
|
+
}
|
|
84
|
+
if (options.top !== undefined) {
|
|
85
|
+
container.scrollTop = options.top;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
/**
|
|
61
92
|
* Helper to determine if an options argument is a full `ScrollToIndexOptions` object.
|
|
62
93
|
*
|