@pdanpdan/virtual-scroll 0.9.1 → 0.10.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.
- package/README.md +82 -4
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +192 -156
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +862 -695
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -1
- package/package.json +1 -1
- package/src/components/VirtualScroll.vue +30 -20
- package/src/composables/useVirtualScroll.ts +394 -813
- package/src/composables/useVirtualScrollSizes.ts +28 -37
- package/src/composables/useVirtualScrollbar.ts +16 -0
- package/src/extensions/all.ts +7 -0
- package/src/extensions/coordinate-scaling.ts +30 -0
- package/src/extensions/index.ts +88 -0
- package/src/extensions/infinite-loading.ts +47 -0
- package/src/extensions/prepend-restoration.ts +49 -0
- package/src/extensions/rtl.ts +42 -0
- package/src/extensions/snapping.ts +95 -0
- package/src/extensions/sticky.ts +43 -0
- package/src/types.ts +47 -8
- package/src/utils/scroll.ts +2 -2
- package/src/utils/virtual-scroll-logic.ts +42 -0
package/src/types.ts
CHANGED
|
@@ -87,6 +87,25 @@ export interface ScrollToIndexOptions {
|
|
|
87
87
|
* @internal
|
|
88
88
|
*/
|
|
89
89
|
isCorrection?: boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* If true, only calculates the target position without performing the actual scroll.
|
|
93
|
+
* Useful for extensions that need to validate if a snap is necessary.
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
dryRun?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Result of the `scrollToIndex` method. */
|
|
100
|
+
export interface ScrollToIndexResult {
|
|
101
|
+
/** Target relative horizontal position in virtual units (VU). */
|
|
102
|
+
targetX: number;
|
|
103
|
+
/** Target relative vertical position in virtual units (VU). */
|
|
104
|
+
targetY: number;
|
|
105
|
+
/** Target display horizontal position (DU). */
|
|
106
|
+
displayTargetX: number;
|
|
107
|
+
/** Target display vertical position (DU). */
|
|
108
|
+
displayTargetY: number;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
/** Represents an item currently rendered in the virtual scroll area. */
|
|
@@ -192,9 +211,10 @@ export type PaddingValue = number | { x?: number; y?: number; };
|
|
|
192
211
|
* - 'start': Aligns the first visible item to the viewport start if at least 50% visible, otherwise aligns the next item.
|
|
193
212
|
* - 'center': Aligns the item that intersects the viewport center to the center.
|
|
194
213
|
* - 'end': Aligns the last visible item to the viewport end if at least 50% visible, otherwise aligns the previous item.
|
|
214
|
+
* - 'next': Snaps to the next (closest) snap position in the direction of the scroll.
|
|
195
215
|
* - 'auto': Intelligent snapping based on scroll direction. Acts as 'end' when scrolling towards start, and 'start' when scrolling towards end.
|
|
196
216
|
*/
|
|
197
|
-
export type SnapMode = boolean | 'start' | 'center' | 'end' | 'auto';
|
|
217
|
+
export type SnapMode = boolean | 'start' | 'center' | 'end' | 'next' | 'auto';
|
|
198
218
|
|
|
199
219
|
/** Base configuration properties shared between the component and the composable. */
|
|
200
220
|
export interface VirtualScrollBaseProps<T = unknown> {
|
|
@@ -205,7 +225,7 @@ export interface VirtualScrollBaseProps<T = unknown> {
|
|
|
205
225
|
* Fixed size of each item in virtual units (VU) or a function that returns the size of an item.
|
|
206
226
|
* Pass `0`, `null` or `undefined` for automatic dynamic size detection via `ResizeObserver`.
|
|
207
227
|
*/
|
|
208
|
-
itemSize?: number | ((item: T, index: number) => number) | null | undefined;
|
|
228
|
+
itemSize?: number | (number | null | undefined)[] | ((item: T, index: number) => number) | null | undefined;
|
|
209
229
|
|
|
210
230
|
/**
|
|
211
231
|
* Direction of the virtual scroll.
|
|
@@ -246,7 +266,7 @@ export interface VirtualScrollBaseProps<T = unknown> {
|
|
|
246
266
|
* Fixed width of columns in VU, an array of widths, or a function returning widths.
|
|
247
267
|
* Pass `0`, `null` or `undefined` for dynamic column detection.
|
|
248
268
|
*/
|
|
249
|
-
columnWidth?: number | number[] | ((index: number) => number) | null | undefined;
|
|
269
|
+
columnWidth?: number | (number | null | undefined)[] | ((index: number) => number) | null | undefined;
|
|
250
270
|
|
|
251
271
|
/**
|
|
252
272
|
* Pixel padding at the start of the scroll container in display pixels (DU).
|
|
@@ -283,11 +303,13 @@ export interface VirtualScrollBaseProps<T = unknown> {
|
|
|
283
303
|
|
|
284
304
|
/**
|
|
285
305
|
* Whether data is currently loading.
|
|
306
|
+
* While true, the loading slot is shown and `load` events are suppressed.
|
|
286
307
|
*/
|
|
287
308
|
loading?: boolean | undefined;
|
|
288
309
|
|
|
289
310
|
/**
|
|
290
|
-
* Whether to automatically
|
|
311
|
+
* Whether to automatically maintain scroll position when items are prepended to the array.
|
|
312
|
+
* Useful for "load more" chat interfaces.
|
|
291
313
|
*/
|
|
292
314
|
restoreScrollOnPrepend?: boolean | undefined;
|
|
293
315
|
|
|
@@ -342,6 +364,7 @@ export interface VirtualScrollBaseProps<T = unknown> {
|
|
|
342
364
|
|
|
343
365
|
/**
|
|
344
366
|
* Whether to snap to items after scrolling stops.
|
|
367
|
+
* Options: false, true, 'auto', 'next', 'start', 'center', 'end'.
|
|
345
368
|
* @default false
|
|
346
369
|
*/
|
|
347
370
|
snap?: SnapMode | undefined;
|
|
@@ -514,10 +537,20 @@ export interface VirtualScrollComponentProps<T = unknown> extends VirtualScrollB
|
|
|
514
537
|
/** The HTML tag to use for each item. */
|
|
515
538
|
itemTag?: string;
|
|
516
539
|
/** Whether the content in the 'header' slot is sticky. */
|
|
540
|
+
/**
|
|
541
|
+
* If true, measures the header slot size and adds it to the scroll padding.
|
|
542
|
+
* Can be combined with CSS for sticky headers.
|
|
543
|
+
*/
|
|
517
544
|
stickyHeader?: boolean;
|
|
518
|
-
/**
|
|
545
|
+
/**
|
|
546
|
+
* If true, measures the footer slot size and adds it to the scroll padding.
|
|
547
|
+
* Can be combined with CSS for sticky footers.
|
|
548
|
+
*/
|
|
519
549
|
stickyFooter?: boolean;
|
|
520
|
-
/**
|
|
550
|
+
/**
|
|
551
|
+
* Whether to use virtual scrollbars.
|
|
552
|
+
* Automatically enabled when content size exceeds browser limits.
|
|
553
|
+
*/
|
|
521
554
|
virtualScrollbar?: boolean;
|
|
522
555
|
}
|
|
523
556
|
|
|
@@ -547,8 +580,12 @@ export interface VirtualScrollInstance<T = unknown> extends VirtualScrollCompone
|
|
|
547
580
|
getItemOffset: (index: number) => number;
|
|
548
581
|
/** Helper to get the size of a specific item along the scroll axis. */
|
|
549
582
|
getItemSize: (index: number) => number;
|
|
583
|
+
/** Whether the component is in table mode. */
|
|
584
|
+
isTable: boolean;
|
|
585
|
+
/** The tag used for rendering items. */
|
|
586
|
+
itemTag: string;
|
|
550
587
|
/** Programmatically scroll to a specific row and/or column. */
|
|
551
|
-
scrollToIndex: (rowIndex?: number | null, colIndex?: number | null, options?: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions) =>
|
|
588
|
+
scrollToIndex: (rowIndex?: number | null, colIndex?: number | null, options?: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions) => ScrollToIndexResult;
|
|
552
589
|
/** Programmatically scroll to a specific pixel offset. */
|
|
553
590
|
scrollToOffset: (x?: number | null, y?: number | null, options?: { behavior?: 'auto' | 'smooth'; }) => void;
|
|
554
591
|
/** Resets all dynamic measurements and re-initializes from props. */
|
|
@@ -681,6 +718,8 @@ export interface RangeParams {
|
|
|
681
718
|
usableHeight: number;
|
|
682
719
|
/** Total item count. */
|
|
683
720
|
itemsLength: number;
|
|
721
|
+
/** Column count (for grid mode). */
|
|
722
|
+
columnCount?: number;
|
|
684
723
|
/** Buffer items before. */
|
|
685
724
|
bufferBefore: number;
|
|
686
725
|
/** Buffer items after. */
|
|
@@ -796,7 +835,7 @@ export interface ItemStyleParams<T = unknown> {
|
|
|
796
835
|
/** Scroll direction. */
|
|
797
836
|
direction: ScrollDirection;
|
|
798
837
|
/** Configured item size logic. */
|
|
799
|
-
itemSize: number | ((item: T, index: number) => number) | null | undefined;
|
|
838
|
+
itemSize: number | (number | null | undefined)[] | ((item: T, index: number) => number) | null | undefined;
|
|
800
839
|
/** Parent container tag. */
|
|
801
840
|
containerTag: string;
|
|
802
841
|
/** Padding start on X axis. */
|
package/src/utils/scroll.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const BROWSER_MAX_SIZE = 10000000;
|
|
|
22
22
|
* @returns `true` if the container is the global window object.
|
|
23
23
|
*/
|
|
24
24
|
export function isWindow(container?: HTMLElement | Window | null): container is Window {
|
|
25
|
-
return container === null || container === document.documentElement || (typeof window !== 'undefined' && container === window);
|
|
25
|
+
return container === null || (typeof document !== 'undefined' && container === document.documentElement) || (typeof window !== 'undefined' && container === window);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -95,7 +95,7 @@ export function scrollTo(container: HTMLElement | Window | null | undefined, opt
|
|
|
95
95
|
* @returns `true` if the options object contains scroll-to-index specific properties.
|
|
96
96
|
*/
|
|
97
97
|
export function isScrollToIndexOptions(options: unknown): options is ScrollToIndexOptions {
|
|
98
|
-
return typeof options === 'object' && options != null && ('align' in options || 'behavior' in options || 'isCorrection' in options);
|
|
98
|
+
return typeof options === 'object' && options != null && ('align' in options || 'behavior' in options || 'isCorrection' in options || 'dryRun' in options);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -1189,6 +1189,48 @@ export function resolveSnap(
|
|
|
1189
1189
|
}
|
|
1190
1190
|
}
|
|
1191
1191
|
|
|
1192
|
+
if (mode === 'next') {
|
|
1193
|
+
if (dir === 'end') {
|
|
1194
|
+
// Scrolling items UP (towards end of list) -> snap to NEXT item start
|
|
1195
|
+
const size = getSize(currentIdx);
|
|
1196
|
+
if (size > viewSize) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
const scrolledOut = relScroll - getQuery(currentIdx);
|
|
1200
|
+
const threshold = Math.min(5, size * 0.1);
|
|
1201
|
+
if (scrolledOut <= threshold) {
|
|
1202
|
+
return {
|
|
1203
|
+
index: currentIdx,
|
|
1204
|
+
align: 'start' as const,
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
index: Math.min(count - 1, currentIdx + 1),
|
|
1209
|
+
align: 'start' as const,
|
|
1210
|
+
};
|
|
1211
|
+
} else if (dir === 'start') {
|
|
1212
|
+
// Scrolling items DOWN (towards start of list) -> snap to PREVIOUS item end
|
|
1213
|
+
const size = getSize(currentEndIdx);
|
|
1214
|
+
if (size > viewSize) {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
const scrolledOutBottom = (getQuery(currentEndIdx) + size) - (relScroll + viewSize);
|
|
1218
|
+
const threshold = Math.min(5, size * 0.1);
|
|
1219
|
+
if (scrolledOutBottom <= threshold) {
|
|
1220
|
+
return {
|
|
1221
|
+
index: currentEndIdx,
|
|
1222
|
+
align: 'end' as const,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
index: Math.max(0, currentEndIdx - 1),
|
|
1227
|
+
align: 'end' as const,
|
|
1228
|
+
};
|
|
1229
|
+
} else {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1192
1234
|
if (effectiveMode === 'start') {
|
|
1193
1235
|
const size = getSize(currentIdx);
|
|
1194
1236
|
// Ignore items larger than viewport to prevent jarring jumps
|