@humanspeak/svelte-virtual-list 0.3.11 → 0.3.13

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.
@@ -647,8 +647,15 @@
647
647
  lastMeasuredIndex,
648
648
  heightManager.averageHeight,
649
649
  (result) => {
650
- // Critical updates that must trigger reactive effects immediately
651
- heightManager.itemHeight = result.newHeight
650
+ // Only update the estimated item height from statistically meaningful
651
+ // samples. With _measuredCount === 0 (browser path), the formula
652
+ // _totalHeight = _itemLength × _itemHeight means a single expanded
653
+ // accordion item (e.g., 117px) would balloon _totalHeight from
654
+ // 49,000 to 117,000px — a visible flash. Requiring ≥ 2 valid
655
+ // measurements prevents single-item outliers from swinging the estimate.
656
+ if (result.newValidCount !== 1) {
657
+ heightManager.itemHeight = result.newHeight
658
+ }
652
659
  lastMeasuredIndex = result.newLastMeasuredIndex
653
660
 
654
661
  // Update manager totals/cache before any scroll correction logic relies on them
@@ -697,7 +704,7 @@
697
704
  })
698
705
  heightManager.endDynamicUpdate()
699
706
  },
700
- lastMeasuredIndex < 0 ? 0 : 100, // debounceTime (no debounce on first pass)
707
+ lastMeasuredIndex < 0 || dirtyItems.size > 0 ? 0 : 100, // debounceTime (no debounce on first pass or when dirty items exist)
701
708
  dirtyItems, // Pass dirty items for processing
702
709
  0, // Don't pass ReactiveListManager state - let each system manage its own totals
703
710
  0, // Don't pass ReactiveListManager state - let each system manage its own totals
@@ -1616,6 +1623,7 @@
1616
1623
  class={viewportClass ?? 'virtual-list-viewport'}
1617
1624
  bind:this={heightManager.viewportElement}
1618
1625
  onscroll={handleScroll}
1626
+ style:overflow-anchor="none"
1619
1627
  >
1620
1628
  <!-- Content provides full scrollable height -->
1621
1629
  <div
@@ -126,20 +126,24 @@ export declare class ReactiveListManager {
126
126
  */
127
127
  get isDynamicUpdateInProgress(): boolean;
128
128
  /**
129
- * Begin a dynamic update. Handles nested calls: the first call disables UA scroll anchoring,
130
- * subsequent calls just increment depth. Safe to call when not wired; styles are only toggled
131
- * when both container and viewport are ready.
129
+ * Begin a dynamic update. Handles nested calls: the first call ensures UA scroll anchoring
130
+ * is disabled, subsequent calls just increment depth. Safe to call when not wired; styles
131
+ * are only set when both container and viewport are ready.
132
+ *
133
+ * Note: overflow-anchor is kept permanently as 'none' to prevent browser scroll anchoring
134
+ * from interfering with the virtual list's own scroll correction logic.
132
135
  */
133
136
  startDynamicUpdate(): void;
134
137
  /**
135
138
  * End a dynamic update started by `startDynamicUpdate`. Handles nesting: only the final
136
- * corresponding end call re-enables UA scroll anchoring. Guards against underflow.
139
+ * corresponding end call completes the update. overflow-anchor remains 'none' permanently.
140
+ * Guards against underflow.
137
141
  */
138
142
  endDynamicUpdate(): void;
139
143
  /**
140
- * Run a dynamic update with UA scroll anchoring disabled, then restore it.
141
- * Accepts a sync or async function and ensures `overflow-anchor` is toggled
142
- * around the operation. If the manager isn't ready yet, it simply executes `fn`.
144
+ * Run a dynamic update with UA scroll anchoring disabled.
145
+ * Accepts a sync or async function and ensures `overflow-anchor` stays 'none'
146
+ * throughout. If the manager isn't ready yet, it simply executes `fn`.
143
147
  */
144
148
  runDynamicUpdate<T>(fn: () => T | Promise<T>): Promise<T>;
145
149
  /**
@@ -241,9 +241,12 @@ export class ReactiveListManager {
241
241
  return this._dynamicUpdateDepth > 0;
242
242
  }
243
243
  /**
244
- * Begin a dynamic update. Handles nested calls: the first call disables UA scroll anchoring,
245
- * subsequent calls just increment depth. Safe to call when not wired; styles are only toggled
246
- * when both container and viewport are ready.
244
+ * Begin a dynamic update. Handles nested calls: the first call ensures UA scroll anchoring
245
+ * is disabled, subsequent calls just increment depth. Safe to call when not wired; styles
246
+ * are only set when both container and viewport are ready.
247
+ *
248
+ * Note: overflow-anchor is kept permanently as 'none' to prevent browser scroll anchoring
249
+ * from interfering with the virtual list's own scroll correction logic.
247
250
  */
248
251
  startDynamicUpdate() {
249
252
  const isOuter = this._dynamicUpdateDepth === 0;
@@ -257,7 +260,8 @@ export class ReactiveListManager {
257
260
  }
258
261
  /**
259
262
  * End a dynamic update started by `startDynamicUpdate`. Handles nesting: only the final
260
- * corresponding end call re-enables UA scroll anchoring. Guards against underflow.
263
+ * corresponding end call completes the update. overflow-anchor remains 'none' permanently.
264
+ * Guards against underflow.
261
265
  */
262
266
  endDynamicUpdate() {
263
267
  if (this._dynamicUpdateDepth <= 0) {
@@ -266,16 +270,16 @@ export class ReactiveListManager {
266
270
  this._dynamicUpdateDepth -= 1;
267
271
  if (this._dynamicUpdateDepth === 0) {
268
272
  if (this._isReady && this._viewportElement) {
269
- this._viewportElement.style.setProperty('overflow-anchor', 'auto');
273
+ this._viewportElement.style.setProperty('overflow-anchor', 'none');
270
274
  }
271
275
  this._dynamicUpdateInProgress = false;
272
276
  this._scheduler.unblock();
273
277
  }
274
278
  }
275
279
  /**
276
- * Run a dynamic update with UA scroll anchoring disabled, then restore it.
277
- * Accepts a sync or async function and ensures `overflow-anchor` is toggled
278
- * around the operation. If the manager isn't ready yet, it simply executes `fn`.
280
+ * Run a dynamic update with UA scroll anchoring disabled.
281
+ * Accepts a sync or async function and ensures `overflow-anchor` stays 'none'
282
+ * throughout. If the manager isn't ready yet, it simply executes `fn`.
279
283
  */
280
284
  async runDynamicUpdate(fn) {
281
285
  this.startDynamicUpdate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "A lightweight, high-performance virtual list component for Svelte 5 that renders large datasets with minimal memory usage. Features include dynamic height support, smooth scrolling, TypeScript support, and efficient DOM recycling. Ideal for infinite scrolling lists, data tables, chat interfaces, and any application requiring the rendering of thousands of items without compromising performance. Zero dependencies and fully customizable.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -62,6 +62,7 @@
62
62
  "@eslint/compat": "^2.0.2",
63
63
  "@eslint/js": "^10.0.1",
64
64
  "@faker-js/faker": "^10.3.0",
65
+ "@playwright/cli": "^0.1.1",
65
66
  "@playwright/test": "^1.58.2",
66
67
  "@sveltejs/adapter-auto": "^7.0.1",
67
68
  "@sveltejs/kit": "^2.52.0",
@@ -72,8 +73,8 @@
72
73
  "@testing-library/svelte": "^5.3.1",
73
74
  "@testing-library/user-event": "^14.6.1",
74
75
  "@types/node": "^25.2.3",
75
- "@typescript-eslint/eslint-plugin": "^8.55.0",
76
- "@typescript-eslint/parser": "^8.55.0",
76
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
77
+ "@typescript-eslint/parser": "^8.56.0",
77
78
  "@vitest/coverage-v8": "^4.0.18",
78
79
  "concurrently": "^9.2.1",
79
80
  "eslint": "^10.0.0",
@@ -95,7 +96,7 @@
95
96
  "tailwindcss": "^4.1.18",
96
97
  "tw-animate-css": "^1.4.0",
97
98
  "typescript": "^5.9.3",
98
- "typescript-eslint": "^8.55.0",
99
+ "typescript-eslint": "^8.56.0",
99
100
  "vite": "^7.3.1",
100
101
  "vitest": "^4.0.18"
101
102
  },