vlist 2.0.0 → 2.0.2

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.
Files changed (91) hide show
  1. package/README.github.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/core/dom.d.ts +1 -1
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/pipeline.d.ts +2 -2
  6. package/dist/core/scroll.d.ts +1 -1
  7. package/dist/core/types.d.ts +7 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -28
  10. package/dist/internals.js +1 -60
  11. package/dist/plugins/scrollbar/controller.d.ts +3 -3
  12. package/dist/plugins/scrollbar/scrollbar.d.ts +2 -2
  13. package/dist/rendering/renderer.d.ts +2 -2
  14. package/dist/rendering/viewport.d.ts +1 -1
  15. package/dist/size.json +1 -1
  16. package/dist/types.d.ts +1 -1
  17. package/package.json +1 -1
  18. package/dist/constants.js +0 -83
  19. package/dist/core/create.js +0 -740
  20. package/dist/core/dom.js +0 -47
  21. package/dist/core/hooks.js +0 -67
  22. package/dist/core/index.js +0 -13
  23. package/dist/core/pipeline.js +0 -307
  24. package/dist/core/pool.js +0 -42
  25. package/dist/core/scroll.js +0 -137
  26. package/dist/core/sizes.js +0 -6
  27. package/dist/core/state.js +0 -56
  28. package/dist/core/types.js +0 -7
  29. package/dist/core/velocity.js +0 -33
  30. package/dist/events/emitter.js +0 -60
  31. package/dist/events/index.js +0 -6
  32. package/dist/plugins/a11y/index.js +0 -1
  33. package/dist/plugins/a11y/plugin.js +0 -259
  34. package/dist/plugins/async/index.js +0 -12
  35. package/dist/plugins/async/manager.js +0 -568
  36. package/dist/plugins/async/placeholder.js +0 -154
  37. package/dist/plugins/async/plugin.js +0 -311
  38. package/dist/plugins/async/sparse.js +0 -540
  39. package/dist/plugins/autosize/index.js +0 -4
  40. package/dist/plugins/autosize/plugin.js +0 -185
  41. package/dist/plugins/grid/index.js +0 -5
  42. package/dist/plugins/grid/layout.js +0 -275
  43. package/dist/plugins/grid/plugin.js +0 -347
  44. package/dist/plugins/grid/renderer.js +0 -525
  45. package/dist/plugins/grid/types.js +0 -11
  46. package/dist/plugins/groups/async-bridge.js +0 -246
  47. package/dist/plugins/groups/index.js +0 -13
  48. package/dist/plugins/groups/layout.js +0 -294
  49. package/dist/plugins/groups/plugin.js +0 -571
  50. package/dist/plugins/groups/sticky.js +0 -255
  51. package/dist/plugins/groups/types.js +0 -12
  52. package/dist/plugins/masonry/index.js +0 -6
  53. package/dist/plugins/masonry/layout.js +0 -261
  54. package/dist/plugins/masonry/plugin.js +0 -381
  55. package/dist/plugins/masonry/renderer.js +0 -354
  56. package/dist/plugins/masonry/types.js +0 -9
  57. package/dist/plugins/page/index.js +0 -5
  58. package/dist/plugins/page/plugin.js +0 -166
  59. package/dist/plugins/scale/index.js +0 -4
  60. package/dist/plugins/scale/plugin.js +0 -507
  61. package/dist/plugins/scrollbar/controller.js +0 -574
  62. package/dist/plugins/scrollbar/index.js +0 -6
  63. package/dist/plugins/scrollbar/plugin.js +0 -93
  64. package/dist/plugins/scrollbar/scrollbar.js +0 -556
  65. package/dist/plugins/selection/index.js +0 -7
  66. package/dist/plugins/selection/plugin.js +0 -601
  67. package/dist/plugins/selection/state.js +0 -332
  68. package/dist/plugins/snapshots/index.js +0 -5
  69. package/dist/plugins/snapshots/plugin.js +0 -301
  70. package/dist/plugins/sortable/index.js +0 -6
  71. package/dist/plugins/sortable/plugin.js +0 -753
  72. package/dist/plugins/table/header.js +0 -501
  73. package/dist/plugins/table/index.js +0 -12
  74. package/dist/plugins/table/layout.js +0 -211
  75. package/dist/plugins/table/plugin.js +0 -391
  76. package/dist/plugins/table/renderer.js +0 -625
  77. package/dist/plugins/table/types.js +0 -12
  78. package/dist/plugins/transition/index.js +0 -5
  79. package/dist/plugins/transition/plugin.js +0 -405
  80. package/dist/rendering/aria.js +0 -23
  81. package/dist/rendering/index.js +0 -18
  82. package/dist/rendering/measured.js +0 -98
  83. package/dist/rendering/renderer.js +0 -586
  84. package/dist/rendering/scale.js +0 -267
  85. package/dist/rendering/scroll.js +0 -71
  86. package/dist/rendering/sizes.js +0 -193
  87. package/dist/rendering/sort.js +0 -65
  88. package/dist/rendering/viewport.js +0 -268
  89. package/dist/types.js +0 -5
  90. package/dist/utils/padding.js +0 -49
  91. package/dist/utils/stats.js +0 -124
@@ -1,268 +0,0 @@
1
- /**
2
- * vlist - Virtual Scrolling Core
3
- * Pure functions for virtual scroll calculations
4
- *
5
- * Compression support is NOT imported here — it's injected via
6
- * CompressionState parameters. When compression is inactive
7
- * (the common case), all calculations use simple size-cache math
8
- * with zero dependency on the compression module.
9
- *
10
- * This keeps the builder core lightweight. The withCompression feature
11
- * and the monolithic createVList entry point import compression
12
- * separately and pass the state in.
13
- */
14
- /**
15
- * A "no compression" state for lists that don't need it.
16
- * Used by the builder core when withCompression is not installed.
17
- */
18
- export const NO_COMPRESSION = {
19
- isCompressed: false,
20
- actualSize: 0,
21
- virtualSize: 0,
22
- ratio: 1,
23
- };
24
- /**
25
- * Create a trivial compression state from a size cache.
26
- * No compression logic — just reads the total height.
27
- * For use when the full compression module is not loaded.
28
- */
29
- export const getSimpleCompressionState = (_totalItems, sizeCache) => {
30
- const h = sizeCache.getTotalSize();
31
- return {
32
- isCompressed: false,
33
- actualSize: h,
34
- virtualSize: h,
35
- ratio: 1,
36
- };
37
- };
38
- // =============================================================================
39
- // Simple (non-compressed) range calculation
40
- // =============================================================================
41
- /**
42
- * Calculate visible range using size cache lookups.
43
- * Fast path for lists that don't need compression (< ~350 000 items at 48px).
44
- * Mutates `out` to avoid allocation on the scroll hot path.
45
- */
46
- export const simpleVisibleRange = (scrollPosition, containerHeight, sizeCache, totalItems, _compression, out) => {
47
- if (totalItems === 0 || containerHeight === 0) {
48
- out.start = 0;
49
- out.end = -1;
50
- return out;
51
- }
52
- const start = sizeCache.indexAtOffset(scrollPosition);
53
- let end = sizeCache.indexAtOffset(scrollPosition + containerHeight);
54
- if (end < totalItems - 1)
55
- end++;
56
- out.start = Math.max(0, start);
57
- out.end = Math.min(totalItems - 1, Math.max(0, end));
58
- return out;
59
- };
60
- /**
61
- * Calculate render range (adds overscan around visible range).
62
- * This function is compression-agnostic — works for both paths.
63
- * Mutates `out` to avoid allocation on the scroll hot path.
64
- */
65
- export const calculateRenderRange = (visibleRange, overscan, totalItems, out) => {
66
- if (totalItems === 0) {
67
- out.start = 0;
68
- out.end = -1;
69
- return out;
70
- }
71
- out.start = Math.max(0, visibleRange.start - overscan);
72
- out.end = Math.min(totalItems - 1, visibleRange.end + overscan);
73
- return out;
74
- };
75
- /**
76
- * Simple scroll-to-index calculation (non-compressed).
77
- * Uses size cache offsets directly.
78
- */
79
- export const simpleScrollToIndex = (index, sizeCache, containerHeight, totalItems, _compression, align) => {
80
- if (totalItems === 0)
81
- return 0;
82
- const safeIndex = Math.max(0, Math.min(index, totalItems - 1));
83
- const itemOffset = sizeCache.getOffset(safeIndex);
84
- const itemSize = sizeCache.getSize(safeIndex);
85
- const totalHeight = sizeCache.getTotalSize();
86
- const maxScroll = Math.max(0, totalHeight - containerHeight);
87
- let position;
88
- switch (align) {
89
- case "center":
90
- position = itemOffset - containerHeight / 2 + itemSize / 2;
91
- break;
92
- case "end":
93
- position = itemOffset - containerHeight + itemSize;
94
- break;
95
- case "start":
96
- default:
97
- position = itemOffset;
98
- break;
99
- }
100
- return Math.max(0, Math.min(position, maxScroll));
101
- };
102
- // =============================================================================
103
- // Calculate total content height
104
- // =============================================================================
105
- /**
106
- * Calculate total content height.
107
- * Uses compression's virtualSize when compressed, raw height otherwise.
108
- */
109
- export const calculateTotalSize = (_totalItems, sizeCache, compression) => {
110
- if (compression && compression.isCompressed) {
111
- return compression.virtualSize;
112
- }
113
- return sizeCache.getTotalSize();
114
- };
115
- /**
116
- * Calculate actual total size (without compression cap)
117
- */
118
- export const calculateActualSize = (_totalItems, sizeCache) => {
119
- return sizeCache.getTotalSize();
120
- };
121
- /**
122
- * Calculate the offset (translateY) for an item
123
- * For non-compressed lists only
124
- */
125
- export const calculateItemOffset = (index, sizeCache) => {
126
- return sizeCache.getOffset(index);
127
- };
128
- // =============================================================================
129
- // Scroll helpers
130
- // =============================================================================
131
- /**
132
- * Clamp scroll position to valid range
133
- */
134
- export const clampScrollPosition = (scrollPosition, totalHeight, containerHeight) => {
135
- const maxScroll = Math.max(0, totalHeight - containerHeight);
136
- return Math.max(0, Math.min(scrollPosition, maxScroll));
137
- };
138
- /**
139
- * Determine scroll direction
140
- */
141
- export const getScrollDirection = (currentScrollTop, previousScrollTop) => {
142
- return currentScrollTop >= previousScrollTop ? "down" : "up";
143
- };
144
- // =============================================================================
145
- // Viewport State
146
- // =============================================================================
147
- /**
148
- * Create initial viewport state.
149
- *
150
- * Accepts an optional `visibleRangeFn` so that compression-aware callers
151
- * can inject the compressed version. Defaults to `simpleVisibleRange`.
152
- */
153
- export const createViewportState = (containerHeight, sizeCache, totalItems, overscan, compression, visibleRangeFn = simpleVisibleRange) => {
154
- const visibleRange = { start: 0, end: 0 };
155
- const renderRange = { start: 0, end: 0 };
156
- visibleRangeFn(0, containerHeight, sizeCache, totalItems, compression, visibleRange);
157
- calculateRenderRange(visibleRange, overscan, totalItems, renderRange);
158
- return {
159
- scrollPosition: 0,
160
- containerSize: containerHeight,
161
- totalSize: compression.virtualSize,
162
- actualSize: compression.actualSize,
163
- isCompressed: compression.isCompressed,
164
- compressionRatio: compression.ratio,
165
- visibleRange,
166
- renderRange,
167
- };
168
- };
169
- /**
170
- * Update viewport state after scroll.
171
- * Mutates state in place for performance on the scroll hot path.
172
- */
173
- export const updateViewportState = (state, scrollPosition, sizeCache, totalItems, overscan, compression, visibleRangeFn = simpleVisibleRange) => {
174
- visibleRangeFn(scrollPosition, state.containerSize, sizeCache, totalItems, compression, state.visibleRange);
175
- calculateRenderRange(state.visibleRange, overscan, totalItems, state.renderRange);
176
- state.scrollPosition = scrollPosition;
177
- return state;
178
- };
179
- /**
180
- * Update viewport state when container resizes.
181
- * Mutates state in place for performance.
182
- */
183
- export const updateViewportSize = (state, containerHeight, sizeCache, totalItems, overscan, compression, visibleRangeFn = simpleVisibleRange) => {
184
- visibleRangeFn(state.scrollPosition, containerHeight, sizeCache, totalItems, compression, state.visibleRange);
185
- calculateRenderRange(state.visibleRange, overscan, totalItems, state.renderRange);
186
- state.containerSize = containerHeight;
187
- state.totalSize = compression.virtualSize;
188
- state.actualSize = compression.actualSize;
189
- state.isCompressed = compression.isCompressed;
190
- state.compressionRatio = compression.ratio;
191
- return state;
192
- };
193
- /**
194
- * Update viewport state when total items changes.
195
- * Mutates state in place for performance.
196
- */
197
- export const updateViewportItems = (state, sizeCache, totalItems, overscan, compression, visibleRangeFn = simpleVisibleRange) => {
198
- visibleRangeFn(state.scrollPosition, state.containerSize, sizeCache, totalItems, compression, state.visibleRange);
199
- calculateRenderRange(state.visibleRange, overscan, totalItems, state.renderRange);
200
- state.totalSize = compression.virtualSize;
201
- state.actualSize = compression.actualSize;
202
- state.isCompressed = compression.isCompressed;
203
- state.compressionRatio = compression.ratio;
204
- return state;
205
- };
206
- // =============================================================================
207
- // calculateScrollToIndex (public API)
208
- // =============================================================================
209
- /**
210
- * Calculate scroll position to bring an index into view.
211
- *
212
- * Accepts an optional `scrollToIndexFn` so that compression-aware callers
213
- * can inject the compressed version. Defaults to `simpleScrollToIndex`.
214
- */
215
- export const calculateScrollToIndex = (index, sizeCache, containerHeight, totalItems, align = "start", compression, scrollToIndexFn = simpleScrollToIndex) => {
216
- return scrollToIndexFn(index, sizeCache, containerHeight, totalItems, compression, align);
217
- };
218
- // =============================================================================
219
- // Range Utilities
220
- // =============================================================================
221
- /**
222
- * Check if two ranges are equal
223
- */
224
- export const rangesEqual = (a, b) => {
225
- return a.start === b.start && a.end === b.end;
226
- };
227
- /**
228
- * Check if an index is within a range
229
- */
230
- export const isInRange = (index, range) => {
231
- return index >= range.start && index <= range.end;
232
- };
233
- /**
234
- * Get the count of items in a range
235
- */
236
- export const getRangeCount = (range) => {
237
- if (range.end < range.start)
238
- return 0;
239
- return range.end - range.start + 1;
240
- };
241
- /**
242
- * Create an array of indices from a range
243
- */
244
- export const rangeToIndices = (range) => {
245
- const indices = [];
246
- for (let i = range.start; i <= range.end; i++) {
247
- indices.push(i);
248
- }
249
- return indices;
250
- };
251
- /**
252
- * Calculate which indices need to be added/removed when range changes
253
- */
254
- export const diffRanges = (oldRange, newRange) => {
255
- const add = [];
256
- const remove = [];
257
- for (let i = oldRange.start; i <= oldRange.end; i++) {
258
- if (i < newRange.start || i > newRange.end) {
259
- remove.push(i);
260
- }
261
- }
262
- for (let i = newRange.start; i <= newRange.end; i++) {
263
- if (i < oldRange.start || i > oldRange.end) {
264
- add.push(i);
265
- }
266
- }
267
- return { add, remove };
268
- };
package/dist/types.js DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * vlist - Core Types
3
- * Minimal, clean interfaces for the virtual list
4
- */
5
- export {};
@@ -1,49 +0,0 @@
1
- /**
2
- * vlist/utils — Padding Resolution
3
- * Shared helpers for resolving the CSS-shorthand padding config into
4
- * concrete pixel values. Used by core (to apply CSS) and by plugins
5
- * (to subtract cross-axis padding from container dimensions).
6
- */
7
- // =============================================================================
8
- // Resolve All Sides
9
- // =============================================================================
10
- /** Zero padding singleton — avoids allocation when padding is undefined */
11
- const ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
12
- /**
13
- * Resolve a padding config into all four sides.
14
- *
15
- * - `undefined` → { 0, 0, 0, 0 }
16
- * - `number` → equal on all sides
17
- * - `[v, h]` → top/bottom = v, left/right = h
18
- * - `[t, r, b, l]` → per-side (CSS order)
19
- *
20
- * Returns a frozen singleton for the zero case (no allocation).
21
- */
22
- export const resolvePadding = (padding) => {
23
- if (padding == null)
24
- return ZERO;
25
- if (typeof padding === "number") {
26
- return padding === 0 ? ZERO : { top: padding, right: padding, bottom: padding, left: padding };
27
- }
28
- if (padding.length === 2) {
29
- const [v, h] = padding;
30
- return (v === 0 && h === 0) ? ZERO : { top: v, right: h, bottom: v, left: h };
31
- }
32
- const [top, right, bottom, left] = padding;
33
- return (top === 0 && right === 0 && bottom === 0 && left === 0)
34
- ? ZERO
35
- : { top, right, bottom, left };
36
- };
37
- // =============================================================================
38
- // Axis Helpers
39
- // =============================================================================
40
- /**
41
- * Total padding along the main axis (scroll direction).
42
- * Vertical → top + bottom. Horizontal → left + right.
43
- */
44
- export const mainAxisPaddingFrom = (p, isHorizontal) => isHorizontal ? p.left + p.right : p.top + p.bottom;
45
- /**
46
- * Total padding along the cross axis (perpendicular to scroll).
47
- * Vertical → left + right. Horizontal → top + bottom.
48
- */
49
- export const crossAxisPaddingFrom = (p, isHorizontal) => isHorizontal ? p.top + p.bottom : p.left + p.right;
@@ -1,124 +0,0 @@
1
- // src/utils/stats.ts
2
- // Pure computation module for scroll statistics.
3
- // Tracks velocity (current + running average), computes cumulative item count
4
- // and progress from scroll position using geometric mapping.
5
- //
6
- // No DOM access, no RAF, no side effects — purely functional state tracker.
7
- import { MAX_VIRTUAL_SIZE } from "../constants";
8
- // =============================================================================
9
- // Constants
10
- // =============================================================================
11
- const MAX_VELOCITY = 50;
12
- const MIN_VELOCITY = 0.1;
13
- // =============================================================================
14
- // Factory
15
- // =============================================================================
16
- /**
17
- * Create a stats tracker.
18
- *
19
- * All inputs are provided via callbacks so the tracker always reflects
20
- * the latest values without needing to be recreated when the list changes.
21
- *
22
- * ```ts
23
- * const stats = createStats({
24
- * getScrollPosition: () => list.getScrollPosition(),
25
- * getTotal: () => items.length,
26
- * getItemSize: () => ITEM_HEIGHT,
27
- * getContainerSize: () => containerEl.clientHeight,
28
- * })
29
- *
30
- * const { progress, itemCount, total } = stats.getState()
31
- * ```
32
- */
33
- export function createStats(config) {
34
- // ───────────────────────────────────────────────────────────────────────────
35
- // Velocity state
36
- // ───────────────────────────────────────────────────────────────────────────
37
- let currentVelocity = 0;
38
- let velocitySum = 0;
39
- let velocityCount = 0;
40
- function getVelocityAverage() {
41
- return velocityCount > 0 ? velocitySum / velocityCount : 0;
42
- }
43
- function onVelocity(velocity) {
44
- currentVelocity = velocity;
45
- if (velocity > MIN_VELOCITY && velocity < MAX_VELOCITY) {
46
- velocitySum += velocity;
47
- velocityCount++;
48
- }
49
- }
50
- // ───────────────────────────────────────────────────────────────────────────
51
- // Geometric item count
52
- // ───────────────────────────────────────────────────────────────────────────
53
- /**
54
- * Compute cumulative item count from scroll position geometrically.
55
- *
56
- * scrollPosition is the DOM scrollTop/scrollLeft — in compressed/scaled
57
- * mode this is the virtual (compressed) scroll position. We map it back
58
- * to real item indices using the same linear mapping vlist uses internally:
59
- *
60
- * columns = getColumns() ?? 1
61
- * totalRows = ceil(total / columns)
62
- * totalActualSize = totalRows × itemSize
63
- * totalVirtualSize = min(totalActualSize, MAX_VIRTUAL_SIZE)
64
- * maxVirtualScroll = totalVirtualSize − containerSize
65
- * maxActualScroll = totalActualSize − containerSize
66
- * ratio = maxActualScroll / maxVirtualScroll
67
- * actualOffset = scrollPosition × ratio
68
- * visibleRows = ceil((actualOffset + containerSize) / itemSize)
69
- * itemCount = min(visibleRows × columns, total)
70
- *
71
- * containerSize is provided by the caller (clientHeight for vertical,
72
- * clientWidth for horizontal).
73
- *
74
- * Using scroll-range ratio (not size ratio) ensures that at max scroll
75
- * the end of the viewport aligns exactly with the last row/column.
76
- * For grid/masonry, rows are converted to items via the column multiplier.
77
- */
78
- function getItemCount() {
79
- const total = config.getTotal();
80
- if (total === 0)
81
- return 0;
82
- const itemSize = config.getItemSize();
83
- if (itemSize <= 0)
84
- return 0;
85
- const containerSize = config.getContainerSize();
86
- if (containerSize <= 0)
87
- return 0;
88
- const scrollPosition = config.getScrollPosition();
89
- // For grid/masonry layouts, vlist virtualizes rows — each row holds
90
- // `columns` items. Convert total items → total rows for the geometric
91
- // mapping, then convert visible rows back to items at the end.
92
- const columns = typeof config.getColumns === "function" ? config.getColumns() : 1;
93
- const totalRows = Math.ceil(total / columns);
94
- // Map virtual scroll position back to actual content offset.
95
- // Use scroll-range ratio so maxScroll maps exactly to the last row.
96
- const totalActualSize = totalRows * itemSize;
97
- const totalVirtualSize = Math.min(totalActualSize, MAX_VIRTUAL_SIZE);
98
- const maxVirtualScroll = totalVirtualSize - containerSize;
99
- const maxActualScroll = totalActualSize - containerSize;
100
- const ratio = maxVirtualScroll > 0 ? maxActualScroll / maxVirtualScroll : 1;
101
- const actualOffset = scrollPosition * ratio;
102
- const lastVisibleRow = Math.ceil((actualOffset + containerSize) / itemSize);
103
- return Math.min(lastVisibleRow * columns, total);
104
- }
105
- // ───────────────────────────────────────────────────────────────────────────
106
- // Public API
107
- // ───────────────────────────────────────────────────────────────────────────
108
- function getState() {
109
- const total = config.getTotal();
110
- const itemCount = getItemCount();
111
- const progress = total > 0 ? Math.min(100, Math.max(0, (itemCount / total) * 100)) : 0;
112
- return {
113
- progress,
114
- velocity: currentVelocity,
115
- velocityAvg: getVelocityAverage(),
116
- itemCount,
117
- total,
118
- };
119
- }
120
- return {
121
- getState,
122
- onVelocity,
123
- };
124
- }