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,405 +0,0 @@
1
- /**
2
- * vlist v2 — Transition Plugin
3
- *
4
- * FLIP-based enter/exit animations for insertItem and removeItem.
5
- * Without this plugin, insert/remove are instantaneous.
6
- *
7
- * Priority: 45 (before selection, after most layout plugins)
8
- */
9
- const DEFAULT_DURATION = 200;
10
- const MAX_DURATION = 1000;
11
- const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
12
- function resolveTiming(base, override) {
13
- if (override === false)
14
- return null;
15
- return {
16
- duration: Math.min(override?.duration ?? base.duration, MAX_DURATION),
17
- easing: override?.easing ?? base.easing,
18
- };
19
- }
20
- // =============================================================================
21
- // Factory
22
- // =============================================================================
23
- export function transition(config) {
24
- const baseDuration = config?.duration ?? DEFAULT_DURATION;
25
- const baseEasing = config?.easing ?? DEFAULT_EASING;
26
- const base = { duration: baseDuration, easing: baseEasing };
27
- const removeTiming = resolveTiming(base, config?.remove);
28
- const insertTiming = resolveTiming(base, config?.insert);
29
- let removePending = null;
30
- let addPending = null;
31
- return {
32
- name: "transition",
33
- conflicts: ["grid", "table", "masonry"],
34
- priority: 45,
35
- setup(ctx) {
36
- const { sizeCache: sc, emitter } = ctx;
37
- const cfg = ctx.config;
38
- const dom = ctx.dom;
39
- const state = ctx.getState();
40
- const origin = cfg.reverse ? "bottom center" : "top center";
41
- const prop = cfg.horizontal ? "translateX" : "translateY";
42
- const toLayout = ctx.getMethod("_dataToLayoutIndex") ?? null;
43
- const dataToLayout = (dataIndex) => toLayout ? toLayout(dataIndex) : dataIndex;
44
- const findById = (id) => {
45
- const eid = String(id).replace(/"/g, '\\"');
46
- const el = dom.content.querySelector(`[data-id="${eid}"]`);
47
- const layoutIndex = el ? parseInt(el.dataset.index ?? "-1", 10) : -1;
48
- return { el, layoutIndex };
49
- };
50
- const commitStyles = () => {
51
- const children = dom.content.children;
52
- for (let i = 0; i < children.length; i++) {
53
- children[i].style.transition = "none";
54
- }
55
- dom.content.offsetHeight;
56
- for (let i = 0; i < children.length; i++) {
57
- children[i].style.transition = "";
58
- }
59
- };
60
- // ── Animated removeItem ──────────────────────────────────
61
- if (removeTiming) {
62
- const removeItem = (id) => {
63
- if (removePending)
64
- removePending();
65
- const active = typeof document !== "undefined" ? document.activeElement : null;
66
- const focIdx = active && dom.content.contains(active)
67
- ? parseInt(active.dataset?.index ?? "-1", 10)
68
- : -1;
69
- const found = findById(id);
70
- const layoutIndex = found.layoutIndex;
71
- const removedEl = found.el;
72
- const itemSize = layoutIndex >= 0 ? sc.getSize(layoutIndex) : 0;
73
- const originalOffset = layoutIndex >= 0 ? sc.getOffset(layoutIndex) : 0;
74
- if (!removedEl || layoutIndex < 0) {
75
- const result = ctx.removeItemById(id);
76
- if (result < 0)
77
- return false;
78
- ctx.forceRender();
79
- commitStyles();
80
- emitter.emit("data:change", { type: "remove", id });
81
- if (focIdx >= 0) {
82
- const t = state.totalItems;
83
- if (t > 0)
84
- ctx.getRenderedElement(Math.min(focIdx, t - 1))?.focus();
85
- }
86
- emitter.emit("remove:end", { id });
87
- return true;
88
- }
89
- // FIRST — clone before removal
90
- const exitClone = removedEl.cloneNode(true);
91
- exitClone.style.pointerEvents = "none";
92
- exitClone.style.overflow = "hidden";
93
- exitClone.removeAttribute("data-index");
94
- exitClone.removeAttribute("data-id");
95
- exitClone.removeAttribute("id");
96
- exitClone.removeAttribute("aria-selected");
97
- exitClone.classList.remove(`${cfg.classPrefix}-item--selected`);
98
- const scrollProp = cfg.horizontal ? "scrollLeft" : "scrollTop";
99
- const oldScroll = dom.viewport[scrollProp];
100
- // LAST — remove data + reconcile
101
- const result = ctx.removeItemById(id);
102
- if (result < 0)
103
- return false;
104
- ctx.forceRender();
105
- commitStyles();
106
- emitter.emit("data:change", { type: "remove", id });
107
- const scrollDelta = oldScroll - dom.viewport[scrollProp];
108
- exitClone.style.zIndex = "1";
109
- dom.content.appendChild(exitClone);
110
- const rt = removeTiming;
111
- const animOptions = { duration: rt.duration, easing: rt.easing };
112
- const animations = [];
113
- const cloneStart = Math.round(originalOffset - scrollDelta);
114
- animations.push(exitClone.animate([
115
- { transform: `${prop}(${cloneStart}px) scaleY(1)`, opacity: 1, transformOrigin: origin },
116
- { transform: `${prop}(${Math.round(originalOffset)}px) scaleY(0)`, opacity: 0, transformOrigin: origin },
117
- ], animOptions));
118
- const allOnClamp = scrollDelta > 0;
119
- const itemChildren = dom.content.children;
120
- for (let i = 0; i < itemChildren.length; i++) {
121
- const el = itemChildren[i];
122
- if (el === exitClone)
123
- continue;
124
- const idx = parseInt(el.dataset?.index ?? "-1", 10);
125
- if (idx < 0 || (!allOnClamp && idx < layoutIndex))
126
- continue;
127
- const newOffset = sc.getOffset(idx);
128
- const oldVisual = idx >= layoutIndex
129
- ? Math.round(newOffset + itemSize - scrollDelta)
130
- : Math.round(newOffset - scrollDelta);
131
- const newVisual = Math.round(newOffset);
132
- if (oldVisual === newVisual)
133
- continue;
134
- animations.push(el.animate([
135
- { transform: `${prop}(${oldVisual}px)` },
136
- { transform: `${prop}(${newVisual}px)` },
137
- ], animOptions));
138
- }
139
- let settled = false;
140
- const finalize = () => {
141
- if (settled)
142
- return;
143
- settled = true;
144
- removePending = null;
145
- exitClone.remove();
146
- for (const a of animations) {
147
- if (a.playState !== "finished")
148
- a.cancel();
149
- }
150
- if (focIdx >= 0) {
151
- const t = state.totalItems;
152
- if (t > 0)
153
- ctx.getRenderedElement(Math.min(focIdx, t - 1))?.focus();
154
- }
155
- emitter.emit("remove:end", { id });
156
- };
157
- removePending = finalize;
158
- Promise.all(animations.map(a => a.finished)).then(finalize, finalize);
159
- setTimeout(finalize, rt.duration + 50);
160
- return true;
161
- };
162
- // ── Batch removeItems ──────────────────────────────────
163
- const removeItems = (ids) => {
164
- if (ids.length === 0)
165
- return 0;
166
- if (ids.length === 1)
167
- return removeItem(ids[0]) ? 1 : 0;
168
- if (removePending)
169
- removePending();
170
- if (addPending)
171
- addPending();
172
- const active = typeof document !== "undefined" ? document.activeElement : null;
173
- const focIdx = active && dom.content.contains(active)
174
- ? parseInt(active.dataset?.index ?? "-1", 10)
175
- : -1;
176
- const targets = [];
177
- for (const id of ids) {
178
- const found = findById(id);
179
- targets.push({
180
- id,
181
- layoutIndex: found.layoutIndex,
182
- el: found.el,
183
- size: found.layoutIndex >= 0 ? sc.getSize(found.layoutIndex) : 0,
184
- offset: found.layoutIndex >= 0 ? sc.getOffset(found.layoutIndex) : 0,
185
- });
186
- }
187
- targets.sort((a, b) => b.layoutIndex - a.layoutIndex);
188
- const clones = [];
189
- for (const t of targets) {
190
- if (!t.el || t.layoutIndex < 0)
191
- continue;
192
- const clone = t.el.cloneNode(true);
193
- clone.style.pointerEvents = "none";
194
- clone.style.overflow = "hidden";
195
- clone.removeAttribute("data-index");
196
- clone.removeAttribute("data-id");
197
- clone.removeAttribute("id");
198
- clone.removeAttribute("aria-selected");
199
- clone.classList.remove(`${cfg.classPrefix}-item--selected`);
200
- clones.push({ clone, offset: t.offset, size: t.size, layoutIndex: t.layoutIndex });
201
- }
202
- const oldOffsetById = new Map();
203
- const children = dom.content.children;
204
- for (let i = 0; i < children.length; i++) {
205
- const el = children[i];
206
- const elId = el.dataset?.id;
207
- const idx = parseInt(el.dataset?.index ?? "-1", 10);
208
- if (elId && idx >= 0)
209
- oldOffsetById.set(elId, sc.getOffset(idx));
210
- }
211
- const scrollProp = cfg.horizontal ? "scrollLeft" : "scrollTop";
212
- const oldScroll = dom.viewport[scrollProp];
213
- // Remove all items (descending order preserved)
214
- const removedIds = [];
215
- for (const t of targets) {
216
- const result = ctx.removeItemById(t.id);
217
- if (result >= 0)
218
- removedIds.push(t.id);
219
- }
220
- if (removedIds.length === 0)
221
- return 0;
222
- ctx.forceRender();
223
- commitStyles();
224
- for (const rid of removedIds) {
225
- emitter.emit("data:change", { type: "remove", id: rid });
226
- }
227
- if (clones.length === 0) {
228
- if (focIdx >= 0) {
229
- const t = state.totalItems;
230
- if (t > 0)
231
- ctx.getRenderedElement(Math.min(focIdx, t - 1))?.focus();
232
- }
233
- for (const rid of removedIds)
234
- emitter.emit("remove:end", { id: rid });
235
- return removedIds.length;
236
- }
237
- const scrollDelta = oldScroll - dom.viewport[scrollProp];
238
- const rt = removeTiming;
239
- const animOptions = { duration: rt.duration, easing: rt.easing };
240
- const animations = [];
241
- clones.sort((a, b) => a.layoutIndex - b.layoutIndex);
242
- let removedSizeAbove = 0;
243
- for (const c of clones) {
244
- c.clone.style.zIndex = "1";
245
- dom.content.appendChild(c.clone);
246
- const cloneStart = Math.round(c.offset - scrollDelta);
247
- const shiftedEnd = Math.round(c.offset - removedSizeAbove);
248
- animations.push(c.clone.animate([
249
- { transform: `${prop}(${cloneStart}px) scaleY(1)`, opacity: 1, transformOrigin: origin },
250
- { transform: `${prop}(${shiftedEnd}px) scaleY(0)`, opacity: 0, transformOrigin: origin },
251
- ], animOptions));
252
- removedSizeAbove += c.size;
253
- }
254
- const cloneSet = new Set(clones.map(c => c.clone));
255
- const itemChildren = dom.content.children;
256
- for (let i = 0; i < itemChildren.length; i++) {
257
- const el = itemChildren[i];
258
- if (cloneSet.has(el))
259
- continue;
260
- const elId = el.dataset?.id;
261
- const idx = parseInt(el.dataset?.index ?? "-1", 10);
262
- if (!elId || idx < 0)
263
- continue;
264
- const oldOffset = oldOffsetById.get(elId);
265
- if (oldOffset === undefined)
266
- continue;
267
- const newOffset = sc.getOffset(idx);
268
- const oldVisual = Math.round(oldOffset - scrollDelta);
269
- const newVisual = Math.round(newOffset);
270
- if (oldVisual === newVisual)
271
- continue;
272
- animations.push(el.animate([
273
- { transform: `${prop}(${oldVisual}px)` },
274
- { transform: `${prop}(${newVisual}px)` },
275
- ], animOptions));
276
- }
277
- let settled = false;
278
- const finalize = () => {
279
- if (settled)
280
- return;
281
- settled = true;
282
- removePending = null;
283
- for (const { clone } of clones)
284
- clone.remove();
285
- for (const a of animations) {
286
- if (a.playState !== "finished")
287
- a.cancel();
288
- }
289
- if (focIdx >= 0) {
290
- const t = state.totalItems;
291
- if (t > 0)
292
- ctx.getRenderedElement(Math.min(focIdx, t - 1))?.focus();
293
- }
294
- for (const rid of removedIds)
295
- emitter.emit("remove:end", { id: rid });
296
- };
297
- removePending = finalize;
298
- Promise.all(animations.map(a => a.finished)).then(finalize, finalize);
299
- setTimeout(finalize, rt.duration + 50);
300
- return removedIds.length;
301
- };
302
- ctx.registerMethod("removeItem", removeItem);
303
- ctx.registerMethod("removeItems", removeItems);
304
- }
305
- // ── Animated insertItem ────────────────────────────────────
306
- if (insertTiming) {
307
- const insertItem = (item, index) => {
308
- if (addPending)
309
- addPending();
310
- if (removePending)
311
- removePending();
312
- const insertDataIndex = index ?? 0;
313
- const oldOffsetById = new Map();
314
- const children = dom.content.children;
315
- for (let i = 0; i < children.length; i++) {
316
- const el = children[i];
317
- const id = el.dataset?.id;
318
- const idx = parseInt(el.dataset?.index ?? "-1", 10);
319
- if (id && idx >= 0)
320
- oldOffsetById.set(id, sc.getOffset(idx));
321
- }
322
- const scrollProp = cfg.horizontal ? "scrollLeft" : "scrollTop";
323
- const sizeProp = cfg.horizontal ? "scrollWidth" : "scrollHeight";
324
- const clientProp = cfg.horizontal ? "clientWidth" : "clientHeight";
325
- const oldScroll = dom.viewport[scrollProp];
326
- const oldMaxScroll = dom.viewport[sizeProp] - dom.viewport[clientProp];
327
- const wasAtEnd = oldScroll >= oldMaxScroll - 1;
328
- ctx.insertItemAt(item, insertDataIndex);
329
- ctx.forceRender();
330
- commitStyles();
331
- emitter.emit("data:change", { type: "insert", id: item.id });
332
- let scrollDelta = dom.viewport[scrollProp] - oldScroll;
333
- const postInsertLayoutIndex = dataToLayout(insertDataIndex);
334
- if (cfg.reverse && scrollDelta === 0 && wasAtEnd) {
335
- dom.viewport[scrollProp] = dom.viewport[sizeProp] - dom.viewport[clientProp];
336
- const bumped = dom.viewport[scrollProp];
337
- state.prevScrollPosition = state.scrollPosition;
338
- state.scrollPosition = bumped;
339
- scrollDelta = bumped - oldScroll;
340
- if (scrollDelta > 0) {
341
- ctx.forceRender();
342
- commitStyles();
343
- }
344
- }
345
- const newEl = ctx.getRenderedElement(postInsertLayoutIndex);
346
- const at = insertTiming;
347
- const animOptions = { duration: at.duration, easing: at.easing };
348
- const animations = [];
349
- if (newEl) {
350
- const newOffset = sc.getOffset(postInsertLayoutIndex);
351
- animations.push(newEl.animate([
352
- { transform: `${prop}(${Math.round(newOffset)}px) scaleY(0)`, opacity: 0, transformOrigin: origin },
353
- { transform: `${prop}(${Math.round(newOffset)}px) scaleY(1)`, opacity: 1, transformOrigin: origin },
354
- ], animOptions));
355
- }
356
- const postChildren = dom.content.children;
357
- for (let i = 0; i < postChildren.length; i++) {
358
- const el = postChildren[i];
359
- if (el === newEl)
360
- continue;
361
- const id = el.dataset?.id;
362
- const idx = parseInt(el.dataset?.index ?? "-1", 10);
363
- if (!id || idx < 0)
364
- continue;
365
- const oldOffset = oldOffsetById.get(id);
366
- if (oldOffset === undefined)
367
- continue;
368
- const newOffset = sc.getOffset(idx);
369
- const visualOld = Math.round(oldOffset + scrollDelta);
370
- const visualNew = Math.round(newOffset);
371
- if (visualOld === visualNew)
372
- continue;
373
- animations.push(el.animate([
374
- { transform: `${prop}(${visualOld}px)` },
375
- { transform: `${prop}(${visualNew}px)` },
376
- ], animOptions));
377
- }
378
- if (animations.length === 0)
379
- return;
380
- let settled = false;
381
- const finalize = () => {
382
- if (settled)
383
- return;
384
- settled = true;
385
- addPending = null;
386
- for (const a of animations) {
387
- if (a.playState !== "finished")
388
- a.cancel();
389
- }
390
- };
391
- addPending = finalize;
392
- Promise.all(animations.map(a => a.finished)).then(finalize, finalize);
393
- setTimeout(finalize, at.duration + 50);
394
- };
395
- ctx.registerMethod("insertItem", insertItem);
396
- }
397
- },
398
- destroy() {
399
- if (removePending)
400
- removePending();
401
- if (addPending)
402
- addPending();
403
- },
404
- };
405
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * vlist/rendering -- ARIA position resolvers
3
- *
4
- * Lazy-resolves _getTotal and _layoutToDataIndex from the methods map
5
- * so that aria-setsize uses data total (not layout total including
6
- * group headers) and aria-posinset uses data-space position.
7
- */
8
- export const createAriaResolvers = (methods, fallbackTotal) => {
9
- let gt;
10
- let l2d;
11
- return {
12
- getSetSize: () => {
13
- if (gt === undefined)
14
- gt = methods.get("_getTotal") ?? null;
15
- return gt ? gt() : fallbackTotal();
16
- },
17
- getPosInSet: (layoutIndex) => {
18
- if (l2d === undefined)
19
- l2d = methods.get("_layoutToDataIndex") ?? null;
20
- return l2d ? l2d(layoutIndex) + 1 : layoutIndex + 1;
21
- },
22
- };
23
- };
@@ -1,18 +0,0 @@
1
- /**
2
- * vlist - Rendering Domain
3
- * Rendering, virtualization, and scaling for large datasets
4
- */
5
- // Size Cache (dimension-agnostic for vertical/horizontal scrolling)
6
- export { createSizeCache, countVisibleItems, countItemsFittingFromBottom, getOffsetForVirtualIndex, } from "./sizes";
7
- // Measured Size Cache (auto-measurement for Mode B)
8
- export { createMeasuredSizeCache, } from "./measured";
9
- // DOM Sort (accessibility — reorder children on scroll idle)
10
- export { sortRenderedDOM } from "./sort";
11
- // Renderer
12
- export { createRenderer, createDOMStructure, updateContentHeight, updateContentWidth, resolveContainer, getContainerDimensions, } from "./renderer";
13
- // Viewport Scrolling
14
- export { createViewportState, updateViewportState, updateViewportSize, updateViewportItems, calculateRenderRange, calculateTotalSize, calculateActualSize, calculateItemOffset, calculateScrollToIndex, clampScrollPosition, getScrollDirection, rangesEqual, isInRange, getRangeCount, rangeToIndices, diffRanges, getSimpleCompressionState, simpleVisibleRange, simpleScrollToIndex, NO_COMPRESSION, } from "./viewport";
15
- // Scale (large dataset handling - used by withScale feature)
16
- export { MAX_VIRTUAL_SIZE, getCompressionState, getCompressionState as getCompression, needsCompression, getMaxItemsWithoutCompression, getCompressionInfo, calculateCompressedVisibleRange, calculateCompressedRenderRange, calculateCompressedItemPosition, calculateCompressedScrollToIndex, calculateIndexFromScrollPosition, } from "./scale";
17
- // Smart Edge Scroll (shared by core baseline and selection feature)
18
- export { scrollToFocus, scrollToFocusSimple, } from "./scroll";
@@ -1,98 +0,0 @@
1
- // src/rendering/measured.ts
2
- /**
3
- * vlist - Measured Size Cache
4
- * Auto-measurement support for items with unknown sizes (Mode B)
5
- *
6
- * Wraps the existing variable SizeCache with measurement tracking.
7
- * Once an item is measured, it behaves identically to Mode A (known size).
8
- * Unmeasured items use the estimated size as a fallback.
9
- *
10
- * Fully axis-neutral: works identically for vertical (estimatedHeight)
11
- * and horizontal (estimatedWidth) orientations. This cache stores plain
12
- * numbers representing the main-axis dimension — it never knows whether
13
- * those numbers are heights or widths. The axis-specific translation
14
- * happens in builder/core.ts at the DOM boundary.
15
- *
16
- * Implements the SizeCache interface so all downstream code
17
- * (viewport, scale, features) works unchanged.
18
- */
19
- import { createSizeCache } from "./sizes";
20
- // =============================================================================
21
- // Factory
22
- // =============================================================================
23
- /**
24
- * Create a measured size cache for auto-measurement (Mode B)
25
- *
26
- * Works for both orientations:
27
- * - Vertical: estimatedSize = estimatedHeight, measures block size
28
- * - Horizontal: estimatedSize = estimatedWidth, measures inline size
29
- *
30
- * The cache itself is axis-neutral — it only stores numbers. The caller
31
- * (builder/core.ts) is responsible for reading the correct axis from
32
- * the config and from ResizeObserver entries (blockSize vs inlineSize).
33
- *
34
- * Internally maintains a Map of measured sizes keyed by item index.
35
- * Unmeasured items fall back to the estimated size. The underlying
36
- * prefix-sum array is rebuilt when measurements change.
37
- *
38
- * The size function fed into the variable SizeCache becomes:
39
- * (index) => measuredSizes.has(index) ? measuredSizes.get(index) : estimatedSize
40
- *
41
- * This means all existing viewport, compression, and range calculations
42
- * work unchanged — they only see a SizeCache with variable sizes.
43
- */
44
- export const createMeasuredSizeCache = (estimatedSize, initialTotal) => {
45
- const measuredSizes = new Map();
46
- // Size function: return measured size if available, else estimated
47
- const sizeFn = (index) => measuredSizes.get(index) ?? estimatedSize;
48
- // Create the underlying variable SizeCache with our size function
49
- let inner = createSizeCache(sizeFn, initialTotal);
50
- return {
51
- // ── SizeCache interface ──────────────────────────────────────
52
- getOffset(index) {
53
- return inner.getOffset(index);
54
- },
55
- getSize(index) {
56
- return sizeFn(index);
57
- },
58
- indexAtOffset(offset) {
59
- return inner.indexAtOffset(offset);
60
- },
61
- getTotalSize() {
62
- return inner.getTotalSize();
63
- },
64
- getTotal() {
65
- return inner.getTotal();
66
- },
67
- rebuild(totalItems) {
68
- // Discard measured sizes for indices that no longer exist
69
- if (totalItems < inner.getTotal()) {
70
- for (const index of measuredSizes.keys()) {
71
- if (index >= totalItems)
72
- measuredSizes.delete(index);
73
- }
74
- }
75
- // Rebuild the underlying variable cache with current size function
76
- // We must recreate because createSizeCache captures sizeFn at creation,
77
- // but our sizeFn closes over measuredSizes which is mutable — so a
78
- // rebuild of the inner cache re-evaluates all prefix sums.
79
- inner = createSizeCache(sizeFn, totalItems);
80
- },
81
- isVariable() {
82
- return true;
83
- },
84
- // ── MeasuredSizeCache extensions ─────────────────────────────
85
- setMeasuredSize(index, size) {
86
- measuredSizes.set(index, size);
87
- },
88
- isMeasured(index) {
89
- return measuredSizes.has(index);
90
- },
91
- getEstimatedSize() {
92
- return estimatedSize;
93
- },
94
- measuredCount() {
95
- return measuredSizes.size;
96
- },
97
- };
98
- };