@pdanpdan/virtual-scroll 0.2.1 → 0.4.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 +278 -140
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +866 -112
- package/dist/index.js +1 -844
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1125 -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 +527 -688
- package/src/components/VirtualScroll.vue +402 -209
- package/src/composables/useVirtualScroll.test.ts +241 -1447
- package/src/composables/useVirtualScroll.ts +544 -531
- package/src/index.ts +2 -0
- package/src/types.ts +535 -0
- package/src/utils/fenwick-tree.ts +38 -18
- package/src/utils/scroll.test.ts +148 -0
- package/src/utils/scroll.ts +40 -10
- package/src/utils/virtual-scroll-logic.test.ts +2517 -0
- package/src/utils/virtual-scroll-logic.ts +605 -0
- package/dist/index.css +0 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getPaddingX, getPaddingY, isBody, isElement, isScrollableElement, isScrollToIndexOptions, isWindow, isWindowLike } from './scroll';
|
|
4
|
+
|
|
5
|
+
describe('scroll utils', () => {
|
|
6
|
+
describe('isWindow', () => {
|
|
7
|
+
it('should return true for null', () => {
|
|
8
|
+
expect(isWindow(null)).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should return true for window object', () => {
|
|
12
|
+
expect(isWindow(window)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should return false for an element', () => {
|
|
16
|
+
const el = document.createElement('div');
|
|
17
|
+
expect(isWindow(el)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('isBody', () => {
|
|
22
|
+
it('should return true for document.body', () => {
|
|
23
|
+
expect(isBody(document.body)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return false for null', () => {
|
|
27
|
+
expect(isBody(null)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return false for undefined', () => {
|
|
31
|
+
expect(isBody(undefined)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return false for a string', () => {
|
|
35
|
+
// @ts-expect-error testing invalid input
|
|
36
|
+
expect(isBody('not an object')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return false for a plain object', () => {
|
|
40
|
+
// @ts-expect-error testing invalid input
|
|
41
|
+
expect(isBody({})).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return false for a div', () => {
|
|
45
|
+
const el = document.createElement('div');
|
|
46
|
+
expect(isBody(el)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return false for window', () => {
|
|
50
|
+
expect(isBody(window)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('isWindowLike', () => {
|
|
55
|
+
it('should return true for window', () => {
|
|
56
|
+
expect(isWindowLike(window)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return true for body', () => {
|
|
60
|
+
expect(isWindowLike(document.body)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return true for null', () => {
|
|
64
|
+
expect(isWindowLike(null)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return false for a div', () => {
|
|
68
|
+
const el = document.createElement('div');
|
|
69
|
+
expect(isWindowLike(el)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('isElement', () => {
|
|
74
|
+
it('should return true for a div', () => {
|
|
75
|
+
const el = document.createElement('div');
|
|
76
|
+
expect(isElement(el)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return false for window', () => {
|
|
80
|
+
expect(isElement(window)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return false for null', () => {
|
|
84
|
+
expect(isElement(null)).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('isScrollableElement', () => {
|
|
89
|
+
it('should return true for a div', () => {
|
|
90
|
+
const el = document.createElement('div');
|
|
91
|
+
expect(isScrollableElement(el)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return false for null', () => {
|
|
95
|
+
expect(isScrollableElement(null)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('isScrollToIndexOptions', () => {
|
|
100
|
+
it('should return true for valid options', () => {
|
|
101
|
+
expect(isScrollToIndexOptions({ align: 'start' })).toBe(true);
|
|
102
|
+
expect(isScrollToIndexOptions({ behavior: 'smooth' })).toBe(true);
|
|
103
|
+
expect(isScrollToIndexOptions({ isCorrection: true })).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return false for other values', () => {
|
|
107
|
+
expect(isScrollToIndexOptions(null)).toBe(false);
|
|
108
|
+
expect(isScrollToIndexOptions('start')).toBe(false);
|
|
109
|
+
expect(isScrollToIndexOptions({})).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('getPaddingX', () => {
|
|
114
|
+
it('should handle numeric padding', () => {
|
|
115
|
+
expect(getPaddingX(10, 'horizontal')).toBe(10);
|
|
116
|
+
expect(getPaddingX(10, 'both')).toBe(10);
|
|
117
|
+
expect(getPaddingX(10, 'vertical')).toBe(0);
|
|
118
|
+
expect(getPaddingX(0, 'horizontal')).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should handle object padding', () => {
|
|
122
|
+
expect(getPaddingX({ x: 15 }, 'vertical')).toBe(15);
|
|
123
|
+
expect(getPaddingX({ y: 20 }, 'horizontal')).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return 0 for undefined', () => {
|
|
127
|
+
expect(getPaddingX(undefined)).toBe(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('getPaddingY', () => {
|
|
132
|
+
it('should handle numeric padding', () => {
|
|
133
|
+
expect(getPaddingY(10, 'vertical')).toBe(10);
|
|
134
|
+
expect(getPaddingY(10, 'both')).toBe(10);
|
|
135
|
+
expect(getPaddingY(10, 'horizontal')).toBe(0);
|
|
136
|
+
expect(getPaddingY(0, 'vertical')).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle object padding', () => {
|
|
140
|
+
expect(getPaddingY({ y: 15 }, 'horizontal')).toBe(15);
|
|
141
|
+
expect(getPaddingY({ x: 20 }, 'vertical')).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return 0 for undefined', () => {
|
|
145
|
+
expect(getPaddingY(undefined)).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
package/src/utils/scroll.ts
CHANGED
|
@@ -1,37 +1,67 @@
|
|
|
1
|
-
import type { ScrollDirection, ScrollToIndexOptions } from '../
|
|
1
|
+
import type { ScrollDirection, ScrollToIndexOptions } from '../types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Checks if the container
|
|
4
|
+
* Checks if the container is the window object.
|
|
5
5
|
*
|
|
6
6
|
* @param container - The container element or window to check.
|
|
7
|
-
* @returns
|
|
7
|
+
* @returns `true` if the container is the global window object.
|
|
8
|
+
*/
|
|
9
|
+
export function isWindow(container: HTMLElement | Window | null | undefined): container is Window {
|
|
10
|
+
return container === null || (typeof window !== 'undefined' && container === window);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the container is the document body element.
|
|
15
|
+
*
|
|
16
|
+
* @param container - The container element or window to check.
|
|
17
|
+
* @returns `true` if the container is the `<body>` element.
|
|
18
|
+
*/
|
|
19
|
+
export function isBody(container: HTMLElement | Window | null | undefined): container is HTMLElement {
|
|
20
|
+
return !!container && typeof container === 'object' && 'tagName' in container && container.tagName === 'BODY';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Checks if the container is window-like (global window or document body).
|
|
25
|
+
*
|
|
26
|
+
* @param container - The container element or window to check.
|
|
27
|
+
* @returns `true` if the container is window or body.
|
|
28
|
+
*/
|
|
29
|
+
export function isWindowLike(container: HTMLElement | Window | null | undefined): boolean {
|
|
30
|
+
return isWindow(container) || isBody(container);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks if the container is a valid HTML Element with bounding rect support.
|
|
35
|
+
*
|
|
36
|
+
* @param container - The container to check.
|
|
37
|
+
* @returns `true` if the container is an `HTMLElement`.
|
|
8
38
|
*/
|
|
9
39
|
export function isElement(container: HTMLElement | Window | null | undefined): container is HTMLElement {
|
|
10
40
|
return !!container && 'getBoundingClientRect' in container;
|
|
11
41
|
}
|
|
12
42
|
|
|
13
43
|
/**
|
|
14
|
-
* Checks if the target is an element
|
|
44
|
+
* Checks if the target is an element that supports scrolling.
|
|
15
45
|
*
|
|
16
46
|
* @param target - The event target to check.
|
|
17
|
-
* @returns
|
|
47
|
+
* @returns `true` if the target is an `HTMLElement` with scroll properties.
|
|
18
48
|
*/
|
|
19
49
|
export function isScrollableElement(target: EventTarget | null): target is HTMLElement {
|
|
20
50
|
return !!target && 'scrollLeft' in target;
|
|
21
51
|
}
|
|
22
52
|
|
|
23
53
|
/**
|
|
24
|
-
* Helper to determine if an options argument is
|
|
54
|
+
* Helper to determine if an options argument is a full `ScrollToIndexOptions` object.
|
|
25
55
|
*
|
|
26
56
|
* @param options - The options object to check.
|
|
27
|
-
* @returns
|
|
57
|
+
* @returns `true` if the options object contains scroll-to-index specific properties.
|
|
28
58
|
*/
|
|
29
59
|
export function isScrollToIndexOptions(options: unknown): options is ScrollToIndexOptions {
|
|
30
60
|
return typeof options === 'object' && options !== null && ('align' in options || 'behavior' in options || 'isCorrection' in options);
|
|
31
61
|
}
|
|
32
62
|
|
|
33
63
|
/**
|
|
34
|
-
* Extracts the horizontal padding from a padding
|
|
64
|
+
* Extracts the horizontal padding from a padding configuration.
|
|
35
65
|
*
|
|
36
66
|
* @param p - The padding value (number or object with x/y).
|
|
37
67
|
* @param direction - The current scroll direction.
|
|
@@ -41,11 +71,11 @@ export function getPaddingX(p: number | { x?: number; y?: number; } | undefined,
|
|
|
41
71
|
if (typeof p === 'object' && p !== null) {
|
|
42
72
|
return p.x || 0;
|
|
43
73
|
}
|
|
44
|
-
return direction === 'horizontal' ? (p || 0) : 0;
|
|
74
|
+
return (direction === 'horizontal' || direction === 'both') ? (p || 0) : 0;
|
|
45
75
|
}
|
|
46
76
|
|
|
47
77
|
/**
|
|
48
|
-
* Extracts the vertical padding from a padding
|
|
78
|
+
* Extracts the vertical padding from a padding configuration.
|
|
49
79
|
*
|
|
50
80
|
* @param p - The padding value (number or object with x/y).
|
|
51
81
|
* @param direction - The current scroll direction.
|