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,185 +0,0 @@
1
- /**
2
- * vlist v2 — Autosize Plugin
3
- *
4
- * Enables dynamic item measurement via ResizeObserver for items with
5
- * unknown sizes. Items are rendered without an explicit main-axis size,
6
- * measured once by ResizeObserver, then pinned to their measured size.
7
- *
8
- * Priority 5 — runs before grid/masonry (10) so the measured cache
9
- * is in place before layout plugins consume it.
10
- *
11
- * Requires: `item.estimatedHeight` or `item.estimatedWidth` in config
12
- */
13
- // =============================================================================
14
- // Factory
15
- // =============================================================================
16
- export function autosize(config) {
17
- let gap = config?.gap ?? 0;
18
- let observer = null;
19
- let storedCtx = null;
20
- let engineState;
21
- let hz;
22
- let sizeProp;
23
- let estimatedSize;
24
- const measuredSizes = new Map();
25
- const elementToIndex = new WeakMap();
26
- let pendingScrollDelta = 0;
27
- let pendingContentSizeUpdate = false;
28
- const BOTTOM_THRESHOLD = 2;
29
- function sizeFn(index) {
30
- return measuredSizes.get(index) ?? estimatedSize;
31
- }
32
- function isAtBottom() {
33
- const viewport = storedCtx.dom.viewport;
34
- const maxScroll = viewport.scrollHeight - viewport.clientHeight;
35
- return maxScroll > 0 && engineState.scrollPosition >= maxScroll - BOTTOM_THRESHOLD;
36
- }
37
- function snapToBottom() {
38
- const viewport = storedCtx.dom.viewport;
39
- void viewport.scrollHeight;
40
- const maxScroll = viewport.scrollHeight - viewport.clientHeight;
41
- if (maxScroll > engineState.scrollPosition) {
42
- storedCtx.scrollTo(maxScroll);
43
- }
44
- }
45
- function updateContentSize() {
46
- storedCtx.updateContentSize(storedCtx.sizeCache.getTotalSize());
47
- }
48
- return {
49
- name: "autosize",
50
- priority: 5,
51
- setup(ctx) {
52
- storedCtx = ctx;
53
- engineState = ctx.getState();
54
- hz = ctx.config.horizontal;
55
- sizeProp = hz ? "width" : "height";
56
- if (gap === 0)
57
- gap = ctx.config.gap;
58
- // Read estimated size from the current sizeCache before replacing it.
59
- // The initial cache already has gap baked in — read the raw spec size.
60
- estimatedSize = typeof ctx.rawSizeSpec === "function"
61
- ? ctx.rawSizeSpec(0) + gap
62
- : ctx.rawSizeSpec + gap;
63
- // Replace the fixed sizeCache with a variable one backed by measurements
64
- ctx.setSizeConfig(sizeFn);
65
- if (gap > 0) {
66
- const orig = ctx.sizeCache.getTotalSize;
67
- ctx.sizeCache.getTotalSize = () => {
68
- const t = orig();
69
- return t > 0 ? t - gap : 0;
70
- };
71
- }
72
- // ResizeObserver for measuring items
73
- observer = new ResizeObserver((entries) => {
74
- if (engineState.destroyed || !storedCtx)
75
- return;
76
- let hasNewMeasurements = false;
77
- const firstVisible = engineState.startIndex;
78
- for (const entry of entries) {
79
- const el = entry.target;
80
- const index = elementToIndex.get(el);
81
- if (index === undefined)
82
- continue;
83
- // Verify element wasn't recycled to a different item
84
- if (el.getAttribute("data-index") !== String(index)) {
85
- observer.unobserve(el);
86
- continue;
87
- }
88
- if (measuredSizes.has(index))
89
- continue;
90
- const boxSize = entry.borderBoxSize[0];
91
- if (!boxSize)
92
- continue;
93
- const newSize = hz ? boxSize.inlineSize : boxSize.blockSize;
94
- if (newSize <= 0)
95
- continue;
96
- const sizeWithGap = newSize + gap;
97
- const oldSize = estimatedSize;
98
- measuredSizes.set(index, sizeWithGap);
99
- hasNewMeasurements = true;
100
- if (index < firstVisible && sizeWithGap !== oldSize) {
101
- pendingScrollDelta += sizeWithGap - oldSize;
102
- }
103
- observer.unobserve(el);
104
- // Pin the element to its measured size
105
- el.style[sizeProp] = `${newSize}px`;
106
- }
107
- if (!hasNewMeasurements)
108
- return;
109
- const atBottom = isAtBottom();
110
- // Rebuild prefix sums with new measurements
111
- ctx.rebuildSizeCache();
112
- // Apply scroll correction for items above viewport
113
- if (pendingScrollDelta) {
114
- ctx.scrollTo(engineState.scrollPosition + pendingScrollDelta);
115
- pendingScrollDelta = 0;
116
- }
117
- const isScrolling = engineState.scrollDirection !== 0;
118
- const nearEnd = engineState.totalItems > 0
119
- && engineState.prevRangeEnd >= engineState.totalItems - 1;
120
- if (atBottom || nearEnd || !isScrolling) {
121
- updateContentSize();
122
- pendingContentSizeUpdate = false;
123
- if (atBottom) {
124
- snapToBottom();
125
- }
126
- }
127
- else {
128
- pendingContentSizeUpdate = true;
129
- }
130
- ctx.forceRender();
131
- });
132
- // Public methods
133
- ctx.registerMethod("isMeasured", (index) => measuredSizes.has(index));
134
- ctx.registerMethod("setMeasuredSize", (index, size) => {
135
- measuredSizes.set(index, size);
136
- });
137
- ctx.registerMethod("getMeasuredCount", () => measuredSizes.size);
138
- // Cleanup
139
- ctx.registerDestroyHandler(() => {
140
- if (observer) {
141
- observer.disconnect();
142
- observer = null;
143
- }
144
- });
145
- },
146
- hooks: {
147
- onCommit(state) {
148
- if (!observer || !storedCtx)
149
- return;
150
- for (let i = 0; i < state.visibleCount; i++) {
151
- const idx = state.visibleIndices[i];
152
- if (measuredSizes.has(idx))
153
- continue;
154
- const el = storedCtx.getRenderedElement(idx);
155
- if (!el)
156
- continue;
157
- // Clear the explicit size set by phase2Commit so
158
- // ResizeObserver can measure the natural content size.
159
- el.style[sizeProp] = "";
160
- elementToIndex.set(el, idx);
161
- observer.observe(el);
162
- }
163
- },
164
- onIdle() {
165
- if (!pendingContentSizeUpdate || !storedCtx)
166
- return;
167
- const atBottom = isAtBottom();
168
- updateContentSize();
169
- pendingContentSizeUpdate = false;
170
- if (atBottom) {
171
- snapToBottom();
172
- storedCtx.forceRender();
173
- }
174
- },
175
- },
176
- destroy() {
177
- if (observer) {
178
- observer.disconnect();
179
- observer = null;
180
- }
181
- measuredSizes.clear();
182
- storedCtx = null;
183
- },
184
- };
185
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * vlist v2 — Grid Plugin
3
- */
4
- export { grid } from "./plugin";
5
- export { createGridLayout, } from "./layout";
@@ -1,275 +0,0 @@
1
- /**
2
- * vlist - Grid Layout
3
- * Pure O(1) calculations for mapping between flat item indices and grid positions.
4
- *
5
- * The grid transforms a flat list into rows:
6
- * Items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
7
- * Grid (columns=4):
8
- * Row 0: [0, 1, 2, 3]
9
- * Row 1: [4, 5, 6, 7]
10
- * Row 2: [8, 9] ← partially filled last row
11
- *
12
- * All operations are O(1) — integer division and modulo only.
13
- */
14
- // =============================================================================
15
- // Factory
16
- // =============================================================================
17
- /**
18
- * Create a GridLayout instance.
19
- *
20
- * @param config - Grid configuration (columns, gap, optional isHeaderFn)
21
- * @returns GridLayout with O(1) mapping functions (or groups-aware if isHeaderFn provided)
22
- */
23
- export const createGridLayout = (config) => {
24
- let columns = Math.max(1, Math.floor(config.columns));
25
- let gap = config.gap ?? 0;
26
- let isHeaderFn = config.isHeaderFn;
27
- // Reusable position object to avoid allocation on hot paths
28
- const reusablePosition = { row: 0, col: 0 };
29
- /**
30
- * Total rows for a given item count.
31
- * When isHeaderFn is provided, headers force new rows.
32
- */
33
- const getTotalRows = (totalItems) => {
34
- if (totalItems <= 0)
35
- return 0;
36
- if (!isHeaderFn) {
37
- return Math.ceil(totalItems / columns);
38
- }
39
- // Groups-aware calculation
40
- let row = 0;
41
- let colInRow = 0;
42
- let headerCount = 0;
43
- for (let i = 0; i < totalItems; i++) {
44
- if (isHeaderFn(i)) {
45
- headerCount++;
46
- // Header: start new row if not at beginning
47
- if (colInRow) {
48
- row++;
49
- colInRow = 0;
50
- }
51
- // Header occupies its own row
52
- row++;
53
- colInRow = 0;
54
- }
55
- else {
56
- // Regular item
57
- colInRow++;
58
- if (colInRow >= columns) {
59
- row++;
60
- colInRow = 0;
61
- }
62
- }
63
- }
64
- // Add final row if there are items in it
65
- if (colInRow) {
66
- row++;
67
- }
68
- return row;
69
- };
70
- /**
71
- * Get row/col position for a flat item index.
72
- * Reuses a single object to reduce GC pressure on scroll hot path.
73
- */
74
- const getPosition = (itemIndex) => {
75
- reusablePosition.row = getRow(itemIndex);
76
- reusablePosition.col = getCol(itemIndex);
77
- return reusablePosition;
78
- };
79
- /**
80
- * Get row index for a flat item index — O(1)
81
- * When isHeaderFn is provided, headers force new rows and span all columns.
82
- */
83
- const getRow = (itemIndex) => {
84
- if (!isHeaderFn) {
85
- return Math.floor(itemIndex / columns);
86
- }
87
- // Groups-aware calculation
88
- let row = 0;
89
- let colInRow = 0;
90
- for (let i = 0; i <= itemIndex; i++) {
91
- const isHeader = isHeaderFn(i);
92
- if (isHeader) {
93
- // Header: start new row if not at beginning
94
- if (colInRow) {
95
- row++;
96
- colInRow = 0;
97
- }
98
- // Header occupies its own row
99
- if (i === itemIndex) {
100
- return row;
101
- }
102
- row++;
103
- colInRow = 0;
104
- }
105
- else {
106
- // Regular item
107
- if (i === itemIndex) {
108
- return row;
109
- }
110
- colInRow++;
111
- if (colInRow >= columns) {
112
- row++;
113
- colInRow = 0;
114
- }
115
- }
116
- }
117
- console.warn(`⚠️ getRow(${itemIndex}) fell through - returning ${row}`);
118
- return row;
119
- };
120
- /**
121
- * Get column index for a flat item index — O(1)
122
- * Headers always return col 0 when isHeaderFn is provided.
123
- */
124
- const getCol = (itemIndex) => {
125
- if (!isHeaderFn) {
126
- return itemIndex % columns;
127
- }
128
- // Headers always at column 0
129
- if (isHeaderFn(itemIndex)) {
130
- return 0;
131
- }
132
- // Calculate column for regular items
133
- let colInRow = 0;
134
- for (let i = 0; i <= itemIndex; i++) {
135
- const isHeader = isHeaderFn(i);
136
- if (isHeader) {
137
- // Header: reset column counter
138
- colInRow = 0;
139
- }
140
- else {
141
- if (i === itemIndex) {
142
- return colInRow;
143
- }
144
- colInRow++;
145
- if (colInRow >= columns) {
146
- colInRow = 0;
147
- }
148
- }
149
- }
150
- return colInRow;
151
- };
152
- /**
153
- * Get the flat item range [start, end] for a range of rows.
154
- *
155
- * rowStart and rowEnd are inclusive row indices.
156
- * The returned end is clamped to totalItems - 1.
157
- * When isHeaderFn is provided, this accounts for headers disrupting the grid flow.
158
- */
159
- const getItemRange = (rowStart, rowEnd, totalItems) => {
160
- if (totalItems <= 0)
161
- return { start: 0, end: -1 };
162
- if (!isHeaderFn) {
163
- // Simple O(1) calculation for regular grids
164
- const start = Math.max(0, rowStart * columns);
165
- const end = Math.min(totalItems - 1, (rowEnd + 1) * columns - 1);
166
- return { start, end };
167
- }
168
- // Groups-aware calculation - find items that fall in the row range
169
- let start = -1;
170
- let end = -1;
171
- let currentRow = 0;
172
- let colInRow = 0;
173
- for (let i = 0; i < totalItems; i++) {
174
- const isHeader = isHeaderFn(i);
175
- if (isHeader) {
176
- // Header: start new row if not at beginning
177
- if (colInRow) {
178
- currentRow++;
179
- colInRow = 0;
180
- }
181
- // Check if this header's row is in range
182
- if (currentRow >= rowStart && currentRow <= rowEnd) {
183
- if (start < 0)
184
- start = i;
185
- end = i;
186
- }
187
- currentRow++;
188
- colInRow = 0;
189
- }
190
- else {
191
- // Regular item
192
- if (currentRow >= rowStart && currentRow <= rowEnd) {
193
- if (start < 0)
194
- start = i;
195
- end = i;
196
- }
197
- colInRow++;
198
- if (colInRow >= columns) {
199
- currentRow++;
200
- colInRow = 0;
201
- }
202
- }
203
- // Early exit if we're past the end row
204
- if (currentRow > rowEnd && !colInRow) {
205
- break;
206
- }
207
- }
208
- // If no items found in range, return empty range
209
- if (start < 0) {
210
- return { start: 0, end: -1 };
211
- }
212
- return { start, end };
213
- };
214
- /**
215
- * Get the flat item index from a row and column.
216
- * Returns -1 if the position is out of bounds.
217
- */
218
- const getItemIndex = (row, col, totalItems) => {
219
- if (col < 0 || col >= columns)
220
- return -1;
221
- const index = row * columns + col;
222
- if (index < 0 || index >= totalItems)
223
- return -1;
224
- return index;
225
- };
226
- /**
227
- * Calculate column width given the container's inner width.
228
- * Distributes gaps evenly: totalGapWidth = (columns - 1) * gap
229
- * columnWidth = (containerWidth - totalGapWidth) / columns
230
- */
231
- const getColumnWidth = (containerWidth) => {
232
- const totalGap = (columns - 1) * gap;
233
- return Math.max(0, (containerWidth - totalGap) / columns);
234
- };
235
- /**
236
- * Calculate the X pixel offset for a given column index.
237
- * offset = col * (columnWidth + gap)
238
- */
239
- const getColumnOffset = (col, containerWidth) => {
240
- const colWidth = getColumnWidth(containerWidth);
241
- return col * (colWidth + gap);
242
- };
243
- /**
244
- * Update grid configuration without recreating the layout.
245
- * This is more efficient than destroying and recreating.
246
- */
247
- const updateConfig = (newConfig) => {
248
- if (newConfig.columns !== undefined) {
249
- columns = Math.max(1, Math.floor(newConfig.columns));
250
- }
251
- if (newConfig.gap !== undefined) {
252
- gap = newConfig.gap;
253
- }
254
- if (newConfig.isHeaderFn !== undefined) {
255
- isHeaderFn = newConfig.isHeaderFn;
256
- }
257
- };
258
- return {
259
- get columns() {
260
- return columns;
261
- },
262
- get gap() {
263
- return gap;
264
- },
265
- update: updateConfig,
266
- getTotalRows,
267
- getPosition,
268
- getRow,
269
- getCol,
270
- getItemRange,
271
- getItemIndex,
272
- getColumnWidth,
273
- getColumnOffset,
274
- };
275
- };