igniteui-webcomponents 7.4.0 → 7.4.1

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 (97) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/components/checkbox/checkbox-base.d.ts +2 -0
  3. package/components/checkbox/checkbox-base.js +4 -0
  4. package/components/checkbox/checkbox-base.js.map +1 -1
  5. package/components/checkbox/checkbox.js +3 -11
  6. package/components/checkbox/checkbox.js.map +1 -1
  7. package/components/checkbox/switch.js +1 -8
  8. package/components/checkbox/switch.js.map +1 -1
  9. package/components/color-picker/color-picker.js +3 -3
  10. package/components/color-picker/color-picker.js.map +1 -1
  11. package/components/combo/combo.d.ts +6 -4
  12. package/components/combo/combo.js +13 -24
  13. package/components/combo/combo.js.map +1 -1
  14. package/components/date-picker/date-picker.base.d.ts +3 -3
  15. package/components/date-picker/date-picker.base.js +2 -2
  16. package/components/date-picker/date-picker.base.js.map +1 -1
  17. package/components/date-picker/date-picker.d.ts +1 -1
  18. package/components/date-range-picker/date-range-picker.d.ts +1 -1
  19. package/components/date-time-input/date-time-input.base.d.ts +4 -4
  20. package/components/date-time-input/date-time-input.base.js +3 -5
  21. package/components/date-time-input/date-time-input.base.js.map +1 -1
  22. package/components/file-input/file-input.d.ts +5 -0
  23. package/components/file-input/file-input.js +4 -0
  24. package/components/file-input/file-input.js.map +1 -1
  25. package/components/input/input-base.d.ts +2 -2
  26. package/components/input/input-base.js +3 -5
  27. package/components/input/input-base.js.map +1 -1
  28. package/components/input/input.d.ts +1 -1
  29. package/components/radio/radio.d.ts +2 -0
  30. package/components/radio/radio.js +7 -11
  31. package/components/radio/radio.js.map +1 -1
  32. package/components/rating/rating.d.ts +3 -0
  33. package/components/rating/rating.js +17 -18
  34. package/components/rating/rating.js.map +1 -1
  35. package/components/select/select.js +1 -1
  36. package/components/select/select.js.map +1 -1
  37. package/components/slider/range-slider.d.ts +0 -1
  38. package/components/slider/range-slider.js +6 -11
  39. package/components/slider/range-slider.js.map +1 -1
  40. package/components/slider/slider-base.d.ts +4 -1
  41. package/components/slider/slider-base.js +20 -18
  42. package/components/slider/slider-base.js.map +1 -1
  43. package/components/slider/slider.d.ts +2 -0
  44. package/components/slider/slider.js +5 -1
  45. package/components/slider/slider.js.map +1 -1
  46. package/components/textarea/textarea.d.ts +1 -4
  47. package/components/textarea/textarea.js +3 -5
  48. package/components/textarea/textarea.js.map +1 -1
  49. package/components/validation-container/validation-container.js +2 -1
  50. package/components/validation-container/validation-container.js.map +1 -1
  51. package/components/virtualization/engine.d.ts +35 -4
  52. package/components/virtualization/engine.js +126 -61
  53. package/components/virtualization/engine.js.map +1 -1
  54. package/components/virtualization/recycle.d.ts +13 -0
  55. package/components/virtualization/recycle.js +152 -0
  56. package/components/virtualization/recycle.js.map +1 -0
  57. package/components/virtualization/virtualization.d.ts +32 -11
  58. package/components/virtualization/virtualization.js +14 -11
  59. package/components/virtualization/virtualization.js.map +1 -1
  60. package/custom-elements.json +2382 -79
  61. package/igniteui-webcomponents.html-data.json +1 -1
  62. package/index.d.ts +1 -1
  63. package/index.js.map +1 -1
  64. package/internals/controllers/aria-projection.d.ts +61 -23
  65. package/internals/controllers/aria-projection.js +77 -26
  66. package/internals/controllers/aria-projection.js.map +1 -1
  67. package/internals/mixins/forms/associated.js +34 -0
  68. package/internals/mixins/forms/associated.js.map +1 -1
  69. package/internals/mixins/forms/types.d.ts +5 -0
  70. package/internals/mixins/forms/types.js.map +1 -1
  71. package/internals/templates/toggle-shell.d.ts +15 -14
  72. package/internals/templates/toggle-shell.js +12 -8
  73. package/internals/templates/toggle-shell.js.map +1 -1
  74. package/internals/utils/dom.d.ts +2 -2
  75. package/internals/utils/dom.js +2 -2
  76. package/internals/utils/dom.js.map +1 -1
  77. package/package.json +1 -1
  78. package/skills/README.md +2 -4
  79. package/skills/igniteui-wc-choose-components/SKILL.md +2 -1
  80. package/skills/igniteui-wc-customize-component-theme/SKILL.md +2 -1
  81. package/skills/igniteui-wc-figma-to-app/SKILL.md +133 -712
  82. package/skills/igniteui-wc-figma-to-app/references/asset-extraction.md +42 -62
  83. package/skills/igniteui-wc-figma-to-app/references/design-provenance.md +199 -0
  84. package/skills/igniteui-wc-figma-to-app/references/design-token-bridge.md +189 -107
  85. package/skills/igniteui-wc-figma-to-app/references/figma-component-map.md +129 -84
  86. package/skills/igniteui-wc-figma-to-app/references/figma-exploration.md +222 -0
  87. package/skills/igniteui-wc-figma-to-app/references/mcp-setup.md +81 -127
  88. package/skills/igniteui-wc-figma-to-app/references/project-setup.md +105 -0
  89. package/skills/igniteui-wc-figma-to-app/references/theme-generation.md +180 -0
  90. package/skills/igniteui-wc-figma-to-app/references/validation-patterns.md +74 -80
  91. package/skills/igniteui-wc-generate-from-image-design/SKILL.md +2 -1
  92. package/skills/igniteui-wc-generate-from-image-design/references/gotchas.md +3 -3
  93. package/skills/igniteui-wc-grids/SKILL.md +133 -0
  94. package/skills/igniteui-wc-integrate-with-framework/SKILL.md +2 -1
  95. package/skills/igniteui-wc-migrate-grid-lite-to-premium/SKILL.md +2 -1
  96. package/skills/igniteui-wc-optimize-bundle-size/SKILL.md +2 -1
  97. package/web-types.json +1 -1
@@ -25,82 +25,104 @@ function getMaxBrowserSizeProbePx(doc) {
25
25
  function clampIndex(index, length) {
26
26
  return clamp(index, 0, length - 1);
27
27
  }
28
- function buildTree(tree, sizes) {
29
- const length = sizes.length;
30
- let total = 0;
31
- for (let i = 1; i <= length; i++) {
32
- tree[i] += sizes[i - 1];
33
- total += sizes[i - 1];
34
- const j = i + (i & -i);
35
- if (j <= length) {
36
- tree[j] += tree[i];
37
- }
38
- }
39
- return total;
40
- }
28
+ const UNMEASURED = 0;
29
+ const MEASURED = 1;
30
+ const SAMPLED = 2;
41
31
  class SizeTree {
42
- constructor(length, sizes, tree, total, measured) {
32
+ constructor(length, estimate, sizes = new Float64Array(length), states = new Uint8Array(length)) {
33
+ this._measuredCount = 0;
34
+ this._measuredTotal = 0;
35
+ this._sampleCount = 0;
36
+ this._sampleTotal = 0;
43
37
  this.length = length;
38
+ this.estimate = estimate;
44
39
  this._sizes = sizes;
45
- this._tree = tree;
46
- this._total = total;
47
- this._measured = measured;
40
+ this._states = states;
41
+ this._sums = new Float64Array(length + 1);
42
+ this._counts = new Int32Array(length + 1);
48
43
  this._topBit = length > 0 ? 1 << (31 - Math.clz32(length)) : 0;
44
+ for (let i = 1; i <= length; i++) {
45
+ const state = states[i - 1];
46
+ if (state !== UNMEASURED) {
47
+ const size = sizes[i - 1];
48
+ this._sums[i] += size;
49
+ this._counts[i]++;
50
+ this._measuredCount++;
51
+ this._measuredTotal += size;
52
+ if (state === SAMPLED) {
53
+ this._sampleCount++;
54
+ this._sampleTotal += size;
55
+ }
56
+ }
57
+ const parent = i + (i & -i);
58
+ if (parent <= length) {
59
+ this._sums[parent] += this._sums[i];
60
+ this._counts[parent] += this._counts[i];
61
+ }
62
+ }
49
63
  }
50
- static filled(length, fillSize) {
51
- return SizeTree._build(new Float64Array(length).fill(fillSize), new Uint8Array(length));
64
+ get totalSize() {
65
+ return (this._measuredTotal + (this.length - this._measuredCount) * this.estimate);
52
66
  }
53
- static _build(sizes, measured) {
54
- const tree = new Float64Array(sizes.length + 1);
55
- const total = buildTree(tree, sizes);
56
- return new SizeTree(sizes.length, sizes, tree, total, measured);
67
+ get sampleAverage() {
68
+ return this._sampleCount > 0 ? this._sampleTotal / this._sampleCount : 0;
57
69
  }
58
- get totalSize() {
59
- return this._total;
70
+ isMeasuredBefore(index) {
71
+ let count = 0;
72
+ for (let j = index; j > 0; j -= j & -j) {
73
+ count += this._counts[j];
74
+ }
75
+ return count === index;
60
76
  }
61
77
  prefixSum(i) {
62
78
  let sum = 0;
79
+ let count = 0;
63
80
  for (let j = i; j > 0; j -= j & -j) {
64
- sum += this._tree[j];
81
+ sum += this._sums[j];
82
+ count += this._counts[j];
65
83
  }
66
- return sum;
84
+ return sum + (i - count) * this.estimate;
67
85
  }
68
- update(index, newSize) {
86
+ update(index, size) {
69
87
  if (index < 0 || index >= this.length)
70
88
  return false;
71
- const old = this._sizes[index];
72
- this._measured[index] = 1;
73
- if (old === newSize)
74
- return false;
75
- const delta = newSize - old;
76
- this._sizes[index] = newSize;
77
- this._total += delta;
78
- for (let i = index + 1; i <= this.length; i += i & -i) {
79
- this._tree[i] += delta;
89
+ const state = this._states[index];
90
+ const isNew = state === UNMEASURED;
91
+ const old = isNew ? 0 : this._sizes[index];
92
+ const delta = size - old;
93
+ this._states[index] = SAMPLED;
94
+ this._sizes[index] = size;
95
+ this._measuredTotal += delta;
96
+ this._sampleTotal += state === SAMPLED ? delta : size;
97
+ if (state !== SAMPLED)
98
+ this._sampleCount++;
99
+ if (isNew)
100
+ this._measuredCount++;
101
+ if (isNew || delta !== 0) {
102
+ for (let i = index + 1; i <= this.length; i += i & -i) {
103
+ this._sums[i] += delta;
104
+ if (isNew)
105
+ this._counts[i]++;
106
+ }
80
107
  }
81
- return true;
108
+ return size !== (isNew ? this.estimate : old);
82
109
  }
83
- cloneResized(newLength, fillSize, retainCount = newLength) {
84
- const sizes = new Float64Array(newLength).fill(fillSize);
85
- const measured = new Uint8Array(newLength);
110
+ cloneResized(newLength, retainCount = newLength) {
86
111
  const retained = Math.max(0, Math.min(this.length, newLength, retainCount));
112
+ const sizes = new Float64Array(newLength);
113
+ const states = new Uint8Array(newLength);
87
114
  sizes.set(this._sizes.subarray(0, retained));
88
- measured.set(this._measured.subarray(0, retained));
89
- return SizeTree._build(sizes, measured);
115
+ states.set(this._states.subarray(0, retained));
116
+ return new SizeTree(newLength, this.estimate, sizes, states);
90
117
  }
91
- applyEstimate(estimatedSize) {
92
- let changed = false;
118
+ startSample() {
93
119
  for (let i = 0; i < this.length; i++) {
94
- if (!this._measured[i] && this._sizes[i] !== estimatedSize) {
95
- this._sizes[i] = estimatedSize;
96
- changed = true;
120
+ if (this._states[i] === SAMPLED) {
121
+ this._states[i] = MEASURED;
97
122
  }
98
123
  }
99
- if (!changed)
100
- return false;
101
- this._tree.fill(0);
102
- this._total = buildTree(this._tree, this._sizes);
103
- return true;
124
+ this._sampleCount = 0;
125
+ this._sampleTotal = 0;
104
126
  }
105
127
  findIndexAtOffset(offset) {
106
128
  if (offset <= 0 || this.length === 0)
@@ -109,9 +131,12 @@ class SizeTree {
109
131
  let remaining = offset;
110
132
  for (let bit = this._topBit; bit > 0; bit >>= 1) {
111
133
  const next = idx + bit;
112
- if (next <= this.length && this._tree[next] <= remaining) {
134
+ if (next > this.length)
135
+ continue;
136
+ const size = this._sums[next] + (bit - this._counts[next]) * this.estimate;
137
+ if (size <= remaining) {
113
138
  idx = next;
114
- remaining -= this._tree[idx];
139
+ remaining -= size;
115
140
  }
116
141
  }
117
142
  return Math.min(this.length - 1, idx);
@@ -122,6 +147,8 @@ export class VirtualScrollEngine {
122
147
  this._maxBrowserSize = Number.POSITIVE_INFINITY;
123
148
  this._virtualRatio = 1;
124
149
  this._tree = null;
150
+ this._configuredEstimate = Number.NaN;
151
+ this._hasAdaptedEstimate = false;
125
152
  this.onSizeChange = null;
126
153
  }
127
154
  get totalSize() {
@@ -137,9 +164,13 @@ export class VirtualScrollEngine {
137
164
  resize(length, estimatedSize, retainCount = length) {
138
165
  if (this._tree?.length === length && retainCount >= length)
139
166
  return;
140
- this._tree = this._tree
141
- ? this._tree.cloneResized(length, estimatedSize, retainCount)
142
- : SizeTree.filled(length, estimatedSize);
167
+ const reconfigured = this._configureEstimate(estimatedSize);
168
+ const tree = this._tree?.cloneResized(length, retainCount) ??
169
+ new SizeTree(length, estimatedSize);
170
+ if (reconfigured) {
171
+ tree.estimate = estimatedSize;
172
+ }
173
+ this._tree = tree;
143
174
  this._updateVirtualRatio();
144
175
  this.onSizeChange?.();
145
176
  }
@@ -150,10 +181,18 @@ export class VirtualScrollEngine {
150
181
  this.onSizeChange?.();
151
182
  }
152
183
  updateEstimatedSize(estimatedSize) {
153
- if (!this._tree?.applyEstimate(estimatedSize))
184
+ if (this._configureEstimate(estimatedSize)) {
185
+ this._applyEstimate(estimatedSize);
186
+ }
187
+ }
188
+ adaptEstimate(windowStart) {
189
+ const tree = this._tree;
190
+ if (!tree?.sampleAverage)
154
191
  return;
155
- this._updateVirtualRatio();
156
- this.onSizeChange?.();
192
+ if (this._hasAdaptedEstimate && !tree.isMeasuredBefore(windowStart))
193
+ return;
194
+ this._hasAdaptedEstimate = true;
195
+ this._applyEstimate(tree.sampleAverage);
157
196
  }
158
197
  getScrollOffsetForIndex(index) {
159
198
  if (!this._tree || index <= 0)
@@ -177,6 +216,14 @@ export class VirtualScrollEngine {
177
216
  }
178
217
  return clamp(offset / this._virtualRatio, 0, this._getMaxScrollOffset(viewportSize));
179
218
  }
219
+ resolveScrollOffset(index, scrollPosition, viewportSize, position = 'start') {
220
+ if (position === 'nearest') {
221
+ const start = this.getAlignedScrollOffset(index, viewportSize, 'start');
222
+ const end = this.getAlignedScrollOffset(index, viewportSize, 'end');
223
+ return clamp(scrollPosition, Math.min(start, end), Math.max(start, end));
224
+ }
225
+ return this.getAlignedScrollOffset(index, viewportSize, position === 'center' || position === 'end' ? position : 'start');
226
+ }
180
227
  isIndexInView(index, scrollPosition, viewportSize) {
181
228
  if (!this._tree || this._tree.length === 0)
182
229
  return false;
@@ -208,6 +255,24 @@ export class VirtualScrollEngine {
208
255
  const end = Math.min(Math.max(endIndex + 1, start), this._tree.length);
209
256
  return this._tree.prefixSum(end) - this._tree.prefixSum(start);
210
257
  }
258
+ _configureEstimate(estimatedSize) {
259
+ if (estimatedSize === this._configuredEstimate)
260
+ return false;
261
+ this._configuredEstimate = estimatedSize;
262
+ this._tree?.startSample();
263
+ return true;
264
+ }
265
+ _applyEstimate(estimatedSize) {
266
+ const tree = this._tree;
267
+ if (!tree)
268
+ return;
269
+ const total = tree.totalSize;
270
+ tree.estimate = estimatedSize;
271
+ if (tree.totalSize === total)
272
+ return;
273
+ this._updateVirtualRatio();
274
+ this.onSizeChange?.();
275
+ }
211
276
  _updateVirtualRatio() {
212
277
  const totalSize = this._tree?.totalSize ?? 0;
213
278
  this._virtualRatio =
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/components/virtualization/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { clamp } from '#internals/utils/math.js';\nimport type { ScrollAlignment, VisibleRange } from './types.js';\n\n/**\n * The maximum scrollable coordinate of a document does not change.\n * All instances in one document share one probe result.\n */\nconst _maxBrowserSizeCache = new WeakMap<Document, number>();\n\n/**\n * Measures the maximum scrollable coordinate that the browser supports.\n */\nfunction getMaxBrowserSizeProbePx(doc: Document): number {\n const cached = _maxBrowserSizeCache.get(doc);\n if (cached !== undefined) {\n return cached;\n }\n\n const container = doc.body ?? doc.documentElement;\n if (!container) {\n return Number.POSITIVE_INFINITY;\n }\n\n const div = doc.createElement('div');\n div.style.position = 'absolute';\n div.style.top = `${Number.MAX_SAFE_INTEGER}px`;\n div.style.width = '0';\n div.style.height = '0';\n div.style.visibility = 'hidden';\n container.appendChild(div);\n const scrollOffset = doc.documentElement?.scrollTop ?? 0;\n const size = Math.abs(div.getBoundingClientRect().top) + scrollOffset;\n container.removeChild(div);\n\n _maxBrowserSizeCache.set(doc, size);\n return size;\n}\n\n/**\n * Clamps `index` into `[0, length - 1]`. This keeps `prefixSum(index)` and\n * `prefixSum(index + 1)` valid. Callers make sure that `length > 0`.\n */\nfunction clampIndex(index: number, length: number): number {\n return clamp(index, 0, length - 1);\n}\n\n/**\n * Fills `tree` with the partial range sums of `sizes` in one O(N) pass.\n * `tree` is a 1-indexed Fenwick array of `sizes.length + 1` zeroed entries.\n * Returns the total sum.\n */\nfunction buildTree(tree: Float64Array, sizes: Float64Array): number {\n const length = sizes.length;\n let total = 0;\n\n for (let i = 1; i <= length; i++) {\n tree[i] += sizes[i - 1];\n total += sizes[i - 1];\n const j = i + (i & -i);\n if (j <= length) {\n tree[j] += tree[i];\n }\n }\n return total;\n}\n\n/**\n * Binary Indexed Tree (Fenwick tree) over item sizes. Each hot-path operation\n * is O(log N): point update (item measured), prefix sum (scroll offset), and\n * index at offset (scroll to item, through binary lifting).\n */\nclass SizeTree {\n public readonly length: number;\n\n /** A 1-indexed BIT. Each cell holds a partial range sum. */\n private readonly _tree: Float64Array;\n\n /** Raw per-item sizes, 0-indexed. Kept for O(1) reads and delta calculation. */\n private readonly _sizes: Float64Array;\n\n /**\n * Flags the indices that hold a DOM-measured size (`1`) instead of an\n * estimate (`0`). `applyEstimate` changes only the estimated entries.\n */\n private readonly _measured: Uint8Array;\n\n /** Running total. Updated together with the tree in O(1). */\n private _total: number;\n\n /**\n * The highest power of two <= `length`, for the binary lifting in\n * `findIndexAtOffset`. Precomputed because that runs on each scroll event.\n */\n private readonly _topBit: number;\n\n private constructor(\n length: number,\n sizes: Float64Array,\n tree: Float64Array,\n total: number,\n measured: Uint8Array\n ) {\n this.length = length;\n this._sizes = sizes;\n this._tree = tree;\n this._total = total;\n this._measured = measured;\n this._topBit = length > 0 ? 1 << (31 - Math.clz32(length)) : 0;\n }\n\n /**\n * Creates a tree of `length` items, each set to `fillSize` and marked\n * unmeasured. O(N).\n */\n public static filled(length: number, fillSize: number): SizeTree {\n return SizeTree._build(\n new Float64Array(length).fill(fillSize),\n new Uint8Array(length)\n );\n }\n\n /**\n * Builds a tree from a sizes array and its matching measured-flags array. O(N).\n */\n private static _build(sizes: Float64Array, measured: Uint8Array): SizeTree {\n const tree = new Float64Array(sizes.length + 1);\n const total = buildTree(tree, sizes);\n return new SizeTree(sizes.length, sizes, tree, total, measured);\n }\n\n /** Total size of all items. O(1). */\n public get totalSize(): number {\n return this._total;\n }\n\n /**\n * Prefix sum of items [0, i): the virtual scroll offset at the leading\n * edge of item i. O(log N).\n */\n public prefixSum(i: number): number {\n let sum = 0;\n for (let j = i; j > 0; j -= j & -j) {\n sum += this._tree[j];\n }\n return sum;\n }\n\n /**\n * Sets the size of the item at a 0-based index and marks the item as\n * measured. Later `applyEstimate()` calls do not change measured items.\n * Returns true when the size changed. O(log N).\n */\n public update(index: number, newSize: number): boolean {\n if (index < 0 || index >= this.length) return false;\n\n const old = this._sizes[index];\n this._measured[index] = 1;\n if (old === newSize) return false;\n\n const delta = newSize - old;\n this._sizes[index] = newSize;\n this._total += delta;\n for (let i = index + 1; i <= this.length; i += i & -i) {\n this._tree[i] += delta;\n }\n return true;\n }\n\n /**\n * Returns a new tree of `newLength` items in one O(N) pass. Sizes and\n * measured flags are kept up to `min(this.length, newLength, retainCount)`.\n * The remainder is filled with `fillSize` and marked unmeasured. Pass a\n * `retainCount` below the item count when the data behind those indices\n * changed identity.\n */\n public cloneResized(\n newLength: number,\n fillSize: number,\n retainCount = newLength\n ): SizeTree {\n const sizes = new Float64Array(newLength).fill(fillSize);\n const measured = new Uint8Array(newLength);\n const retained = Math.max(0, Math.min(this.length, newLength, retainCount));\n sizes.set(this._sizes.subarray(0, retained));\n measured.set(this._measured.subarray(0, retained));\n return SizeTree._build(sizes, measured);\n }\n\n /**\n * Sets `estimatedSize` on each unmeasured item. Measured items do not\n * change. Returns true when at least one size changed.\n *\n * One estimate change can touch most of the list, so this rebuilds in one\n * O(N) pass instead of one O(log N) `update` per item.\n */\n public applyEstimate(estimatedSize: number): boolean {\n let changed = false;\n for (let i = 0; i < this.length; i++) {\n if (!this._measured[i] && this._sizes[i] !== estimatedSize) {\n this._sizes[i] = estimatedSize;\n changed = true;\n }\n }\n if (!changed) return false;\n\n this._tree.fill(0);\n this._total = buildTree(this._tree, this._sizes);\n return true;\n }\n\n /**\n * Returns the 0-based index of the item that contains the scroll `offset`:\n * the largest i where `prefixSum(i) <= offset < prefixSum(i + 1)`. O(log N).\n */\n public findIndexAtOffset(offset: number): number {\n if (offset <= 0 || this.length === 0) return 0;\n\n let idx = 0;\n let remaining = offset;\n\n for (let bit = this._topBit; bit > 0; bit >>= 1) {\n const next = idx + bit;\n if (next <= this.length && this._tree[next] <= remaining) {\n idx = next;\n remaining -= this._tree[idx];\n }\n }\n return Math.min(this.length - 1, idx);\n }\n}\n\n/**\n * Pure scroll-math engine for one axis of virtual scrolling. A Fenwick tree\n * holds all size state.\n *\n * ### Virtual and DOM coordinates\n *\n * Browsers limit how far an element can scroll. When the total item size is\n * larger than that limit, the engine compresses the *virtual* space\n * (`0…totalSize`) into the *DOM* space the browser can represent\n * (`0…domSize`) by the factor `_virtualRatio`. Each offset that crosses that\n * boundary is scaled: incoming scroll positions are multiplied by the ratio,\n * and outgoing offsets are divided by it. Items render at their real pixel\n * size, so item sizes are always virtual.\n */\nexport class VirtualScrollEngine {\n private _maxBrowserSize = Number.POSITIVE_INFINITY;\n\n /**\n * Maps a virtual scroll position to a DOM scroll position. The ratio\n * `totalSize / maxBrowserSize` if `totalSize` is larger than the maximum DOM\n * coordinate of the browser, and `1` in all other cases.\n */\n private _virtualRatio = 1;\n\n /** Binary Indexed Tree for O(log N) size queries and updates. */\n private _tree: SizeTree | null = null;\n\n /**\n * Called when item sizes or the item count change.\n * Example: `() => this.requestUpdate()`.\n */\n public onSizeChange: (() => void) | null = null;\n\n /** Total virtual size of all items in px. */\n public get totalSize(): number {\n return this._tree?.totalSize ?? 0;\n }\n\n /** Total size in DOM space, clamped to the maximum browser size. */\n public get domSize(): number {\n return this._virtualRatio !== 1 ? this._maxBrowserSize : this.totalSize;\n }\n\n /** Measures the maximum browser size for the document and rescales. */\n public initMaxBrowserSize(doc: Document): void {\n this._maxBrowserSize = getMaxBrowserSizeProbePx(doc);\n this._updateVirtualRatio();\n }\n\n /**\n * Resizes the internal sizes array to `length`. Measured sizes below\n * `retainCount` are kept. The remainder is filled with `estimatedSize` and\n * marked unmeasured. Callers that only append can keep the default\n * `retainCount`. Callers whose data changed identity at some index must\n * pass that index, so the stale measurements after it are discarded.\n */\n public resize(\n length: number,\n estimatedSize: number,\n retainCount = length\n ): void {\n if (this._tree?.length === length && retainCount >= length) return;\n\n this._tree = this._tree\n ? this._tree.cloneResized(length, estimatedSize, retainCount)\n : SizeTree.filled(length, estimatedSize);\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n /** Records the measured DOM size for a single item. */\n public measureItem(index: number, size: number): void {\n if (!this._tree?.update(index, size)) return;\n\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n /**\n * Applies a new estimated size to each item that is not measured in the\n * DOM. Use this when `estimatedItemSize` changes but the item count does\n * not, because `resize` is then a no-op.\n */\n public updateEstimatedSize(estimatedSize: number): void {\n if (!this._tree?.applyEstimate(estimatedSize)) return;\n\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n /**\n * Returns the DOM scroll offset in px that puts the item at `index` at the\n * leading edge of the viewport.\n */\n public getScrollOffsetForIndex(index: number): number {\n if (!this._tree || index <= 0) return 0;\n\n const clamped = Math.min(index, this._tree.length);\n return this._tree.prefixSum(clamped) / this._virtualRatio;\n }\n\n /**\n * The largest DOM scroll offset the host can reach for the given viewport\n * size. A request past it does nothing, so an offset given to a caller that\n * waits for the scroll to settle is clamped to this value.\n */\n private _getMaxScrollOffset(viewportSize: number): number {\n return Math.max(0, this.domSize - viewportSize);\n }\n\n /**\n * Returns the DOM scroll offset that positions the item at `index` in a\n * `viewportSize` px viewport, aligned by `align` and clamped to the\n * reachable scroll range.\n *\n * The slack is computed in virtual space against the item's real size and\n * converted to DOM space once, at the end. One DOM pixel equals\n * `_virtualRatio` virtual pixels, so mixed coordinates would scale the\n * slack.\n */\n public getAlignedScrollOffset(\n index: number,\n viewportSize: number,\n align: ScrollAlignment\n ): number {\n if (!this._tree || this._tree.length === 0) return 0;\n\n const clamped = clampIndex(index, this._tree.length);\n const itemStart = this._tree.prefixSum(clamped);\n let offset = itemStart;\n\n if (align !== 'start') {\n const itemEnd = this._tree.prefixSum(clamped + 1);\n const slack = viewportSize - Math.max(0, itemEnd - itemStart);\n offset -= align === 'center' ? slack / 2 : slack;\n }\n\n return clamp(\n offset / this._virtualRatio,\n 0,\n this._getMaxScrollOffset(viewportSize)\n );\n }\n\n /**\n * Whether the item at `index` is visible without more scrolling at the\n * given DOM scroll position. True when the item is fully inside the\n * viewport, or when it is larger than the viewport and covers it fully.\n * The second case matches native `scrollIntoView({ block: 'nearest' })`.\n */\n public isIndexInView(\n index: number,\n scrollPosition: number,\n viewportSize: number\n ): boolean {\n if (!this._tree || this._tree.length === 0) return false;\n\n const clamped = clampIndex(index, this._tree.length);\n const itemStart = this._tree.prefixSum(clamped);\n const itemEnd = this._tree.prefixSum(clamped + 1);\n const viewStart = Math.max(0, scrollPosition) * this._virtualRatio;\n const viewEnd = viewStart + viewportSize;\n\n const contained = itemStart >= viewStart && itemEnd <= viewEnd;\n const spanning = itemStart <= viewStart && itemEnd >= viewEnd;\n\n return contained || spanning;\n }\n\n /**\n * Returns the visible and over-scanned item range for the given scroll\n * state.\n */\n public getVisibleRange(\n scrollPosition: number,\n viewportSize: number,\n overScan: number\n ): VisibleRange {\n if (!this._tree || this._tree.length === 0 || viewportSize <= 0) {\n return { startIndex: 0, endIndex: -1 };\n }\n\n // The virtual ratio does not scale the viewport. Items render at their real\n // pixel size, so a `viewportSize` px viewport shows that many virtual pixels\n // of items at any compression of the scroll range.\n const startOffset = Math.max(0, scrollPosition) * this._virtualRatio;\n const first = this._tree.findIndexAtOffset(startOffset);\n const last = this._tree.findIndexAtOffset(startOffset + viewportSize);\n\n return {\n startIndex: Math.max(0, first - overScan),\n endIndex: Math.min(this._tree.length - 1, last + overScan),\n };\n }\n\n /**\n * Sum of the actual sizes of the items in [startIndex, endIndex]. The\n * render pass uses it to clamp the content translate offset, so rendered\n * items do not overflow past `domSize` under coordinate compression.\n */\n public getPhysicalRangeSize(startIndex: number, endIndex: number): number {\n if (!this._tree) return 0;\n\n const start = Math.max(0, startIndex);\n const end = Math.min(Math.max(endIndex + 1, start), this._tree.length);\n return this._tree.prefixSum(end) - this._tree.prefixSum(start);\n }\n\n private _updateVirtualRatio(): void {\n const totalSize = this._tree?.totalSize ?? 0;\n this._virtualRatio =\n totalSize <= this._maxBrowserSize ? 1 : totalSize / this._maxBrowserSize;\n }\n}\n"]}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/components/virtualization/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { clamp } from '#internals/utils/math.js';\nimport type { ScrollAlignment, VisibleRange } from './types.js';\n\n/**\n * The maximum scrollable coordinate of a document does not change.\n * All instances in one document share one probe result.\n */\nconst _maxBrowserSizeCache = new WeakMap<Document, number>();\n\n/**\n * Measures the maximum scrollable coordinate that the browser supports.\n */\nfunction getMaxBrowserSizeProbePx(doc: Document): number {\n const cached = _maxBrowserSizeCache.get(doc);\n if (cached !== undefined) {\n return cached;\n }\n\n const container = doc.body ?? doc.documentElement;\n if (!container) {\n return Number.POSITIVE_INFINITY;\n }\n\n const div = doc.createElement('div');\n div.style.position = 'absolute';\n div.style.top = `${Number.MAX_SAFE_INTEGER}px`;\n div.style.width = '0';\n div.style.height = '0';\n div.style.visibility = 'hidden';\n container.appendChild(div);\n const scrollOffset = doc.documentElement?.scrollTop ?? 0;\n const size = Math.abs(div.getBoundingClientRect().top) + scrollOffset;\n container.removeChild(div);\n\n _maxBrowserSizeCache.set(doc, size);\n return size;\n}\n\n/**\n * Clamps `index` into `[0, length - 1]`. This keeps `prefixSum(index)` and\n * `prefixSum(index + 1)` valid. Callers make sure that `length > 0`.\n */\nfunction clampIndex(index: number, length: number): number {\n return clamp(index, 0, length - 1);\n}\n\n/** The item holds the estimate. */\nconst UNMEASURED = 0;\n/** The item was measured under an older estimate. */\nconst MEASURED = 1;\n/** The item was measured under the current estimate. */\nconst SAMPLED = 2;\n\n/**\n * Binary Indexed Tree (Fenwick tree) over item sizes. Each hot-path operation\n * is O(log N): point update (item measured), prefix sum (scroll offset), and\n * index at offset (scroll to item, through binary lifting).\n *\n * The tree holds only the measured sizes and the number of measured items.\n * Each unmeasured item counts as `estimate`, so an estimate change is O(1).\n */\nclass SizeTree {\n public readonly length: number;\n\n /** The size of each unmeasured item. */\n public estimate: number;\n\n /** A 1-indexed BIT of the measured sizes. */\n private readonly _sums: Float64Array;\n\n /** A 1-indexed BIT of the number of measured items. */\n private readonly _counts: Int32Array;\n\n /** Raw measured sizes, 0-indexed. Kept for O(1) delta calculation. */\n private readonly _sizes: Float64Array;\n\n /** `UNMEASURED`, `MEASURED` or `SAMPLED` for each item. */\n private readonly _states: Uint8Array;\n\n private _measuredCount = 0;\n private _measuredTotal = 0;\n\n /** Count and size sum of the `SAMPLED` items. */\n private _sampleCount = 0;\n private _sampleTotal = 0;\n\n /**\n * The highest power of two <= `length`, for the binary lifting in\n * `findIndexAtOffset`. Precomputed because that runs on each scroll event.\n */\n private readonly _topBit: number;\n\n /** Builds the tree in one O(N) pass. Without `states`, no item is measured. */\n constructor(\n length: number,\n estimate: number,\n sizes = new Float64Array(length),\n states = new Uint8Array(length)\n ) {\n this.length = length;\n this.estimate = estimate;\n this._sizes = sizes;\n this._states = states;\n this._sums = new Float64Array(length + 1);\n this._counts = new Int32Array(length + 1);\n this._topBit = length > 0 ? 1 << (31 - Math.clz32(length)) : 0;\n\n for (let i = 1; i <= length; i++) {\n const state = states[i - 1];\n\n if (state !== UNMEASURED) {\n const size = sizes[i - 1];\n this._sums[i] += size;\n this._counts[i]++;\n this._measuredCount++;\n this._measuredTotal += size;\n\n if (state === SAMPLED) {\n this._sampleCount++;\n this._sampleTotal += size;\n }\n }\n\n const parent = i + (i & -i);\n if (parent <= length) {\n this._sums[parent] += this._sums[i];\n this._counts[parent] += this._counts[i];\n }\n }\n }\n\n /** Total size of all items. O(1). */\n public get totalSize(): number {\n return (\n this._measuredTotal + (this.length - this._measuredCount) * this.estimate\n );\n }\n\n /** The average size of the `SAMPLED` items, or `0` if there are none. */\n public get sampleAverage(): number {\n return this._sampleCount > 0 ? this._sampleTotal / this._sampleCount : 0;\n }\n\n /** Whether each item before `index` is measured. O(log N). */\n public isMeasuredBefore(index: number): boolean {\n let count = 0;\n for (let j = index; j > 0; j -= j & -j) {\n count += this._counts[j];\n }\n return count === index;\n }\n\n /**\n * Prefix sum of items [0, i): the virtual scroll offset at the leading\n * edge of item i. O(log N).\n */\n public prefixSum(i: number): number {\n let sum = 0;\n let count = 0;\n for (let j = i; j > 0; j -= j & -j) {\n sum += this._sums[j];\n count += this._counts[j];\n }\n return sum + (i - count) * this.estimate;\n }\n\n /**\n * Sets the size of the item at a 0-based index and marks it `SAMPLED`.\n * Returns true when the size changed. O(log N).\n */\n public update(index: number, size: number): boolean {\n if (index < 0 || index >= this.length) return false;\n\n const state = this._states[index];\n const isNew = state === UNMEASURED;\n const old = isNew ? 0 : this._sizes[index];\n const delta = size - old;\n\n this._states[index] = SAMPLED;\n this._sizes[index] = size;\n this._measuredTotal += delta;\n this._sampleTotal += state === SAMPLED ? delta : size;\n if (state !== SAMPLED) this._sampleCount++;\n if (isNew) this._measuredCount++;\n\n if (isNew || delta !== 0) {\n for (let i = index + 1; i <= this.length; i += i & -i) {\n this._sums[i] += delta;\n if (isNew) this._counts[i]++;\n }\n }\n\n return size !== (isNew ? this.estimate : old);\n }\n\n /**\n * Returns a new tree of `newLength` items. Measured sizes are kept up to\n * `min(this.length, newLength, retainCount)`, and the other items are\n * unmeasured. Pass a `retainCount` below the item count when the data\n * behind those indices changed identity. O(N).\n */\n public cloneResized(newLength: number, retainCount = newLength): SizeTree {\n const retained = Math.max(0, Math.min(this.length, newLength, retainCount));\n const sizes = new Float64Array(newLength);\n const states = new Uint8Array(newLength);\n sizes.set(this._sizes.subarray(0, retained));\n states.set(this._states.subarray(0, retained));\n return new SizeTree(newLength, this.estimate, sizes, states);\n }\n\n /**\n * Starts a new sample: `sampleAverage` then counts only the items measured\n * after this call. O(N).\n */\n public startSample(): void {\n for (let i = 0; i < this.length; i++) {\n if (this._states[i] === SAMPLED) {\n this._states[i] = MEASURED;\n }\n }\n this._sampleCount = 0;\n this._sampleTotal = 0;\n }\n\n /**\n * Returns the 0-based index of the item that contains the scroll `offset`:\n * the largest i where `prefixSum(i) <= offset < prefixSum(i + 1)`. O(log N).\n */\n public findIndexAtOffset(offset: number): number {\n if (offset <= 0 || this.length === 0) return 0;\n\n let idx = 0;\n let remaining = offset;\n\n for (let bit = this._topBit; bit > 0; bit >>= 1) {\n const next = idx + bit;\n if (next > this.length) continue;\n\n // Node `next` covers the `bit` items after `idx`.\n const size =\n this._sums[next] + (bit - this._counts[next]) * this.estimate;\n if (size <= remaining) {\n idx = next;\n remaining -= size;\n }\n }\n return Math.min(this.length - 1, idx);\n }\n}\n\n/**\n * Pure scroll-math engine for one axis of virtual scrolling. A Fenwick tree\n * holds all size state.\n *\n * ### Virtual and DOM coordinates\n *\n * Browsers limit how far an element can scroll. When the total item size is\n * larger than that limit, the engine compresses the *virtual* space\n * (`0…totalSize`) into the *DOM* space the browser can represent\n * (`0…domSize`) by the factor `_virtualRatio`. Each offset that crosses that\n * boundary is scaled: incoming scroll positions are multiplied by the ratio,\n * and outgoing offsets are divided by it. Items render at their real pixel\n * size, so item sizes are always virtual.\n */\nexport class VirtualScrollEngine {\n private _maxBrowserSize = Number.POSITIVE_INFINITY;\n\n /**\n * Maps a virtual scroll position to a DOM scroll position. The ratio\n * `totalSize / maxBrowserSize` if `totalSize` is larger than the maximum DOM\n * coordinate of the browser, and `1` in all other cases.\n */\n private _virtualRatio = 1;\n\n /** Binary Indexed Tree for O(log N) size queries and updates. */\n private _tree: SizeTree | null = null;\n\n /** The estimate last given to `resize` or `updateEstimatedSize`. */\n private _configuredEstimate = Number.NaN;\n\n /** Whether `adaptEstimate` has applied a measured average. */\n private _hasAdaptedEstimate = false;\n\n /**\n * Called when item sizes or the item count change.\n * Example: `() => this.requestUpdate()`.\n */\n public onSizeChange: (() => void) | null = null;\n\n /** Total virtual size of all items in px. */\n public get totalSize(): number {\n return this._tree?.totalSize ?? 0;\n }\n\n /** Total size in DOM space, clamped to the maximum browser size. */\n public get domSize(): number {\n return this._virtualRatio !== 1 ? this._maxBrowserSize : this.totalSize;\n }\n\n /** Measures the maximum browser size for the document and rescales. */\n public initMaxBrowserSize(doc: Document): void {\n this._maxBrowserSize = getMaxBrowserSizeProbePx(doc);\n this._updateVirtualRatio();\n }\n\n /**\n * Resizes the internal sizes array to `length`. Measured sizes below\n * `retainCount` are kept, and the other items are unmeasured. Callers that\n * only append can keep the default `retainCount`. Callers whose data\n * changed identity at some index must pass that index, so the stale\n * measurements after it are discarded.\n *\n * Unmeasured items take `estimatedSize`, or the adapted average while\n * `estimatedSize` stays the same: a data change does not make it less\n * accurate.\n */\n public resize(\n length: number,\n estimatedSize: number,\n retainCount = length\n ): void {\n if (this._tree?.length === length && retainCount >= length) return;\n\n const reconfigured = this._configureEstimate(estimatedSize);\n const tree =\n this._tree?.cloneResized(length, retainCount) ??\n new SizeTree(length, estimatedSize);\n\n if (reconfigured) {\n tree.estimate = estimatedSize;\n }\n this._tree = tree;\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n /** Records the measured DOM size for a single item. */\n public measureItem(index: number, size: number): void {\n if (!this._tree?.update(index, size)) return;\n\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n /**\n * Applies a new estimated size to each item that is not measured in the\n * DOM. Use this when `estimatedItemSize` changes but the item count does\n * not, because `resize` is then a no-op.\n */\n public updateEstimatedSize(estimatedSize: number): void {\n if (this._configureEstimate(estimatedSize)) {\n this._applyEstimate(estimatedSize);\n }\n }\n\n /**\n * Sets the estimate of the unmeasured items to the average size measured\n * since the estimate was last configured. Older sizes can be out of date,\n * for example after a density change.\n *\n * The first change applies anywhere. A later one applies only while each\n * item before `windowStart` is measured: the offset of the rendered items\n * then does not depend on the estimate, so the change cannot move them.\n */\n public adaptEstimate(windowStart: number): void {\n const tree = this._tree;\n if (!tree?.sampleAverage) return;\n if (this._hasAdaptedEstimate && !tree.isMeasuredBefore(windowStart)) return;\n\n this._hasAdaptedEstimate = true;\n this._applyEstimate(tree.sampleAverage);\n }\n\n /**\n * Returns the DOM scroll offset in px that puts the item at `index` at the\n * leading edge of the viewport.\n */\n public getScrollOffsetForIndex(index: number): number {\n if (!this._tree || index <= 0) return 0;\n\n const clamped = Math.min(index, this._tree.length);\n return this._tree.prefixSum(clamped) / this._virtualRatio;\n }\n\n /**\n * The largest DOM scroll offset the host can reach for the given viewport\n * size. A request past it does nothing, so an offset given to a caller that\n * waits for the scroll to settle is clamped to this value.\n */\n private _getMaxScrollOffset(viewportSize: number): number {\n return Math.max(0, this.domSize - viewportSize);\n }\n\n /**\n * Returns the DOM scroll offset that positions the item at `index` in a\n * `viewportSize` px viewport, aligned by `align` and clamped to the\n * reachable scroll range.\n *\n * The slack is computed in virtual space against the item's real size and\n * converted to DOM space once, at the end. One DOM pixel equals\n * `_virtualRatio` virtual pixels, so mixed coordinates would scale the\n * slack.\n */\n public getAlignedScrollOffset(\n index: number,\n viewportSize: number,\n align: ScrollAlignment\n ): number {\n if (!this._tree || this._tree.length === 0) return 0;\n\n const clamped = clampIndex(index, this._tree.length);\n const itemStart = this._tree.prefixSum(clamped);\n let offset = itemStart;\n\n if (align !== 'start') {\n const itemEnd = this._tree.prefixSum(clamped + 1);\n const slack = viewportSize - Math.max(0, itemEnd - itemStart);\n offset -= align === 'center' ? slack / 2 : slack;\n }\n\n return clamp(\n offset / this._virtualRatio,\n 0,\n this._getMaxScrollOffset(viewportSize)\n );\n }\n\n /**\n * Returns the DOM scroll offset that brings the item at `index` into view\n * as `position` asks. `nearest` follows native `scrollIntoView`: an item in\n * view, or one that covers the viewport, keeps `scrollPosition`, and any\n * other item scrolls the smallest distance to an edge alignment.\n */\n public resolveScrollOffset(\n index: number,\n scrollPosition: number,\n viewportSize: number,\n position: ScrollLogicalPosition = 'start'\n ): number {\n if (position === 'nearest') {\n const start = this.getAlignedScrollOffset(index, viewportSize, 'start');\n const end = this.getAlignedScrollOffset(index, viewportSize, 'end');\n return clamp(scrollPosition, Math.min(start, end), Math.max(start, end));\n }\n\n return this.getAlignedScrollOffset(\n index,\n viewportSize,\n position === 'center' || position === 'end' ? position : 'start'\n );\n }\n\n /**\n * Whether the item at `index` is visible without more scrolling at the\n * given DOM scroll position. True when the item is fully inside the\n * viewport, or when it is larger than the viewport and covers it fully.\n * The second case matches native `scrollIntoView({ block: 'nearest' })`.\n */\n public isIndexInView(\n index: number,\n scrollPosition: number,\n viewportSize: number\n ): boolean {\n if (!this._tree || this._tree.length === 0) return false;\n\n const clamped = clampIndex(index, this._tree.length);\n const itemStart = this._tree.prefixSum(clamped);\n const itemEnd = this._tree.prefixSum(clamped + 1);\n const viewStart = Math.max(0, scrollPosition) * this._virtualRatio;\n const viewEnd = viewStart + viewportSize;\n\n const contained = itemStart >= viewStart && itemEnd <= viewEnd;\n const spanning = itemStart <= viewStart && itemEnd >= viewEnd;\n\n return contained || spanning;\n }\n\n /**\n * Returns the visible and over-scanned item range for the given scroll\n * state.\n */\n public getVisibleRange(\n scrollPosition: number,\n viewportSize: number,\n overScan: number\n ): VisibleRange {\n if (!this._tree || this._tree.length === 0 || viewportSize <= 0) {\n return { startIndex: 0, endIndex: -1 };\n }\n\n // The virtual ratio does not scale the viewport. Items render at their real\n // pixel size, so a `viewportSize` px viewport shows that many virtual pixels\n // of items at any compression of the scroll range.\n const startOffset = Math.max(0, scrollPosition) * this._virtualRatio;\n const first = this._tree.findIndexAtOffset(startOffset);\n const last = this._tree.findIndexAtOffset(startOffset + viewportSize);\n\n return {\n startIndex: Math.max(0, first - overScan),\n endIndex: Math.min(this._tree.length - 1, last + overScan),\n };\n }\n\n /**\n * Sum of the actual sizes of the items in [startIndex, endIndex]. The\n * render pass uses it to clamp the content translate offset, so rendered\n * items do not overflow past `domSize` under coordinate compression.\n */\n public getPhysicalRangeSize(startIndex: number, endIndex: number): number {\n if (!this._tree) return 0;\n\n const start = Math.max(0, startIndex);\n const end = Math.min(Math.max(endIndex + 1, start), this._tree.length);\n return this._tree.prefixSum(end) - this._tree.prefixSum(start);\n }\n\n /**\n * Records a changed configured estimate and starts a new sample. Returns\n * false for an unchanged one, which keeps the adapted average.\n */\n private _configureEstimate(estimatedSize: number): boolean {\n if (estimatedSize === this._configuredEstimate) return false;\n\n this._configuredEstimate = estimatedSize;\n this._tree?.startSample();\n return true;\n }\n\n private _applyEstimate(estimatedSize: number): void {\n const tree = this._tree;\n if (!tree) return;\n\n const total = tree.totalSize;\n tree.estimate = estimatedSize;\n if (tree.totalSize === total) return;\n\n this._updateVirtualRatio();\n this.onSizeChange?.();\n }\n\n private _updateVirtualRatio(): void {\n const totalSize = this._tree?.totalSize ?? 0;\n this._virtualRatio =\n totalSize <= this._maxBrowserSize ? 1 : totalSize / this._maxBrowserSize;\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { ItemTemplate, KeyFn } from 'lit/directives/repeat.js';
2
+ interface RecycleDirectiveFn {
3
+ <T>(items: readonly T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): unknown;
4
+ }
5
+ /**
6
+ * Renders `items` as `repeat` does, but gives the DOM of a key that leaves to
7
+ * a key that arrives. `repeat` destroys the part of each key that leaves and
8
+ * creates one for each key that arrives, which in a scrolling window is DOM
9
+ * churn on each scroll step. A reused part keeps any DOM state that the
10
+ * template does not bind.
11
+ */
12
+ export declare const recycle: RecycleDirectiveFn;
13
+ export {};
@@ -0,0 +1,152 @@
1
+ import { noChange, nothing, render } from 'lit';
2
+ import { clearPart, getCommittedValue, insertPart, setChildPartValue, setCommittedValue, } from 'lit/directive-helpers.js';
3
+ import { Directive, directive } from 'lit/directive.js';
4
+ import { getRoot } from '#internals/utils/dom.js';
5
+ const KEPT_WEIGHT = 2;
6
+ function heaviestIncreasingSubsequence(values, weights, size) {
7
+ const heaviest = new Int32Array(size + 1);
8
+ const endsAt = new Int32Array(size + 1);
9
+ const previous = new Int32Array(values.length);
10
+ const marked = new Uint8Array(values.length);
11
+ let best = 0;
12
+ let last = -1;
13
+ for (let i = 0; i < values.length; i++) {
14
+ if (values[i] < 0) {
15
+ continue;
16
+ }
17
+ let total = 0;
18
+ let before = -1;
19
+ for (let node = values[i]; node > 0; node -= node & -node) {
20
+ if (heaviest[node] > total) {
21
+ total = heaviest[node];
22
+ before = endsAt[node];
23
+ }
24
+ }
25
+ total += weights[i];
26
+ previous[i] = before;
27
+ for (let node = values[i] + 1; node <= size; node += node & -node) {
28
+ if (total > heaviest[node]) {
29
+ heaviest[node] = total;
30
+ endsAt[node] = i;
31
+ }
32
+ }
33
+ if (total > best) {
34
+ best = total;
35
+ last = i;
36
+ }
37
+ }
38
+ for (let i = last; i >= 0; i = previous[i]) {
39
+ marked[i] = 1;
40
+ }
41
+ return marked;
42
+ }
43
+ function removeItemPart(part) {
44
+ clearPart(part);
45
+ part.startNode.remove();
46
+ part.endNode.remove();
47
+ }
48
+ function focusedPosition(containerPart, parts) {
49
+ const container = containerPart.parentNode;
50
+ let node = getRoot(container).activeElement;
51
+ while (node && node.parentNode !== container) {
52
+ node = node.parentNode;
53
+ }
54
+ if (!node) {
55
+ return -1;
56
+ }
57
+ return parts.findIndex((part) => {
58
+ let n = part.startNode.nextSibling;
59
+ while (n && n !== part.endNode && n !== node) {
60
+ n = n.nextSibling;
61
+ }
62
+ return n === node;
63
+ });
64
+ }
65
+ class RecycleDirective extends Directive {
66
+ constructor() {
67
+ super(...arguments);
68
+ this._keys = [];
69
+ this._pool = [];
70
+ }
71
+ render(items, _keyFn, template) {
72
+ return items.map(template);
73
+ }
74
+ update(containerPart, [items, keyFn, template]) {
75
+ const keys = items.map(keyFn);
76
+ const values = items.map(template);
77
+ const committed = getCommittedValue(containerPart);
78
+ if (!Array.isArray(committed)) {
79
+ this._keys = keys;
80
+ return values;
81
+ }
82
+ setCommittedValue(containerPart, this._reconcile(containerPart, committed, keys, values));
83
+ this._keys = keys;
84
+ return noChange;
85
+ }
86
+ _reconcile(containerPart, oldParts, keys, values) {
87
+ const count = keys.length;
88
+ const parts = new Array(count);
89
+ const from = new Int32Array(count).fill(-1);
90
+ const weights = new Int32Array(count);
91
+ const claimed = new Uint8Array(oldParts.length);
92
+ const oldPositions = new Map();
93
+ const focused = focusedPosition(containerPart, oldParts);
94
+ for (let i = this._keys.length - 1; i >= 0; i--) {
95
+ oldPositions.set(this._keys[i], i);
96
+ }
97
+ for (let i = 0; i < count; i++) {
98
+ const position = oldPositions.get(keys[i]);
99
+ if (position !== undefined && !claimed[position]) {
100
+ claimed[position] = 1;
101
+ from[i] = position;
102
+ weights[i] = position === focused ? KEPT_WEIGHT * count : KEPT_WEIGHT;
103
+ }
104
+ }
105
+ let spare = 0;
106
+ for (let i = 0; i < count; i++) {
107
+ if (from[i] < 0) {
108
+ while (claimed[spare])
109
+ spare++;
110
+ if (spare < oldParts.length) {
111
+ claimed[spare] = 1;
112
+ from[i] = spare;
113
+ weights[i] = 1;
114
+ }
115
+ }
116
+ parts[i] = from[i] < 0 ? this._pool.pop() : oldParts[from[i]];
117
+ }
118
+ oldParts.forEach((part, position) => {
119
+ if (!claimed[position])
120
+ this._release(part);
121
+ });
122
+ const stable = heaviestIncreasingSubsequence(from, weights, oldParts.length);
123
+ let next;
124
+ for (let i = count - 1; i >= 0; i--) {
125
+ let part = parts[i];
126
+ if (!part) {
127
+ part = parts[i] = insertPart(containerPart, next);
128
+ }
129
+ else if (!stable[i]) {
130
+ insertPart(containerPart, next, part);
131
+ }
132
+ setChildPartValue(part, values[i]);
133
+ next = part;
134
+ }
135
+ while (this._pool.length > count) {
136
+ removeItemPart(this._pool.pop());
137
+ }
138
+ return parts;
139
+ }
140
+ _release(part) {
141
+ const doc = part.parentNode.ownerDocument;
142
+ if (this._poolRoot?.parentNode.ownerDocument !== doc) {
143
+ this._poolRoot = render(nothing, doc.createDocumentFragment(), {
144
+ isConnected: false,
145
+ });
146
+ }
147
+ insertPart(this._poolRoot, undefined, part);
148
+ this._pool.push(part);
149
+ }
150
+ }
151
+ export const recycle = directive(RecycleDirective);
152
+ //# sourceMappingURL=recycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle.js","sourceRoot":"","sources":["../../../src/components/virtualization/recycle.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildPart, noChange, nothing, type RootPart, render } from 'lit';\nimport {\n clearPart,\n getCommittedValue,\n insertPart,\n setChildPartValue,\n setCommittedValue,\n} from 'lit/directive-helpers.js';\nimport { Directive, directive } from 'lit/directive.js';\nimport type { ItemTemplate, KeyFn } from 'lit/directives/repeat.js';\nimport { getRoot } from '#internals/utils/dom.js';\n\n/**\n * The weight of a kept part when the reconciliation selects the parts that do\n * not move. A recycled part weighs 1. A moved element needs a new style and\n * layout, but a recycled element needs a new layout for its new item anyway.\n * So on a scroll, the kept parts move only when they are fewer than half the\n * recycled parts.\n */\nconst KEPT_WEIGHT = 2;\n\n/**\n * Marks the entries of the increasing subsequence of `values` with the largest\n * total weight. The values are distinct integers in `[0, size)`, or negative\n * for the entries to skip. O(n log size).\n */\nfunction heaviestIncreasingSubsequence(\n values: Int32Array,\n weights: Int32Array,\n size: number\n): Uint8Array {\n // A Fenwick tree over the values. Each node holds the weight of the heaviest\n // subsequence that ends at a value in its range, and the entry of its last\n // value.\n const heaviest = new Int32Array(size + 1);\n const endsAt = new Int32Array(size + 1);\n const previous = new Int32Array(values.length);\n const marked = new Uint8Array(values.length);\n let best = 0;\n let last = -1;\n\n for (let i = 0; i < values.length; i++) {\n if (values[i] < 0) {\n continue;\n }\n\n let total = 0;\n let before = -1;\n for (let node = values[i]; node > 0; node -= node & -node) {\n if (heaviest[node] > total) {\n total = heaviest[node];\n before = endsAt[node];\n }\n }\n\n total += weights[i];\n previous[i] = before;\n for (let node = values[i] + 1; node <= size; node += node & -node) {\n if (total > heaviest[node]) {\n heaviest[node] = total;\n endsAt[node] = i;\n }\n }\n if (total > best) {\n best = total;\n last = i;\n }\n }\n\n for (let i = last; i >= 0; i = previous[i]) {\n marked[i] = 1;\n }\n return marked;\n}\n\n/**\n * Removes an item part, its content, and both markers. `removePart` leaves\n * the end marker in the DOM since lit-html 3.3.1 (lit/lit#5010).\n */\nfunction removeItemPart(part: ChildPart): void {\n clearPart(part);\n (part.startNode as ChildNode).remove();\n (part.endNode as ChildNode).remove();\n}\n\n/** Returns the position of the part in `parts` that holds the focus, or -1. */\nfunction focusedPosition(\n containerPart: ChildPart,\n parts: readonly ChildPart[]\n): number {\n const container = containerPart.parentNode;\n let node: Node | null = getRoot(container).activeElement;\n\n while (node && node.parentNode !== container) {\n node = node.parentNode;\n }\n if (!node) {\n return -1;\n }\n\n return parts.findIndex((part) => {\n let n = part.startNode!.nextSibling;\n while (n && n !== part.endNode && n !== node) {\n n = n.nextSibling;\n }\n return n === node;\n });\n}\n\nclass RecycleDirective extends Directive {\n /** The key of each committed item part, in DOM order. */\n private _keys: unknown[] = [];\n\n /** Item parts that no key needed, detached and kept for reuse. */\n private readonly _pool: ChildPart[] = [];\n\n /**\n * The disconnected parent of the pooled parts, in the document of the\n * container. A part that moves to another document is adopted: its custom\n * elements get `adoptedCallback`, and its images load again. `insertPart`\n * notifies the async directives in a part when it moves the part between\n * parents with a different connected state, so a pooled part is\n * disconnected, and a reused one is connected again.\n */\n private _poolRoot?: RootPart;\n\n public render<T>(\n items: readonly T[],\n _keyFn: KeyFn<T>,\n template: ItemTemplate<T>\n ): unknown[] {\n return items.map(template);\n }\n\n public override update<T>(\n containerPart: ChildPart,\n [items, keyFn, template]: [readonly T[], KeyFn<T>, ItemTemplate<T>]\n ): unknown {\n const keys = items.map(keyFn);\n const values = items.map(template);\n const committed = getCommittedValue(containerPart);\n\n // First render, or the part held another value: Lit creates the parts.\n if (!Array.isArray(committed)) {\n this._keys = keys;\n return values;\n }\n\n setCommittedValue(\n containerPart,\n this._reconcile(containerPart, committed as ChildPart[], keys, values)\n );\n this._keys = keys;\n return noChange;\n }\n\n /**\n * Gives each key an item part, puts the parts in key order, and sets their\n * values.\n *\n * 1. A key that stays keeps its part.\n * 2. The other keys take the parts of the keys that left, in DOM order, so\n * a full replacement moves no DOM. Then they take pooled parts. A part\n * is created only when both run out.\n * 3. The parts that no key takes go to the pool, so a window that changes\n * size by an item or two creates no DOM.\n * 4. The reused parts in the heaviest subsequence that keeps its old order\n * do not move, see `KEPT_WEIGHT`. A kept part that holds the focus\n * weighs more than all the other parts together, so it does not move and\n * keeps the focus. Each other part moves in front of its successor,\n * unless it is already there.\n */\n private _reconcile(\n containerPart: ChildPart,\n oldParts: ChildPart[],\n keys: unknown[],\n values: unknown[]\n ): ChildPart[] {\n const count = keys.length;\n const parts: (ChildPart | undefined)[] = new Array(count);\n // The old DOM position (-1 for a pooled or new part) and the weight of the\n // part at each new position.\n const from = new Int32Array(count).fill(-1);\n const weights = new Int32Array(count);\n const claimed = new Uint8Array(oldParts.length);\n const oldPositions = new Map<unknown, number>();\n const focused = focusedPosition(containerPart, oldParts);\n\n for (let i = this._keys.length - 1; i >= 0; i--) {\n oldPositions.set(this._keys[i], i);\n }\n\n for (let i = 0; i < count; i++) {\n const position = oldPositions.get(keys[i]);\n\n if (position !== undefined && !claimed[position]) {\n claimed[position] = 1;\n from[i] = position;\n weights[i] = position === focused ? KEPT_WEIGHT * count : KEPT_WEIGHT;\n }\n }\n\n let spare = 0;\n for (let i = 0; i < count; i++) {\n if (from[i] < 0) {\n while (claimed[spare]) spare++;\n if (spare < oldParts.length) {\n claimed[spare] = 1;\n from[i] = spare;\n weights[i] = 1;\n }\n }\n parts[i] = from[i] < 0 ? this._pool.pop() : oldParts[from[i]];\n }\n\n oldParts.forEach((part, position) => {\n if (!claimed[position]) this._release(part);\n });\n\n const stable = heaviestIncreasingSubsequence(\n from,\n weights,\n oldParts.length\n );\n\n let next: ChildPart | undefined;\n for (let i = count - 1; i >= 0; i--) {\n let part = parts[i];\n\n if (!part) {\n part = parts[i] = insertPart(containerPart, next);\n } else if (!stable[i]) {\n insertPart(containerPart, next, part);\n }\n\n setChildPartValue(part, values[i]);\n next = part;\n }\n\n // Cap the pool at the window size, so a window that shrinks for good\n // frees its parts.\n while (this._pool.length > count) {\n removeItemPart(this._pool.pop()!);\n }\n\n return parts as ChildPart[];\n }\n\n private _release(part: ChildPart): void {\n const doc = part.parentNode.ownerDocument!;\n\n // A new root on the first release, and after the container moves to\n // another document. The parts in an old root move to the new document\n // only when they are reused.\n if (this._poolRoot?.parentNode.ownerDocument !== doc) {\n this._poolRoot = render(nothing, doc.createDocumentFragment(), {\n isConnected: false,\n });\n }\n\n insertPart(this._poolRoot, undefined, part);\n this._pool.push(part);\n }\n}\n\ninterface RecycleDirectiveFn {\n <T>(items: readonly T[], keyFn: KeyFn<T>, template: ItemTemplate<T>): unknown;\n}\n\n/**\n * Renders `items` as `repeat` does, but gives the DOM of a key that leaves to\n * a key that arrives. `repeat` destroys the part of each key that leaves and\n * creates one for each key that arrives, which in a scrolling window is DOM\n * churn on each scroll step. A reused part keeps any DOM state that the\n * template does not bind.\n */\nexport const recycle = directive(RecycleDirective) as RecycleDirectiveFn;\n"]}