@pdanpdan/virtual-scroll 0.4.0 → 0.5.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.
@@ -4,15 +4,15 @@ import { FenwickTree } from './fenwick-tree';
4
4
 
5
5
  describe('fenwickTree', () => {
6
6
  describe('initialization', () => {
7
- it('should initialize with correct size', () => {
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('query and update', () => {
15
- it('should update and query values', () => {
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('should handle updates to existing indices', () => {
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('should ignore updates for out of bounds indices', () => {
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('search and bounds', () => {
44
- it('should find lower bound correctly', () => {
45
- const tree = new FenwickTree(5);
46
- tree.update(0, 10); // sum up to 1: 10
47
- tree.update(1, 10); // sum up to 2: 20
48
- tree.update(2, 10); // sum up to 3: 30
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
- expect(tree.findLowerBound(5)).toBe(0);
51
- expect(tree.findLowerBound(15)).toBe(1);
52
- expect(tree.findLowerBound(25)).toBe(2);
53
- expect(tree.findLowerBound(35)).toBe(5); // Returns size when not found
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 and resize', () => {
58
- it('should set and rebuild correctly', () => {
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('should resize and preserve existing values', () => {
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('values access', () => {
81
- it('should return the individual value at an index', () => {
82
- const tree = new FenwickTree(3);
83
- tree.update(0, 10);
84
- expect(tree.get(0)).toBe(10);
85
- expect(tree.get(-1)).toBe(0);
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
- it('should return the underlying values array', () => {
90
- const tree = new FenwickTree(3);
91
- tree.update(0, 10);
92
- tree.update(1, 20);
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('should do nothing when offset is 0', () => {
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('should shift values forward when offset is positive', () => {
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('should shift values backward when offset is negative', () => {
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);
@@ -3,146 +3,172 @@ import { describe, expect, it } from 'vitest';
3
3
  import { getPaddingX, getPaddingY, isBody, isElement, isScrollableElement, isScrollToIndexOptions, isWindow, isWindowLike } from './scroll';
4
4
 
5
5
  describe('scroll utils', () => {
6
- describe('isWindow', () => {
7
- it('should return true for null', () => {
8
- expect(isWindow(null)).toBe(true);
9
- });
6
+ describe('element type guards', () => {
7
+ describe('iswindow', () => {
8
+ it('returns true for null', () => {
9
+ expect(isWindow(null)).toBe(true);
10
+ });
10
11
 
11
- it('should return true for window object', () => {
12
- expect(isWindow(window)).toBe(true);
13
- });
12
+ it('returns true for window object', () => {
13
+ expect(isWindow(window)).toBe(true);
14
+ });
14
15
 
15
- it('should return false for an element', () => {
16
- const el = document.createElement('div');
17
- expect(isWindow(el)).toBe(false);
18
- });
19
- });
16
+ it('returns true for document.documentelement object', () => {
17
+ expect(isWindow(document.documentElement)).toBe(true);
18
+ });
20
19
 
21
- describe('isBody', () => {
22
- it('should return true for document.body', () => {
23
- expect(isBody(document.body)).toBe(true);
24
- });
20
+ it('returns false for an element', () => {
21
+ const el = document.createElement('div');
22
+ expect(isWindow(el)).toBe(false);
23
+ });
25
24
 
26
- it('should return false for null', () => {
27
- expect(isBody(null)).toBe(false);
25
+ it('returns false for undefined', () => {
26
+ expect(isWindow(undefined)).toBe(false);
27
+ });
28
28
  });
29
29
 
30
- it('should return false for undefined', () => {
31
- expect(isBody(undefined)).toBe(false);
32
- });
30
+ describe('isbody', () => {
31
+ it('returns true for document.body', () => {
32
+ expect(isBody(document.body)).toBe(true);
33
+ });
33
34
 
34
- it('should return false for a string', () => {
35
- // @ts-expect-error testing invalid input
36
- expect(isBody('not an object')).toBe(false);
37
- });
35
+ it('returns false for null', () => {
36
+ expect(isBody(null)).toBe(false);
37
+ });
38
38
 
39
- it('should return false for a plain object', () => {
40
- // @ts-expect-error testing invalid input
41
- expect(isBody({})).toBe(false);
42
- });
39
+ it('returns false for undefined', () => {
40
+ expect(isBody(undefined)).toBe(false);
41
+ });
43
42
 
44
- it('should return false for a div', () => {
45
- const el = document.createElement('div');
46
- expect(isBody(el)).toBe(false);
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
- it('should return false for window', () => {
50
- expect(isBody(window)).toBe(false);
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
- describe('isWindowLike', () => {
55
- it('should return true for window', () => {
56
- expect(isWindowLike(window)).toBe(true);
57
- });
53
+ it('returns false for a div', () => {
54
+ const el = document.createElement('div');
55
+ expect(isBody(el)).toBe(false);
56
+ });
58
57
 
59
- it('should return true for body', () => {
60
- expect(isWindowLike(document.body)).toBe(true);
61
- });
58
+ it('returns false for window', () => {
59
+ expect(isBody(window)).toBe(false);
60
+ });
62
61
 
63
- it('should return true for null', () => {
64
- expect(isWindowLike(null)).toBe(true);
62
+ it('returns false for document.documentelement', () => {
63
+ expect(isBody(document.documentElement)).toBe(false);
64
+ });
65
65
  });
66
66
 
67
- it('should return false for a div', () => {
68
- const el = document.createElement('div');
69
- expect(isWindowLike(el)).toBe(false);
70
- });
71
- });
67
+ describe('iswindowlike', () => {
68
+ it('returns true for window', () => {
69
+ expect(isWindowLike(window)).toBe(true);
70
+ });
72
71
 
73
- describe('isElement', () => {
74
- it('should return true for a div', () => {
75
- const el = document.createElement('div');
76
- expect(isElement(el)).toBe(true);
77
- });
72
+ it('returns true for document.documentelement', () => {
73
+ expect(isWindowLike(document.documentElement)).toBe(true);
74
+ });
78
75
 
79
- it('should return false for window', () => {
80
- expect(isElement(window)).toBe(false);
81
- });
76
+ it('returns true for body', () => {
77
+ expect(isWindowLike(document.body)).toBe(true);
78
+ });
82
79
 
83
- it('should return false for null', () => {
84
- expect(isElement(null)).toBe(false);
85
- });
86
- });
80
+ it('returns true for null', () => {
81
+ expect(isWindowLike(null)).toBe(true);
82
+ });
87
83
 
88
- describe('isScrollableElement', () => {
89
- it('should return true for a div', () => {
90
- const el = document.createElement('div');
91
- expect(isScrollableElement(el)).toBe(true);
84
+ it('returns false for a div', () => {
85
+ const el = document.createElement('div');
86
+ expect(isWindowLike(el)).toBe(false);
87
+ });
92
88
  });
93
89
 
94
- it('should return false for null', () => {
95
- expect(isScrollableElement(null)).toBe(false);
96
- });
97
- });
90
+ describe('iselement', () => {
91
+ it('returns true for a div', () => {
92
+ const el = document.createElement('div');
93
+ expect(isElement(el)).toBe(true);
94
+ });
98
95
 
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
- });
96
+ it('returns true for document.documentelement', () => {
97
+ expect(isElement(document.documentElement)).toBe(true);
98
+ });
105
99
 
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
- });
100
+ it('returns false for window', () => {
101
+ expect(isElement(window)).toBe(false);
102
+ });
112
103
 
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);
104
+ it('returns false for null', () => {
105
+ expect(isElement(null)).toBe(false);
106
+ });
119
107
  });
120
108
 
121
- it('should handle object padding', () => {
122
- expect(getPaddingX({ x: 15 }, 'vertical')).toBe(15);
123
- expect(getPaddingX({ y: 20 }, 'horizontal')).toBe(0);
124
- });
109
+ describe('isscrollableelement', () => {
110
+ it('returns true for a div', () => {
111
+ const el = document.createElement('div');
112
+ expect(isScrollableElement(el)).toBe(true);
113
+ });
125
114
 
126
- it('should return 0 for undefined', () => {
127
- expect(getPaddingX(undefined)).toBe(0);
115
+ it('returns false for null', () => {
116
+ expect(isScrollableElement(null)).toBe(false);
117
+ });
128
118
  });
129
119
  });
130
120
 
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
- });
121
+ describe('options type guards', () => {
122
+ describe('isscrolltoindexoptions', () => {
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
+ });
138
128
 
139
- it('should handle object padding', () => {
140
- expect(getPaddingY({ y: 15 }, 'horizontal')).toBe(15);
141
- expect(getPaddingY({ x: 20 }, 'vertical')).toBe(0);
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
+ });
142
134
  });
135
+ });
143
136
 
144
- it('should return 0 for undefined', () => {
145
- expect(getPaddingY(undefined)).toBe(0);
137
+ describe('padding utilities', () => {
138
+ describe('getpaddingx', () => {
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('getpaddingy', () => {
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
+ });
146
172
  });
147
173
  });
148
174
  });
@@ -1,5 +1,12 @@
1
1
  import type { ScrollDirection, ScrollToIndexOptions } from '../types';
2
2
 
3
+ /**
4
+ * Maximum size (in pixels) for an element that most browsers can handle reliably.
5
+ * Beyond this size, we use scaling for the scrollable area.
6
+ * @default 10000000
7
+ */
8
+ export const BROWSER_MAX_SIZE = 10000000;
9
+
3
10
  /**
4
11
  * Checks if the container is the window object.
5
12
  *
@@ -7,7 +14,7 @@ import type { ScrollDirection, ScrollToIndexOptions } from '../types';
7
14
  * @returns `true` if the container is the global window object.
8
15
  */
9
16
  export function isWindow(container: HTMLElement | Window | null | undefined): container is Window {
10
- return container === null || (typeof window !== 'undefined' && container === window);
17
+ return container === null || container === document.documentElement || (typeof window !== 'undefined' && container === window);
11
18
  }
12
19
 
13
20
  /**
@@ -17,7 +24,7 @@ export function isWindow(container: HTMLElement | Window | null | undefined): co
17
24
  * @returns `true` if the container is the `<body>` element.
18
25
  */
19
26
  export function isBody(container: HTMLElement | Window | null | undefined): container is HTMLElement {
20
- return !!container && typeof container === 'object' && 'tagName' in container && container.tagName === 'BODY';
27
+ return container != null && typeof container === 'object' && 'tagName' in container && container.tagName === 'BODY';
21
28
  }
22
29
 
23
30
  /**
@@ -37,7 +44,7 @@ export function isWindowLike(container: HTMLElement | Window | null | undefined)
37
44
  * @returns `true` if the container is an `HTMLElement`.
38
45
  */
39
46
  export function isElement(container: HTMLElement | Window | null | undefined): container is HTMLElement {
40
- return !!container && 'getBoundingClientRect' in container;
47
+ return container != null && 'getBoundingClientRect' in container;
41
48
  }
42
49
 
43
50
  /**
@@ -47,7 +54,7 @@ export function isElement(container: HTMLElement | Window | null | undefined): c
47
54
  * @returns `true` if the target is an `HTMLElement` with scroll properties.
48
55
  */
49
56
  export function isScrollableElement(target: EventTarget | null): target is HTMLElement {
50
- return !!target && 'scrollLeft' in target;
57
+ return target != null && 'scrollLeft' in target;
51
58
  }
52
59
 
53
60
  /**
@@ -57,7 +64,7 @@ export function isScrollableElement(target: EventTarget | null): target is HTMLE
57
64
  * @returns `true` if the options object contains scroll-to-index specific properties.
58
65
  */
59
66
  export function isScrollToIndexOptions(options: unknown): options is ScrollToIndexOptions {
60
- return typeof options === 'object' && options !== null && ('align' in options || 'behavior' in options || 'isCorrection' in options);
67
+ return typeof options === 'object' && options != null && ('align' in options || 'behavior' in options || 'isCorrection' in options);
61
68
  }
62
69
 
63
70
  /**