@humanspeak/svelte-virtual-list 0.2.6-beta.7 → 0.3.1-beta.0
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.
- package/dist/SvelteVirtualList.svelte +3 -3
- package/dist/SvelteVirtualList.svelte.d.ts +137 -59
- package/dist/reactive-height-manager/test/TestComponent.svelte +78 -0
- package/dist/reactive-height-manager/test/TestComponent.svelte.d.ts +23 -0
- package/dist/reactive-height-manager/types.d.ts +2 -2
- package/dist/types.d.ts +3 -3
- package/dist/utils/scrollCalculation.js +0 -19
- package/dist/utils/virtualList.js +0 -36
- package/package.json +3 -4
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
MIT License © Humanspeak, Inc.
|
|
62
62
|
-->
|
|
63
63
|
|
|
64
|
-
<script lang="ts">
|
|
64
|
+
<script lang="ts" generics="TItem = any">
|
|
65
65
|
/**
|
|
66
66
|
* SvelteVirtualList Implementation Journey
|
|
67
67
|
*
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
import.meta.env.DEV && import.meta.env.VITE_SVELTE_VIRTUAL_LIST_DEBUG === 'true'
|
|
183
183
|
/**
|
|
184
184
|
* Core configuration props with default values
|
|
185
|
-
* @type {SvelteVirtualListProps}
|
|
185
|
+
* @type {SvelteVirtualListProps<TItem>}
|
|
186
186
|
*/
|
|
187
187
|
const {
|
|
188
188
|
items = [], // Array of items to be rendered in the virtual list
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
mode = 'topToBottom', // Scroll direction mode
|
|
198
198
|
bufferSize = 20, // Number of items to render outside visible area
|
|
199
199
|
testId // Base test ID for component elements (undefined = no data-testid attributes)
|
|
200
|
-
}: SvelteVirtualListProps = $props()
|
|
200
|
+
}: SvelteVirtualListProps<TItem> = $props()
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
203
|
* DOM References and Core State
|
|
@@ -89,6 +89,141 @@
|
|
|
89
89
|
* - Progressive size adjustment system
|
|
90
90
|
*/
|
|
91
91
|
import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from './types.js';
|
|
92
|
+
declare function $$render<TItem = any>(): {
|
|
93
|
+
props: SvelteVirtualListProps<TItem>;
|
|
94
|
+
exports: {
|
|
95
|
+
/**
|
|
96
|
+
* Scrolls the virtual list to the item at the given index.
|
|
97
|
+
*
|
|
98
|
+
* @deprecated This function is deprecated and will be removed in a future version.
|
|
99
|
+
* Use the new scroll method from the component instance instead.
|
|
100
|
+
*
|
|
101
|
+
* @function scrollToIndex
|
|
102
|
+
* @param index The index of the item to scroll to.
|
|
103
|
+
* @param smoothScroll (default: true) Whether to use smooth scrolling.
|
|
104
|
+
* @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Svelte usage:
|
|
108
|
+
* // In your <script> block:
|
|
109
|
+
* import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
|
|
110
|
+
* let virtualList;
|
|
111
|
+
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
112
|
+
*
|
|
113
|
+
* // In your markup:
|
|
114
|
+
* <button onclick={() => virtualList.scrollToIndex(5000)}>
|
|
115
|
+
* Scroll to 5000
|
|
116
|
+
* </button>
|
|
117
|
+
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
118
|
+
* {#snippet renderItem(item)}
|
|
119
|
+
* <div>{item.text}</div>
|
|
120
|
+
* {/snippet}
|
|
121
|
+
* </SvelteVirtualList>
|
|
122
|
+
*
|
|
123
|
+
* @returns {void}
|
|
124
|
+
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
125
|
+
*/ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
|
|
126
|
+
/**
|
|
127
|
+
* Scrolls the virtual list to the item at the given index using a type-based options approach.
|
|
128
|
+
*
|
|
129
|
+
* @function scroll
|
|
130
|
+
* @param options Configuration options for scrolling behavior.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // Svelte usage:
|
|
134
|
+
* // In your <script> block:
|
|
135
|
+
* import SvelteVirtualList from './index.js';
|
|
136
|
+
* let virtualList;
|
|
137
|
+
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
138
|
+
*
|
|
139
|
+
* <button onclick={() => virtualList.scroll({ index: 5000 })}>
|
|
140
|
+
* Scroll to 5000
|
|
141
|
+
* </button>
|
|
142
|
+
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
143
|
+
* {#snippet renderItem(item)}
|
|
144
|
+
* <div>{item.text}</div>
|
|
145
|
+
* {/snippet}
|
|
146
|
+
* </SvelteVirtualList>
|
|
147
|
+
*
|
|
148
|
+
* @returns {Promise<void>} Promise that resolves when scrolling is complete
|
|
149
|
+
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
150
|
+
*/ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
|
|
151
|
+
};
|
|
152
|
+
bindings: "";
|
|
153
|
+
slots: {};
|
|
154
|
+
events: {};
|
|
155
|
+
};
|
|
156
|
+
declare class __sveltets_Render<TItem = any> {
|
|
157
|
+
props(): ReturnType<typeof $$render<TItem>>['props'];
|
|
158
|
+
events(): ReturnType<typeof $$render<TItem>>['events'];
|
|
159
|
+
slots(): ReturnType<typeof $$render<TItem>>['slots'];
|
|
160
|
+
bindings(): "";
|
|
161
|
+
exports(): {
|
|
162
|
+
/**
|
|
163
|
+
* Scrolls the virtual list to the item at the given index.
|
|
164
|
+
*
|
|
165
|
+
* @deprecated This function is deprecated and will be removed in a future version.
|
|
166
|
+
* Use the new scroll method from the component instance instead.
|
|
167
|
+
*
|
|
168
|
+
* @function scrollToIndex
|
|
169
|
+
* @param index The index of the item to scroll to.
|
|
170
|
+
* @param smoothScroll (default: true) Whether to use smooth scrolling.
|
|
171
|
+
* @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* // Svelte usage:
|
|
175
|
+
* // In your <script> block:
|
|
176
|
+
* import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
|
|
177
|
+
* let virtualList;
|
|
178
|
+
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
179
|
+
*
|
|
180
|
+
* // In your markup:
|
|
181
|
+
* <button onclick={() => virtualList.scrollToIndex(5000)}>
|
|
182
|
+
* Scroll to 5000
|
|
183
|
+
* </button>
|
|
184
|
+
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
185
|
+
* {#snippet renderItem(item)}
|
|
186
|
+
* <div>{item.text}</div>
|
|
187
|
+
* {/snippet}
|
|
188
|
+
* </SvelteVirtualList>
|
|
189
|
+
*
|
|
190
|
+
* @returns {void}
|
|
191
|
+
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
192
|
+
*/ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
|
|
193
|
+
/**
|
|
194
|
+
* Scrolls the virtual list to the item at the given index using a type-based options approach.
|
|
195
|
+
*
|
|
196
|
+
* @function scroll
|
|
197
|
+
* @param options Configuration options for scrolling behavior.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* // Svelte usage:
|
|
201
|
+
* // In your <script> block:
|
|
202
|
+
* import SvelteVirtualList from './index.js';
|
|
203
|
+
* let virtualList;
|
|
204
|
+
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
205
|
+
*
|
|
206
|
+
* <button onclick={() => virtualList.scroll({ index: 5000 })}>
|
|
207
|
+
* Scroll to 5000
|
|
208
|
+
* </button>
|
|
209
|
+
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
210
|
+
* {#snippet renderItem(item)}
|
|
211
|
+
* <div>{item.text}</div>
|
|
212
|
+
* {/snippet}
|
|
213
|
+
* </SvelteVirtualList>
|
|
214
|
+
*
|
|
215
|
+
* @returns {Promise<void>} Promise that resolves when scrolling is complete
|
|
216
|
+
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
217
|
+
*/ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
interface $$IsomorphicComponent {
|
|
221
|
+
new <TItem = any>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TItem>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TItem>['props']>, ReturnType<__sveltets_Render<TItem>['events']>, ReturnType<__sveltets_Render<TItem>['slots']>> & {
|
|
222
|
+
$$bindings?: ReturnType<__sveltets_Render<TItem>['bindings']>;
|
|
223
|
+
} & ReturnType<__sveltets_Render<TItem>['exports']>;
|
|
224
|
+
<TItem = any>(internal: unknown, props: ReturnType<__sveltets_Render<TItem>['props']> & {}): ReturnType<__sveltets_Render<TItem>['exports']>;
|
|
225
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
226
|
+
}
|
|
92
227
|
/**
|
|
93
228
|
* SvelteVirtualList
|
|
94
229
|
*
|
|
@@ -151,63 +286,6 @@ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from
|
|
|
151
286
|
*
|
|
152
287
|
* MIT License © Humanspeak, Inc.
|
|
153
288
|
*/
|
|
154
|
-
declare const SvelteVirtualList:
|
|
155
|
-
|
|
156
|
-
* Scrolls the virtual list to the item at the given index.
|
|
157
|
-
*
|
|
158
|
-
* @deprecated This function is deprecated and will be removed in a future version.
|
|
159
|
-
* Use the new scroll method from the component instance instead.
|
|
160
|
-
*
|
|
161
|
-
* @function scrollToIndex
|
|
162
|
-
* @param index The index of the item to scroll to.
|
|
163
|
-
* @param smoothScroll (default: true) Whether to use smooth scrolling.
|
|
164
|
-
* @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* // Svelte usage:
|
|
168
|
-
* // In your <script> block:
|
|
169
|
-
* import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
|
|
170
|
-
* let virtualList;
|
|
171
|
-
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
172
|
-
*
|
|
173
|
-
* // In your markup:
|
|
174
|
-
* <button onclick={() => virtualList.scrollToIndex(5000)}>
|
|
175
|
-
* Scroll to 5000
|
|
176
|
-
* </button>
|
|
177
|
-
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
178
|
-
* {#snippet renderItem(item)}
|
|
179
|
-
* <div>{item.text}</div>
|
|
180
|
-
* {/snippet}
|
|
181
|
-
* </SvelteVirtualList>
|
|
182
|
-
*
|
|
183
|
-
* @returns {void}
|
|
184
|
-
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
185
|
-
*/ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
|
|
186
|
-
/**
|
|
187
|
-
* Scrolls the virtual list to the item at the given index using a type-based options approach.
|
|
188
|
-
*
|
|
189
|
-
* @function scroll
|
|
190
|
-
* @param options Configuration options for scrolling behavior.
|
|
191
|
-
*
|
|
192
|
-
* @example
|
|
193
|
-
* // Svelte usage:
|
|
194
|
-
* // In your <script> block:
|
|
195
|
-
* import SvelteVirtualList from './index.js';
|
|
196
|
-
* let virtualList;
|
|
197
|
-
* const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
198
|
-
*
|
|
199
|
-
* <button onclick={() => virtualList.scroll({ index: 5000 })}>
|
|
200
|
-
* Scroll to 5000
|
|
201
|
-
* </button>
|
|
202
|
-
* <SvelteVirtualList {items} bind:this={virtualList}>
|
|
203
|
-
* {#snippet renderItem(item)}
|
|
204
|
-
* <div>{item.text}</div>
|
|
205
|
-
* {/snippet}
|
|
206
|
-
* </SvelteVirtualList>
|
|
207
|
-
*
|
|
208
|
-
* @returns {Promise<void>} Promise that resolves when scrolling is complete
|
|
209
|
-
* @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
|
|
210
|
-
*/ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
|
|
211
|
-
}, "">;
|
|
212
|
-
type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
|
|
289
|
+
declare const SvelteVirtualList: $$IsomorphicComponent;
|
|
290
|
+
type SvelteVirtualList<TItem = any> = InstanceType<typeof SvelteVirtualList<TItem>>;
|
|
213
291
|
export default SvelteVirtualList;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte'
|
|
3
|
+
import { ReactiveHeightManager } from '../ReactiveHeightManager.svelte.js'
|
|
4
|
+
import type { HeightChange, HeightManagerConfig } from '../types.js'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
config: HeightManagerConfig
|
|
8
|
+
onReactiveUpdate?: (data: {
|
|
9
|
+
totalHeight: number
|
|
10
|
+
measuredCount: number
|
|
11
|
+
effectRuns: number
|
|
12
|
+
}) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { config, onReactiveUpdate }: Props = $props()
|
|
16
|
+
|
|
17
|
+
// Create the manager
|
|
18
|
+
const manager = new ReactiveHeightManager(config)
|
|
19
|
+
|
|
20
|
+
// Derived reactive values (clean, no side effects)
|
|
21
|
+
let currentTotalHeight = $derived(manager.totalHeight)
|
|
22
|
+
let currentMeasuredCount = $derived(manager.measuredCount)
|
|
23
|
+
|
|
24
|
+
// Effect run counter (non-reactive - just for tracking)
|
|
25
|
+
let effectRunCount = 0
|
|
26
|
+
|
|
27
|
+
// Reactive counter for DOM display (separate from effect logic)
|
|
28
|
+
let displayEffectRuns = $state(0)
|
|
29
|
+
|
|
30
|
+
// Simple effect that just notifies - no state modification
|
|
31
|
+
$effect(() => {
|
|
32
|
+
// Read the current values (triggers when manager changes)
|
|
33
|
+
const totalHeight = manager.totalHeight
|
|
34
|
+
const measuredCount = manager.measuredCount
|
|
35
|
+
|
|
36
|
+
// Increment counters
|
|
37
|
+
effectRunCount++
|
|
38
|
+
displayEffectRuns = effectRunCount // Update reactive display
|
|
39
|
+
|
|
40
|
+
// Notify parent with fresh values each time
|
|
41
|
+
onReactiveUpdate?.({
|
|
42
|
+
totalHeight,
|
|
43
|
+
measuredCount,
|
|
44
|
+
effectRuns: effectRunCount
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Export methods for testing
|
|
49
|
+
export function processDirtyHeights(changes: HeightChange[]) {
|
|
50
|
+
manager.processDirtyHeights(changes)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function updateItemLength(newLength: number) {
|
|
54
|
+
manager.updateItemLength(newLength)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function setItemHeight(height: number) {
|
|
58
|
+
manager.itemHeight = height
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getReactiveData() {
|
|
62
|
+
return {
|
|
63
|
+
totalHeight: currentTotalHeight,
|
|
64
|
+
measuredCount: currentMeasuredCount,
|
|
65
|
+
effectRuns: displayEffectRuns
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getManager() {
|
|
70
|
+
return manager
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<div data-testid="reactive-test-component">
|
|
75
|
+
<div data-testid="total-height">{currentTotalHeight}</div>
|
|
76
|
+
<div data-testid="measured-count">{currentMeasuredCount}</div>
|
|
77
|
+
<div data-testid="effect-runs">{displayEffectRuns}</div>
|
|
78
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactiveHeightManager } from '../ReactiveHeightManager.svelte.js';
|
|
2
|
+
import type { HeightChange, HeightManagerConfig } from '../types.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
config: HeightManagerConfig;
|
|
5
|
+
onReactiveUpdate?: (data: {
|
|
6
|
+
totalHeight: number;
|
|
7
|
+
measuredCount: number;
|
|
8
|
+
effectRuns: number;
|
|
9
|
+
}) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const TestComponent: import("svelte").Component<Props, {
|
|
12
|
+
processDirtyHeights: (changes: HeightChange[]) => void;
|
|
13
|
+
updateItemLength: (newLength: number) => void;
|
|
14
|
+
setItemHeight: (height: number) => void;
|
|
15
|
+
getReactiveData: () => {
|
|
16
|
+
totalHeight: number;
|
|
17
|
+
measuredCount: number;
|
|
18
|
+
effectRuns: number;
|
|
19
|
+
};
|
|
20
|
+
getManager: () => ReactiveHeightManager;
|
|
21
|
+
}, "">;
|
|
22
|
+
type TestComponent = ReturnType<typeof TestComponent>;
|
|
23
|
+
export default TestComponent;
|
|
@@ -8,8 +8,8 @@ export interface HeightChange {
|
|
|
8
8
|
readonly index: number;
|
|
9
9
|
/** The previous height (undefined if first measurement) */
|
|
10
10
|
readonly oldHeight: number | undefined;
|
|
11
|
-
/** The new height measurement */
|
|
12
|
-
readonly newHeight: number;
|
|
11
|
+
/** The new height measurement (undefined represents removal/unset) */
|
|
12
|
+
readonly newHeight: number | undefined;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Configuration options for ReactiveHeightManager
|
package/dist/types.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type SvelteVirtualListMode = 'topToBottom' | 'bottomToTop';
|
|
|
10
10
|
*
|
|
11
11
|
* @typedef {Object} SvelteVirtualListProps
|
|
12
12
|
*/
|
|
13
|
-
export type SvelteVirtualListProps = {
|
|
13
|
+
export type SvelteVirtualListProps<TItem = any> = {
|
|
14
14
|
/**
|
|
15
15
|
* Number of items to render outside the visible viewport for smooth scrolling.
|
|
16
16
|
* @default 20
|
|
@@ -41,7 +41,7 @@ export type SvelteVirtualListProps = {
|
|
|
41
41
|
/**
|
|
42
42
|
* The complete array of items to be virtualized.
|
|
43
43
|
*/
|
|
44
|
-
items:
|
|
44
|
+
items: TItem[];
|
|
45
45
|
/**
|
|
46
46
|
* CSS class to apply to individual item containers.
|
|
47
47
|
*/
|
|
@@ -54,7 +54,7 @@ export type SvelteVirtualListProps = {
|
|
|
54
54
|
/**
|
|
55
55
|
* Svelte snippet function that defines how each item should be rendered. Receives the item and its index as arguments.
|
|
56
56
|
*/
|
|
57
|
-
renderItem: Snippet<[item:
|
|
57
|
+
renderItem: Snippet<[item: TItem, index: number]>;
|
|
58
58
|
/**
|
|
59
59
|
* Base test ID for component elements to facilitate testing.
|
|
60
60
|
*/
|
|
@@ -110,28 +110,16 @@ const calculateBottomToTopScrollTarget = (params) => {
|
|
|
110
110
|
*/
|
|
111
111
|
const calculateTopToBottomScrollTarget = (params) => {
|
|
112
112
|
const { align, targetIndex, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
|
|
113
|
-
console.log('[DEBUG] calculateTopToBottomScrollTarget:', {
|
|
114
|
-
align,
|
|
115
|
-
targetIndex,
|
|
116
|
-
calculatedItemHeight,
|
|
117
|
-
height,
|
|
118
|
-
scrollTop,
|
|
119
|
-
firstVisibleIndex,
|
|
120
|
-
lastVisibleIndex,
|
|
121
|
-
heightCacheKeys: Object.keys(heightCache).length
|
|
122
|
-
});
|
|
123
113
|
if (align === 'auto') {
|
|
124
114
|
// If item is above the viewport, align to top
|
|
125
115
|
if (targetIndex < firstVisibleIndex) {
|
|
126
116
|
const scrollTarget = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
127
|
-
console.log(`[DEBUG] Item ${targetIndex} above viewport (${firstVisibleIndex}), scrolling to top:`, scrollTarget);
|
|
128
117
|
return scrollTarget;
|
|
129
118
|
}
|
|
130
119
|
// If item is below the viewport, align to bottom
|
|
131
120
|
else if (targetIndex > lastVisibleIndex - 1) {
|
|
132
121
|
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
133
122
|
const scrollTarget = Math.max(0, itemBottom - height);
|
|
134
|
-
console.log(`[DEBUG] Item ${targetIndex} below viewport (${lastVisibleIndex}), scrolling to bottom:`, scrollTarget);
|
|
135
123
|
return scrollTarget;
|
|
136
124
|
}
|
|
137
125
|
else {
|
|
@@ -140,12 +128,6 @@ const calculateTopToBottomScrollTarget = (params) => {
|
|
|
140
128
|
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
141
129
|
const distanceToTop = Math.abs(scrollTop - itemTop);
|
|
142
130
|
const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
|
|
143
|
-
console.log(`[DEBUG] Item ${targetIndex} visible, choosing nearest edge:`, {
|
|
144
|
-
itemTop,
|
|
145
|
-
itemBottom,
|
|
146
|
-
distanceToTop,
|
|
147
|
-
distanceToBottom
|
|
148
|
-
});
|
|
149
131
|
if (distanceToTop < distanceToBottom) {
|
|
150
132
|
return itemTop;
|
|
151
133
|
}
|
|
@@ -156,7 +138,6 @@ const calculateTopToBottomScrollTarget = (params) => {
|
|
|
156
138
|
}
|
|
157
139
|
else if (align === 'top') {
|
|
158
140
|
const scrollTarget = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
159
|
-
console.log(`[DEBUG] Align to top for index ${targetIndex}:`, scrollTarget);
|
|
160
141
|
return scrollTarget;
|
|
161
142
|
}
|
|
162
143
|
else if (align === 'bottom') {
|
|
@@ -33,14 +33,6 @@ export const calculateScrollPosition = (totalItems, itemHeight, containerHeight)
|
|
|
33
33
|
*/
|
|
34
34
|
export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, totalItems, bufferSize, mode, atBottom, wasAtBottomBeforeHeightChange, lastVisibleRange, totalContentHeight) => {
|
|
35
35
|
if (mode === 'bottomToTop') {
|
|
36
|
-
// if (wasAtBottomBeforeHeightChange && lastVisibleRange) {
|
|
37
|
-
// // console.log('calculateVisibleRange:wasAtBottomBeforeHeightChange', {
|
|
38
|
-
// // lastVisibleRange,
|
|
39
|
-
// // atBottom,
|
|
40
|
-
// // wasAtBottomBeforeHeightChange
|
|
41
|
-
// // })
|
|
42
|
-
// return lastVisibleRange
|
|
43
|
-
// }
|
|
44
36
|
const visibleCount = Math.ceil(viewportHeight / itemHeight) + 1;
|
|
45
37
|
// In bottomToTop mode, scrollTop represents distance from the total content end
|
|
46
38
|
// scrollTop = 0 means we're at the beginning (showing first items)
|
|
@@ -50,37 +42,16 @@ export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, tot
|
|
|
50
42
|
// Convert scrollTop to "distance from start" for bottomToTop
|
|
51
43
|
const distanceFromStart = maxScrollTop - scrollTop;
|
|
52
44
|
const startIndex = Math.floor(distanceFromStart / itemHeight);
|
|
53
|
-
// console.log(
|
|
54
|
-
// `[DEBUG] calculateVisibleRange bottomToTop: scrollTop=${scrollTop}, maxScrollTop=${maxScrollTop}, distanceFromStart=${distanceFromStart}, startIndex=${startIndex}`
|
|
55
|
-
// )
|
|
56
45
|
// Safeguard: handle edge cases
|
|
57
46
|
if (startIndex < 0) {
|
|
58
47
|
// We're scrolled beyond the maximum (showing first items)
|
|
59
48
|
const start = 0;
|
|
60
49
|
const end = Math.min(totalItems, visibleCount + bufferSize * 2);
|
|
61
|
-
// console.log(
|
|
62
|
-
// `[DEBUG] calculateVisibleRange (startIndex < 0): start=${start}, end=${end}`
|
|
63
|
-
// )
|
|
64
|
-
// console.log('calculateVisibleRange:startIndex < 0', {
|
|
65
|
-
// start,
|
|
66
|
-
// end,
|
|
67
|
-
// atBottom,
|
|
68
|
-
// wasAtBottomBeforeHeightChange,
|
|
69
|
-
// lastVisibleRange
|
|
70
|
-
// })
|
|
71
50
|
return { start, end };
|
|
72
51
|
}
|
|
73
52
|
// Add buffer to both ends
|
|
74
53
|
const start = Math.max(0, startIndex - bufferSize);
|
|
75
54
|
const end = Math.min(totalItems, startIndex + visibleCount + bufferSize);
|
|
76
|
-
// console.log(`[DEBUG] calculateVisibleRange result: start=${start}, end=${end}`)
|
|
77
|
-
// console.log('calculateVisibleRange:startIndex >= 0', {
|
|
78
|
-
// start,
|
|
79
|
-
// end,
|
|
80
|
-
// atBottom,
|
|
81
|
-
// wasAtBottomBeforeHeightChange,
|
|
82
|
-
// lastVisibleRange
|
|
83
|
-
// })
|
|
84
55
|
return { start, end };
|
|
85
56
|
}
|
|
86
57
|
else {
|
|
@@ -102,13 +73,6 @@ export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, tot
|
|
|
102
73
|
end: adjustedEnd
|
|
103
74
|
};
|
|
104
75
|
}
|
|
105
|
-
// console.log('calculateVisibleRange:isNotAtBottom', {
|
|
106
|
-
// start: Math.max(0, start - bufferSize),
|
|
107
|
-
// end: Math.min(totalItems, end + bufferSize),
|
|
108
|
-
// atBottom,
|
|
109
|
-
// wasAtBottomBeforeHeightChange,
|
|
110
|
-
// lastVisibleRange
|
|
111
|
-
// })
|
|
112
76
|
// Add buffer to both ends
|
|
113
77
|
const finalStart = Math.max(0, start - bufferSize);
|
|
114
78
|
const finalEnd = Math.min(totalItems, end + bufferSize);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-virtual-list",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1-beta.0",
|
|
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",
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
"dist",
|
|
45
45
|
"!dist/**/*.test.*",
|
|
46
46
|
"!dist/**/*.spec.*",
|
|
47
|
-
"!dist/test/**/*"
|
|
48
|
-
"!dist/reactive-height-manager/test/**/*"
|
|
47
|
+
"!dist/test/**/*"
|
|
49
48
|
],
|
|
50
49
|
"scripts": {
|
|
51
50
|
"build": "vite build && npm run package",
|
|
@@ -110,7 +109,7 @@
|
|
|
110
109
|
"svelte-check": "^4.3.1",
|
|
111
110
|
"typescript": "^5.9.2",
|
|
112
111
|
"typescript-eslint": "^8.39.0",
|
|
113
|
-
"vite": "^7.1.
|
|
112
|
+
"vite": "^7.1.1",
|
|
114
113
|
"vitest": "^3.2.4"
|
|
115
114
|
},
|
|
116
115
|
"peerDependencies": {
|