@pdanpdan/virtual-scroll 0.4.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 +172 -324
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +836 -376
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1334 -741
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -1
- package/package.json +8 -2
- package/src/components/VirtualScroll.test.ts +1921 -325
- package/src/components/VirtualScroll.vue +829 -386
- package/src/components/VirtualScrollbar.test.ts +174 -0
- package/src/components/VirtualScrollbar.vue +102 -0
- package/src/composables/useVirtualScroll.test.ts +1506 -228
- package/src/composables/useVirtualScroll.ts +869 -517
- package/src/composables/useVirtualScrollbar.test.ts +526 -0
- package/src/composables/useVirtualScrollbar.ts +244 -0
- package/src/index.ts +9 -0
- package/src/types.ts +353 -110
- package/src/utils/fenwick-tree.test.ts +39 -39
- package/src/utils/scroll.test.ts +181 -101
- package/src/utils/scroll.ts +43 -5
- package/src/utils/virtual-scroll-logic.test.ts +673 -323
- package/src/utils/virtual-scroll-logic.ts +759 -430
|
@@ -4,15 +4,15 @@ import { FenwickTree } from './fenwick-tree';
|
|
|
4
4
|
|
|
5
5
|
describe('fenwickTree', () => {
|
|
6
6
|
describe('initialization', () => {
|
|
7
|
-
it('
|
|
7
|
+
it('initializes with correct size', () => {
|
|
8
8
|
const tree = new FenwickTree(5);
|
|
9
9
|
expect(tree.query(5)).toBe(0);
|
|
10
10
|
expect(tree.length).toBe(5);
|
|
11
11
|
});
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
describe('
|
|
15
|
-
it('
|
|
14
|
+
describe('updates & queries', () => {
|
|
15
|
+
it('updates and queries values', () => {
|
|
16
16
|
const tree = new FenwickTree(5);
|
|
17
17
|
tree.update(0, 10);
|
|
18
18
|
tree.update(1, 20);
|
|
@@ -24,7 +24,7 @@ describe('fenwickTree', () => {
|
|
|
24
24
|
expect(tree.query(3)).toBe(60);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
it('
|
|
27
|
+
it('handles updates to existing indices', () => {
|
|
28
28
|
const tree = new FenwickTree(3);
|
|
29
29
|
tree.update(1, 10);
|
|
30
30
|
expect(tree.query(2)).toBe(10);
|
|
@@ -32,7 +32,7 @@ describe('fenwickTree', () => {
|
|
|
32
32
|
expect(tree.query(2)).toBe(15);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('
|
|
35
|
+
it('ignores updates for out of bounds indices', () => {
|
|
36
36
|
const tree = new FenwickTree(5);
|
|
37
37
|
tree.update(-1, 10);
|
|
38
38
|
tree.update(5, 10);
|
|
@@ -40,22 +40,29 @@ describe('fenwickTree', () => {
|
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
describe('
|
|
44
|
-
it('
|
|
45
|
-
const tree = new FenwickTree(
|
|
46
|
-
tree.update(0, 10);
|
|
47
|
-
tree.
|
|
48
|
-
tree.
|
|
43
|
+
describe('value access', () => {
|
|
44
|
+
it('returns the individual value at an index', () => {
|
|
45
|
+
const tree = new FenwickTree(3);
|
|
46
|
+
tree.update(0, 10);
|
|
47
|
+
expect(tree.get(0)).toBe(10);
|
|
48
|
+
expect(tree.get(-1)).toBe(0);
|
|
49
|
+
expect(tree.get(10)).toBe(0);
|
|
50
|
+
});
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
it('returns the underlying values array', () => {
|
|
53
|
+
const tree = new FenwickTree(3);
|
|
54
|
+
tree.update(0, 10);
|
|
55
|
+
tree.update(1, 20);
|
|
56
|
+
const values = tree.getValues();
|
|
57
|
+
expect(values).toBeInstanceOf(Float64Array);
|
|
58
|
+
expect(values[ 0 ]).toBe(10);
|
|
59
|
+
expect(values[ 1 ]).toBe(20);
|
|
60
|
+
expect(values[ 2 ]).toBe(0);
|
|
54
61
|
});
|
|
55
62
|
});
|
|
56
63
|
|
|
57
|
-
describe('rebuild
|
|
58
|
-
it('
|
|
64
|
+
describe('rebuild & resize', () => {
|
|
65
|
+
it('sets and rebuilds correctly', () => {
|
|
59
66
|
const tree = new FenwickTree(5);
|
|
60
67
|
tree.set(0, 10);
|
|
61
68
|
tree.set(1, 20);
|
|
@@ -67,7 +74,7 @@ describe('fenwickTree', () => {
|
|
|
67
74
|
expect(tree.query(3)).toBe(60);
|
|
68
75
|
});
|
|
69
76
|
|
|
70
|
-
it('
|
|
77
|
+
it('resizes and preserves existing values', () => {
|
|
71
78
|
const tree = new FenwickTree(5);
|
|
72
79
|
tree.update(0, 10);
|
|
73
80
|
tree.resize(10);
|
|
@@ -77,36 +84,29 @@ describe('fenwickTree', () => {
|
|
|
77
84
|
});
|
|
78
85
|
});
|
|
79
86
|
|
|
80
|
-
describe('
|
|
81
|
-
it('
|
|
82
|
-
const tree = new FenwickTree(
|
|
83
|
-
tree.update(0, 10);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expect(tree.get(10)).toBe(0);
|
|
87
|
-
});
|
|
87
|
+
describe('search & bounds', () => {
|
|
88
|
+
it('finds lower bound correctly', () => {
|
|
89
|
+
const tree = new FenwickTree(5);
|
|
90
|
+
tree.update(0, 10); // sum up to 1: 10
|
|
91
|
+
tree.update(1, 10); // sum up to 2: 20
|
|
92
|
+
tree.update(2, 10); // sum up to 3: 30
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
tree.
|
|
92
|
-
tree.
|
|
93
|
-
const values = tree.getValues();
|
|
94
|
-
expect(values).toBeInstanceOf(Float64Array);
|
|
95
|
-
expect(values[ 0 ]).toBe(10);
|
|
96
|
-
expect(values[ 1 ]).toBe(20);
|
|
97
|
-
expect(values[ 2 ]).toBe(0);
|
|
94
|
+
expect(tree.findLowerBound(5)).toBe(0);
|
|
95
|
+
expect(tree.findLowerBound(15)).toBe(1);
|
|
96
|
+
expect(tree.findLowerBound(25)).toBe(2);
|
|
97
|
+
expect(tree.findLowerBound(35)).toBe(5); // Returns size when not found
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
describe('shift', () => {
|
|
102
|
-
it('
|
|
101
|
+
describe('shift operations', () => {
|
|
102
|
+
it('does nothing when offset is 0', () => {
|
|
103
103
|
const tree = new FenwickTree(3);
|
|
104
104
|
tree.update(0, 10);
|
|
105
105
|
tree.shift(0);
|
|
106
106
|
expect(tree.get(0)).toBe(10);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
it('
|
|
109
|
+
it('shifts values forward when offset is positive', () => {
|
|
110
110
|
const tree = new FenwickTree(5);
|
|
111
111
|
tree.update(0, 10);
|
|
112
112
|
tree.update(1, 20);
|
|
@@ -119,7 +119,7 @@ describe('fenwickTree', () => {
|
|
|
119
119
|
expect(tree.query(4)).toBe(30);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
it('
|
|
122
|
+
it('shifts values backward when offset is negative', () => {
|
|
123
123
|
const tree = new FenwickTree(5);
|
|
124
124
|
tree.update(2, 10);
|
|
125
125
|
tree.update(3, 20);
|
package/src/utils/scroll.test.ts
CHANGED
|
@@ -1,148 +1,228 @@
|
|
|
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
|
-
describe('
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
describe('element type guards', () => {
|
|
7
|
+
describe('is window', () => {
|
|
8
|
+
it('returns true for null', () => {
|
|
9
|
+
expect(isWindow(null)).toBe(true);
|
|
10
|
+
});
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
it('returns true for window object', () => {
|
|
13
|
+
expect(isWindow(window)).toBe(true);
|
|
14
|
+
});
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
19
|
-
});
|
|
16
|
+
it('returns true for document.documentelement object', () => {
|
|
17
|
+
expect(isWindow(document.documentElement)).toBe(true);
|
|
18
|
+
});
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
it('returns false for an element', () => {
|
|
21
|
+
const el = document.createElement('div');
|
|
22
|
+
expect(isWindow(el)).toBe(false);
|
|
23
|
+
});
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
it('returns false for undefined', () => {
|
|
26
|
+
expect(isWindow(undefined)).toBe(false);
|
|
27
|
+
});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
describe('is body', () => {
|
|
31
|
+
it('returns true for document.body', () => {
|
|
32
|
+
expect(isBody(document.body)).toBe(true);
|
|
33
|
+
});
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
35
|
+
it('returns false for null', () => {
|
|
36
|
+
expect(isBody(null)).toBe(false);
|
|
37
|
+
});
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
39
|
+
it('returns false for undefined', () => {
|
|
40
|
+
expect(isBody(undefined)).toBe(false);
|
|
41
|
+
});
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
it('returns false for a string', () => {
|
|
44
|
+
// @ts-expect-error testing invalid input
|
|
45
|
+
expect(isBody('not an object')).toBe(false);
|
|
46
|
+
});
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
it('returns false for a plain object', () => {
|
|
49
|
+
// @ts-expect-error testing invalid input
|
|
50
|
+
expect(isBody({})).toBe(false);
|
|
51
|
+
});
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
it('returns false for a div', () => {
|
|
54
|
+
const el = document.createElement('div');
|
|
55
|
+
expect(isBody(el)).toBe(false);
|
|
56
|
+
});
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
it('returns false for window', () => {
|
|
59
|
+
expect(isBody(window)).toBe(false);
|
|
60
|
+
});
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
it('returns false for document.documentelement', () => {
|
|
63
|
+
expect(isBody(document.documentElement)).toBe(false);
|
|
64
|
+
});
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
67
|
+
describe('is window like', () => {
|
|
68
|
+
it('returns true for window', () => {
|
|
69
|
+
expect(isWindowLike(window)).toBe(true);
|
|
70
|
+
});
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
it('returns true for document.documentelement', () => {
|
|
73
|
+
expect(isWindowLike(document.documentElement)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('returns true for body', () => {
|
|
77
|
+
expect(isWindowLike(document.body)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns true for null', () => {
|
|
81
|
+
expect(isWindowLike(null)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('returns false for a div', () => {
|
|
85
|
+
const el = document.createElement('div');
|
|
86
|
+
expect(isWindowLike(el)).toBe(false);
|
|
87
|
+
});
|
|
77
88
|
});
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
describe('is element', () => {
|
|
91
|
+
it('returns true for a div', () => {
|
|
92
|
+
const el = document.createElement('div');
|
|
93
|
+
expect(isElement(el)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns true for document.documentelement', () => {
|
|
97
|
+
expect(isElement(document.documentElement)).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns false for window', () => {
|
|
101
|
+
expect(isElement(window)).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns false for null', () => {
|
|
105
|
+
expect(isElement(null)).toBe(false);
|
|
106
|
+
});
|
|
81
107
|
});
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
describe('is scrollable element', () => {
|
|
110
|
+
it('returns true for a div', () => {
|
|
111
|
+
const el = document.createElement('div');
|
|
112
|
+
expect(isScrollableElement(el)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns false for null', () => {
|
|
116
|
+
expect(isScrollableElement(null)).toBe(false);
|
|
117
|
+
});
|
|
85
118
|
});
|
|
86
119
|
});
|
|
87
120
|
|
|
88
|
-
describe('
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
describe('options type guards', () => {
|
|
122
|
+
describe('is scroll to index options', () => {
|
|
123
|
+
it('returns true for valid options', () => {
|
|
124
|
+
expect(isScrollToIndexOptions({ align: 'start' })).toBe(true);
|
|
125
|
+
expect(isScrollToIndexOptions({ behavior: 'smooth' })).toBe(true);
|
|
126
|
+
expect(isScrollToIndexOptions({ isCorrection: true })).toBe(true);
|
|
127
|
+
});
|
|
93
128
|
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
it('returns false for other values', () => {
|
|
130
|
+
expect(isScrollToIndexOptions(null)).toBe(false);
|
|
131
|
+
expect(isScrollToIndexOptions('start')).toBe(false);
|
|
132
|
+
expect(isScrollToIndexOptions({})).toBe(false);
|
|
133
|
+
});
|
|
96
134
|
});
|
|
97
135
|
});
|
|
98
136
|
|
|
99
|
-
describe('
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
137
|
+
describe('padding utilities', () => {
|
|
138
|
+
describe('get padding x', () => {
|
|
139
|
+
it('handles numeric padding', () => {
|
|
140
|
+
expect(getPaddingX(10, 'horizontal')).toBe(10);
|
|
141
|
+
expect(getPaddingX(10, 'both')).toBe(10);
|
|
142
|
+
expect(getPaddingX(10, 'vertical')).toBe(0);
|
|
143
|
+
expect(getPaddingX(0, 'horizontal')).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('handles object padding', () => {
|
|
147
|
+
expect(getPaddingX({ x: 15 }, 'vertical')).toBe(15);
|
|
148
|
+
expect(getPaddingX({ y: 20 }, 'horizontal')).toBe(0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('returns 0 for undefined', () => {
|
|
152
|
+
expect(getPaddingX(undefined)).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('get padding y', () => {
|
|
157
|
+
it('handles numeric padding', () => {
|
|
158
|
+
expect(getPaddingY(10, 'vertical')).toBe(10);
|
|
159
|
+
expect(getPaddingY(10, 'both')).toBe(10);
|
|
160
|
+
expect(getPaddingY(10, 'horizontal')).toBe(0);
|
|
161
|
+
expect(getPaddingY(0, 'vertical')).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('handles object padding', () => {
|
|
165
|
+
expect(getPaddingY({ y: 15 }, 'horizontal')).toBe(15);
|
|
166
|
+
expect(getPaddingY({ x: 20 }, 'vertical')).toBe(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns 0 for undefined', () => {
|
|
170
|
+
expect(getPaddingY(undefined)).toBe(0);
|
|
171
|
+
});
|
|
104
172
|
});
|
|
173
|
+
});
|
|
105
174
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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();
|
|
110
180
|
});
|
|
111
|
-
});
|
|
112
181
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
expect(
|
|
117
|
-
expect(getPaddingX(10, 'vertical')).toBe(0);
|
|
118
|
-
expect(getPaddingX(0, 'horizontal')).toBe(0);
|
|
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 });
|
|
119
186
|
});
|
|
120
187
|
|
|
121
|
-
it('
|
|
122
|
-
|
|
123
|
-
|
|
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 });
|
|
124
192
|
});
|
|
125
193
|
|
|
126
|
-
it('
|
|
127
|
-
|
|
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 });
|
|
128
198
|
});
|
|
129
|
-
});
|
|
130
199
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
expect(
|
|
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 });
|
|
137
206
|
});
|
|
138
207
|
|
|
139
|
-
it('
|
|
140
|
-
|
|
141
|
-
expect
|
|
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);
|
|
142
215
|
});
|
|
143
216
|
|
|
144
|
-
it('
|
|
145
|
-
|
|
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);
|
|
146
226
|
});
|
|
147
227
|
});
|
|
148
228
|
});
|
package/src/utils/scroll.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
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
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Maximum size (in pixels) for an element that most browsers can handle reliably.
|
|
13
|
+
* Beyond this size, we use scaling for the scrollable area.
|
|
14
|
+
* @default 10000000
|
|
15
|
+
*/
|
|
16
|
+
export const BROWSER_MAX_SIZE = 10000000;
|
|
17
|
+
|
|
3
18
|
/**
|
|
4
19
|
* Checks if the container is the window object.
|
|
5
20
|
*
|
|
@@ -7,7 +22,7 @@ import type { ScrollDirection, ScrollToIndexOptions } from '../types';
|
|
|
7
22
|
* @returns `true` if the container is the global window object.
|
|
8
23
|
*/
|
|
9
24
|
export function isWindow(container: HTMLElement | Window | null | undefined): container is Window {
|
|
10
|
-
return container === null || (typeof window !== 'undefined' && container === window);
|
|
25
|
+
return container === null || container === document.documentElement || (typeof window !== 'undefined' && container === window);
|
|
11
26
|
}
|
|
12
27
|
|
|
13
28
|
/**
|
|
@@ -17,7 +32,7 @@ export function isWindow(container: HTMLElement | Window | null | undefined): co
|
|
|
17
32
|
* @returns `true` if the container is the `<body>` element.
|
|
18
33
|
*/
|
|
19
34
|
export function isBody(container: HTMLElement | Window | null | undefined): container is HTMLElement {
|
|
20
|
-
return
|
|
35
|
+
return container != null && typeof container === 'object' && 'tagName' in container && container.tagName === 'BODY';
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
/**
|
|
@@ -37,7 +52,7 @@ export function isWindowLike(container: HTMLElement | Window | null | undefined)
|
|
|
37
52
|
* @returns `true` if the container is an `HTMLElement`.
|
|
38
53
|
*/
|
|
39
54
|
export function isElement(container: HTMLElement | Window | null | undefined): container is HTMLElement {
|
|
40
|
-
return
|
|
55
|
+
return container != null && 'getBoundingClientRect' in container;
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
/**
|
|
@@ -47,7 +62,30 @@ export function isElement(container: HTMLElement | Window | null | undefined): c
|
|
|
47
62
|
* @returns `true` if the target is an `HTMLElement` with scroll properties.
|
|
48
63
|
*/
|
|
49
64
|
export function isScrollableElement(target: EventTarget | null): target is HTMLElement {
|
|
50
|
-
return
|
|
65
|
+
return target != null && 'scrollLeft' in target;
|
|
66
|
+
}
|
|
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
|
+
}
|
|
51
89
|
}
|
|
52
90
|
|
|
53
91
|
/**
|
|
@@ -57,7 +95,7 @@ export function isScrollableElement(target: EventTarget | null): target is HTMLE
|
|
|
57
95
|
* @returns `true` if the options object contains scroll-to-index specific properties.
|
|
58
96
|
*/
|
|
59
97
|
export function isScrollToIndexOptions(options: unknown): options is ScrollToIndexOptions {
|
|
60
|
-
return typeof options === 'object' && options
|
|
98
|
+
return typeof options === 'object' && options != null && ('align' in options || 'behavior' in options || 'isCorrection' in options);
|
|
61
99
|
}
|
|
62
100
|
|
|
63
101
|
/**
|