@humanspeak/svelte-virtual-list 0.4.4 → 0.4.6

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.
@@ -167,8 +167,7 @@
167
167
  calculateVisibleRange,
168
168
  clampValue,
169
169
  updateHeightAndScroll as utilsUpdateHeightAndScroll,
170
- getScrollOffsetForIndex,
171
- buildBlockSums
170
+ getScrollOffsetForIndex
172
171
  } from './utils/virtualList.js'
173
172
  import { createDebugInfo, shouldShowDebugInfo } from './utils/virtualListDebug.js'
174
173
  import { calculateScrollTarget } from './utils/scrollCalculation.js'
@@ -260,7 +259,7 @@
260
259
  const est = heightManager.averageHeight
261
260
  const maxScrollTop = Math.max(0, totalHeight - (height || 0))
262
261
  // Offset from start to anchored item
263
- const blockSums = buildBlockSums(cache, est, items.length)
262
+ const blockSums = heightManager.getBlockSums()
264
263
  const offsetToIndex = getScrollOffsetForIndex(cache, est, anchorIndex, blockSums)
265
264
  const currentTop = heightManager.viewport.scrollTop
266
265
  let offsetWithin: number
@@ -286,7 +285,7 @@
286
285
  if (!pendingAnchorReconcile) return
287
286
  const cache = heightManager.getHeightCache()
288
287
  const est = heightManager.averageHeight
289
- const blockSums = buildBlockSums(cache, est, items.length)
288
+ const blockSums = heightManager.getBlockSums()
290
289
  const offsetToIndex = getScrollOffsetForIndex(
291
290
  cache,
292
291
  est,
@@ -999,19 +998,16 @@
999
998
  }
1000
999
  }
1001
1000
 
1002
- lastVisibleRange = calculateVisibleRange(
1003
- heightManager.scrollTop,
1001
+ lastVisibleRange = calculateVisibleRange({
1002
+ scrollTop: heightManager.scrollTop,
1004
1003
  viewportHeight,
1005
- heightManager.averageHeight,
1006
- items.length,
1004
+ itemHeight: heightManager.averageHeight,
1005
+ totalItems: items.length,
1007
1006
  bufferSize,
1008
1007
  mode,
1009
- atBottom,
1010
- wasAtBottomBeforeHeightChange,
1011
- lastVisibleRange,
1012
- totalHeight,
1013
- heightManager.getHeightCache()
1014
- )
1008
+ totalContentHeight: totalHeight,
1009
+ heightCache: heightManager.getHeightCache()
1010
+ })
1015
1011
 
1016
1012
  return lastVisibleRange
1017
1013
  })
@@ -49,22 +49,45 @@ export declare const clampValue: (value: number, min: number, max: number) => nu
49
49
  * @returns {number} The maximum scroll position in pixels
50
50
  */
51
51
  export declare const calculateScrollPosition: (totalItems: number, itemHeight: number, containerHeight: number) => number;
52
+ /** Options for {@link calculateVisibleRange}. */
53
+ export interface VisibleRangeOptions {
54
+ scrollTop: number;
55
+ viewportHeight: number;
56
+ /** Estimated/average item height used as fallback for unmeasured items. */
57
+ itemHeight: number;
58
+ totalItems: number;
59
+ /** Number of extra items to render outside the visible area. */
60
+ bufferSize: number;
61
+ mode: SvelteVirtualListMode;
62
+ /** Pre-calculated total content height; defaults to `totalItems * itemHeight`. */
63
+ totalContentHeight?: number;
64
+ /** Measured item heights keyed by index; used in topToBottom mode to walk actual heights instead of dividing by average. */
65
+ heightCache?: Record<number, number>;
66
+ }
52
67
  /**
53
68
  * Determines the range of items that should be rendered in the virtual list.
54
69
  *
55
- * This function calculates which items should be visible based on the current scroll position,
56
- * viewport size, and scroll direction. It includes a buffer zone to enable smooth scrolling
70
+ * Calculates which items should be visible based on the current scroll position,
71
+ * viewport size, and scroll direction. Includes a buffer zone to enable smooth scrolling
57
72
  * and prevent visible gaps during rapid scroll movements.
58
73
  *
59
- * @param {number} scrollTop - Current scroll position in pixels
60
- * @param {number} viewportHeight - Height of the visible area in pixels
61
- * @param {number} itemHeight - Height of each list item in pixels
62
- * @param {number} totalItems - Total number of items in the list
63
- * @param {number} bufferSize - Number of items to render outside the visible area
64
- * @param {SvelteVirtualListMode} mode - Scroll direction mode
65
- * @returns {SvelteVirtualListPreviousVisibleRange} Range of indices to render
74
+ * @param options - Inputs used to compute the visible range (see {@link VisibleRangeOptions}).
75
+ * @returns Range of indices to render.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const range = calculateVisibleRange({
80
+ * scrollTop: 200,
81
+ * viewportHeight: 400,
82
+ * itemHeight: 40,
83
+ * totalItems: 1000,
84
+ * bufferSize: 2,
85
+ * mode: 'topToBottom'
86
+ * })
87
+ * // range => { start: 3, end: 15 }
88
+ * ```
66
89
  */
67
- export declare const calculateVisibleRange: (scrollTop: number, viewportHeight: number, itemHeight: number, totalItems: number, bufferSize: number, mode: SvelteVirtualListMode, atBottom: boolean, wasAtBottomBeforeHeightChange: boolean, lastVisibleRange: SvelteVirtualListPreviousVisibleRange | null, totalContentHeight?: number, heightCache?: Record<number, number>) => SvelteVirtualListPreviousVisibleRange;
90
+ export declare const calculateVisibleRange: ({ scrollTop, viewportHeight, itemHeight, totalItems, bufferSize, mode, totalContentHeight, heightCache }: VisibleRangeOptions) => SvelteVirtualListPreviousVisibleRange;
68
91
  /**
69
92
  * Calculates the CSS transform value for positioning the virtual list items.
70
93
  *
@@ -56,19 +56,27 @@ export const calculateScrollPosition = (totalItems, itemHeight, containerHeight)
56
56
  /**
57
57
  * Determines the range of items that should be rendered in the virtual list.
58
58
  *
59
- * This function calculates which items should be visible based on the current scroll position,
60
- * viewport size, and scroll direction. It includes a buffer zone to enable smooth scrolling
59
+ * Calculates which items should be visible based on the current scroll position,
60
+ * viewport size, and scroll direction. Includes a buffer zone to enable smooth scrolling
61
61
  * and prevent visible gaps during rapid scroll movements.
62
62
  *
63
- * @param {number} scrollTop - Current scroll position in pixels
64
- * @param {number} viewportHeight - Height of the visible area in pixels
65
- * @param {number} itemHeight - Height of each list item in pixels
66
- * @param {number} totalItems - Total number of items in the list
67
- * @param {number} bufferSize - Number of items to render outside the visible area
68
- * @param {SvelteVirtualListMode} mode - Scroll direction mode
69
- * @returns {SvelteVirtualListPreviousVisibleRange} Range of indices to render
63
+ * @param options - Inputs used to compute the visible range (see {@link VisibleRangeOptions}).
64
+ * @returns Range of indices to render.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const range = calculateVisibleRange({
69
+ * scrollTop: 200,
70
+ * viewportHeight: 400,
71
+ * itemHeight: 40,
72
+ * totalItems: 1000,
73
+ * bufferSize: 2,
74
+ * mode: 'topToBottom'
75
+ * })
76
+ * // range => { start: 3, end: 15 }
77
+ * ```
70
78
  */
71
- export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, totalItems, bufferSize, mode, atBottom, wasAtBottomBeforeHeightChange, lastVisibleRange, totalContentHeight, heightCache) => {
79
+ export const calculateVisibleRange = ({ scrollTop, viewportHeight, itemHeight, totalItems, bufferSize, mode, totalContentHeight, heightCache }) => {
72
80
  if (mode === 'bottomToTop') {
73
81
  const visibleCount = Math.ceil(viewportHeight / itemHeight) + 1;
74
82
  // In bottomToTop mode, scrollTop represents distance from the total content end
@@ -92,8 +100,25 @@ export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, tot
92
100
  return { start, end };
93
101
  }
94
102
  else {
95
- const start = Math.floor(scrollTop / itemHeight);
96
- const end = Math.min(totalItems, start + Math.ceil(viewportHeight / itemHeight) + 1);
103
+ // Walk forward through measured heights to find the correct start index
104
+ // instead of dividing by average height (which is wrong for variable-height items).
105
+ let start = 0;
106
+ let acc = 0;
107
+ while (start < totalItems) {
108
+ const h = getValidHeight(heightCache?.[start], itemHeight);
109
+ if (acc + h > scrollTop)
110
+ break;
111
+ acc += h;
112
+ start++;
113
+ }
114
+ // Walk forward from start to find end
115
+ let end = start;
116
+ let viewAcc = 0;
117
+ while (end < totalItems && viewAcc < viewportHeight) {
118
+ viewAcc += getValidHeight(heightCache?.[end], itemHeight);
119
+ end++;
120
+ }
121
+ end = Math.min(totalItems, end + 1); // +1 to ensure partial items are visible
97
122
  // Safeguard for topToBottom: ensure last item is fully visible when at max scroll
98
123
  const totalHeight = totalContentHeight ?? totalItems * itemHeight;
99
124
  const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
@@ -104,11 +129,9 @@ export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, tot
104
129
  // Pack from the end using measured heights when available: walk backward until viewport filled
105
130
  const adjustedEnd = totalItems;
106
131
  let startCore = adjustedEnd;
107
- let acc = 0;
108
- const getH = (i) => getValidHeight(heightCache ? heightCache[i] : undefined, itemHeight);
109
- while (startCore > 0 && acc < viewportHeight) {
110
- const h = getH(startCore - 1);
111
- acc += h;
132
+ let backAcc = 0;
133
+ while (startCore > 0 && backAcc < viewportHeight) {
134
+ backAcc += getValidHeight(heightCache?.[startCore - 1], itemHeight);
112
135
  startCore -= 1;
113
136
  }
114
137
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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",
@@ -73,8 +73,8 @@
73
73
  "@testing-library/svelte": "^5.3.1",
74
74
  "@testing-library/user-event": "^14.6.1",
75
75
  "@types/node": "^25.5.0",
76
- "@typescript-eslint/eslint-plugin": "^8.57.2",
77
- "@typescript-eslint/parser": "^8.57.2",
76
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
77
+ "@typescript-eslint/parser": "^8.58.0",
78
78
  "@vitest/coverage-v8": "^4.1.2",
79
79
  "eslint": "^10.1.0",
80
80
  "eslint-config-prettier": "^10.1.8",
@@ -90,12 +90,12 @@
90
90
  "prettier-plugin-svelte": "^3.5.1",
91
91
  "prettier-plugin-tailwindcss": "^0.7.2",
92
92
  "publint": "^0.3.18",
93
- "svelte": "^5.55.0",
93
+ "svelte": "^5.55.1",
94
94
  "svelte-check": "^4.4.5",
95
95
  "tailwindcss": "^4.2.2",
96
96
  "tw-animate-css": "^1.4.0",
97
97
  "typescript": "^5.9.3",
98
- "typescript-eslint": "^8.57.2",
98
+ "typescript-eslint": "^8.58.0",
99
99
  "vite": "^8.0.3",
100
100
  "vitest": "^4.1.2"
101
101
  },