@humanspeak/svelte-virtual-list 0.3.3 → 0.3.5
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/README.md +1 -1
- package/dist/SvelteVirtualList.svelte +129 -45
- package/dist/reactive-list-manager/RecomputeScheduler.d.ts +1 -0
- package/dist/reactive-list-manager/RecomputeScheduler.js +39 -6
- package/dist/utils/raf.d.ts +1 -1
- package/dist/utils/raf.js +4 -4
- package/dist/utils/virtualListDebug.d.ts +4 -4
- package/dist/utils/virtualListDebug.js +4 -4
- package/package.json +20 -19
package/README.md
CHANGED
|
@@ -176,6 +176,9 @@
|
|
|
176
176
|
import { onMount, tick, untrack } from 'svelte'
|
|
177
177
|
|
|
178
178
|
const rafSchedule = createRafScheduler()
|
|
179
|
+
// Per-instance correction guard to avoid same-frame tug-of-war per viewport
|
|
180
|
+
const GLOBAL_CORRECTION_COOLDOWN = 16
|
|
181
|
+
const lastCorrectionTimestampByViewport = new WeakMap<HTMLElement, number>()
|
|
179
182
|
// Package-specific debug flag - safe for library distribution
|
|
180
183
|
// Enable with: PUBLIC_SVELTE_VIRTUAL_LIST_DEBUG=true (preferred) or SVELTE_VIRTUAL_LIST_DEBUG=true
|
|
181
184
|
// Avoid SvelteKit-only $env imports so library works in non-Kit/Vitest contexts
|
|
@@ -251,6 +254,18 @@
|
|
|
251
254
|
itemHeight: defaultEstimatedItemHeight,
|
|
252
255
|
internalDebug: INTERNAL_DEBUG
|
|
253
256
|
})
|
|
257
|
+
const instanceId = Math.random().toString(36).slice(2, 7)
|
|
258
|
+
|
|
259
|
+
// Centralized debug logger gated by flags
|
|
260
|
+
const log = (tag: string, payload?: unknown) => {
|
|
261
|
+
if (!debug && !INTERNAL_DEBUG) return
|
|
262
|
+
try {
|
|
263
|
+
const ts = new Date().toISOString().split('T')[1]?.replace('Z', '')
|
|
264
|
+
console.info(`[SVL][${instanceId}] ${ts} ${tag}`, payload ?? '')
|
|
265
|
+
} catch {
|
|
266
|
+
// no-op
|
|
267
|
+
}
|
|
268
|
+
}
|
|
254
269
|
|
|
255
270
|
// Dynamic update coordination to avoid UA scroll anchoring interference
|
|
256
271
|
let suppressBottomAnchoringUntilMs = $state(0)
|
|
@@ -339,11 +354,21 @@
|
|
|
339
354
|
mode === 'bottomToTop' &&
|
|
340
355
|
wasAtBottomBeforeHeightChange &&
|
|
341
356
|
!programmaticScrollInProgress &&
|
|
342
|
-
performance.now() >= suppressBottomAnchoringUntilMs
|
|
343
|
-
!heightManager.isDynamicUpdateInProgress
|
|
357
|
+
performance.now() >= suppressBottomAnchoringUntilMs
|
|
344
358
|
) {
|
|
359
|
+
// Prevent same-frame corrections; defer if this viewport just corrected
|
|
360
|
+
const now = performance.now()
|
|
361
|
+
const viewportEl = heightManager.viewport
|
|
362
|
+
const lastCorrectionMs = lastCorrectionTimestampByViewport.get(viewportEl) ?? 0
|
|
363
|
+
if (now - lastCorrectionMs < GLOBAL_CORRECTION_COOLDOWN) {
|
|
364
|
+
suppressBottomAnchoringUntilMs = now + 50
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
lastCorrectionTimestampByViewport.set(viewportEl, now)
|
|
368
|
+
|
|
345
369
|
// Step 1: Scroll to approximate position to ensure Item 0 gets rendered in virtual viewport
|
|
346
370
|
const approximateScrollTop = Math.max(0, totalHeight() - height)
|
|
371
|
+
log('b2t-correction-approx', { approximateScrollTop })
|
|
347
372
|
heightManager.viewport.scrollTop = approximateScrollTop
|
|
348
373
|
heightManager.scrollTop = approximateScrollTop
|
|
349
374
|
|
|
@@ -353,15 +378,29 @@
|
|
|
353
378
|
'[data-original-index="0"]'
|
|
354
379
|
)
|
|
355
380
|
if (item0Element) {
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
381
|
+
// Verify alignment via rects; if off, perform one-time scrollIntoView
|
|
382
|
+
const contRect = heightManager.viewport.getBoundingClientRect()
|
|
383
|
+
const itemRect = (item0Element as HTMLElement).getBoundingClientRect()
|
|
384
|
+
const tol = 4
|
|
385
|
+
const aligned =
|
|
386
|
+
Math.abs(contRect.y + contRect.height - (itemRect.y + itemRect.height)) <=
|
|
387
|
+
tol
|
|
388
|
+
if (!aligned) {
|
|
389
|
+
// Native browser API handles all positioning edge cases perfectly
|
|
390
|
+
item0Element.scrollIntoView({
|
|
391
|
+
block: 'end', // Align Item 0 to bottom edge of viewport
|
|
392
|
+
behavior: 'smooth', // Smooth animation for better UX
|
|
393
|
+
inline: 'nearest' // Minimal horizontal adjustment
|
|
394
|
+
})
|
|
395
|
+
log('b2t-correction-native', {
|
|
396
|
+
containerBottom: contRect.y + contRect.height,
|
|
397
|
+
itemBottom: itemRect.y + itemRect.height
|
|
398
|
+
})
|
|
399
|
+
}
|
|
363
400
|
// Sync our internal scroll state with actual DOM position
|
|
364
401
|
heightManager.scrollTop = heightManager.viewport.scrollTop
|
|
402
|
+
// After peer correction, delay further corrections briefly
|
|
403
|
+
suppressBottomAnchoringUntilMs = performance.now() + 200
|
|
365
404
|
}
|
|
366
405
|
})
|
|
367
406
|
|
|
@@ -445,7 +484,9 @@
|
|
|
445
484
|
|
|
446
485
|
// Handle height changes for scroll correction (manager totals already updated)
|
|
447
486
|
if (result.heightChanges.length > 0 && mode === 'bottomToTop') {
|
|
448
|
-
|
|
487
|
+
// Run correction after dynamic update finishes to avoid blocking conditions
|
|
488
|
+
const changes = result.heightChanges
|
|
489
|
+
queueMicrotask(() => handleHeightChangesScrollCorrection(changes))
|
|
449
490
|
}
|
|
450
491
|
|
|
451
492
|
// TopToBottom: maintain bottom anchoring when total height changes
|
|
@@ -770,7 +811,8 @@
|
|
|
770
811
|
if (mode === 'bottomToTop') {
|
|
771
812
|
const delta = lastScrollTopSnapshot - current
|
|
772
813
|
if (delta > 0.5) {
|
|
773
|
-
|
|
814
|
+
// Widen suppression to avoid fighting peer instance corrections
|
|
815
|
+
suppressBottomAnchoringUntilMs = performance.now() + 450
|
|
774
816
|
userHasScrolledAway = true
|
|
775
817
|
}
|
|
776
818
|
}
|
|
@@ -779,7 +821,7 @@
|
|
|
779
821
|
updateDebugTailDistance()
|
|
780
822
|
if (INTERNAL_DEBUG) {
|
|
781
823
|
const vr = visibleItems()
|
|
782
|
-
|
|
824
|
+
log('scroll', {
|
|
783
825
|
mode,
|
|
784
826
|
scrollTop: heightManager.scrollTop,
|
|
785
827
|
height,
|
|
@@ -808,40 +850,65 @@
|
|
|
808
850
|
* @param immediate - Whether to skip the delay (used for resize events)
|
|
809
851
|
*/
|
|
810
852
|
const updateHeightAndScroll = (immediate = false) => {
|
|
853
|
+
log('updateHeightAndScroll-enter', {
|
|
854
|
+
immediate,
|
|
855
|
+
initialized: heightManager.initialized,
|
|
856
|
+
mode
|
|
857
|
+
})
|
|
811
858
|
if (!heightManager.initialized && mode === 'bottomToTop') {
|
|
859
|
+
// Deterministic init order: double RAF + microtask, then apply bottom anchoring
|
|
812
860
|
tick().then(() => {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
861
|
+
requestAnimationFrame(() => {
|
|
862
|
+
requestAnimationFrame(() => {
|
|
863
|
+
if (!heightManager.isReady) return
|
|
864
|
+
const measuredHeight =
|
|
865
|
+
heightManager.container.getBoundingClientRect().height
|
|
866
|
+
height = measuredHeight
|
|
867
|
+
const targetScrollTop = calculateScrollPosition(
|
|
868
|
+
items.length,
|
|
869
|
+
heightManager.averageHeight,
|
|
870
|
+
measuredHeight
|
|
871
|
+
)
|
|
872
|
+
// Instance jitter to avoid same-frame collisions when two lists init together
|
|
873
|
+
const cleanedId = String(instanceId)
|
|
874
|
+
.toLowerCase()
|
|
875
|
+
.replace(/[^a-z0-9]/g, '')
|
|
876
|
+
const suffix = cleanedId.slice(-4)
|
|
877
|
+
const parsed = parseInt(suffix, 36)
|
|
878
|
+
const jitterMs = Number.isNaN(parsed)
|
|
879
|
+
? Math.floor(Math.random() * 3)
|
|
880
|
+
: parsed % 3
|
|
881
|
+
log('b2t-init', { measuredHeight, targetScrollTop, jitterMs })
|
|
882
|
+
setTimeout(() => {
|
|
831
883
|
heightManager.viewport.scrollTop = targetScrollTop
|
|
832
884
|
heightManager.scrollTop = targetScrollTop
|
|
833
|
-
|
|
834
885
|
requestAnimationFrame(() => {
|
|
835
|
-
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
886
|
+
// Guard: only transition false -> true to avoid invariant error
|
|
887
|
+
if (!heightManager.initialized) heightManager.initialized = true
|
|
888
|
+
// Post-init verification: ensure item 0 bottom aligns; fallback to native
|
|
889
|
+
tick().then(() => {
|
|
890
|
+
const el = heightManager.viewport.querySelector(
|
|
891
|
+
'[data-original-index="0"]'
|
|
892
|
+
) as HTMLElement | null
|
|
893
|
+
if (!el) return
|
|
894
|
+
const cont = heightManager.viewport.getBoundingClientRect()
|
|
895
|
+
const r = el.getBoundingClientRect()
|
|
896
|
+
const tol = 4
|
|
897
|
+
const aligned =
|
|
898
|
+
Math.abs(cont.y + cont.height - (r.y + r.height)) <= tol
|
|
899
|
+
if (!aligned) {
|
|
900
|
+
el.scrollIntoView({ block: 'end', inline: 'nearest' })
|
|
901
|
+
heightManager.scrollTop = heightManager.viewport.scrollTop
|
|
902
|
+
log('b2t-init-native-fallback', {
|
|
903
|
+
containerBottom: cont.y + cont.height,
|
|
904
|
+
itemBottom: r.y + r.height
|
|
905
|
+
})
|
|
906
|
+
}
|
|
907
|
+
})
|
|
841
908
|
})
|
|
842
|
-
}
|
|
909
|
+
}, jitterMs)
|
|
843
910
|
})
|
|
844
|
-
}
|
|
911
|
+
})
|
|
845
912
|
})
|
|
846
913
|
return
|
|
847
914
|
}
|
|
@@ -859,17 +926,24 @@
|
|
|
859
926
|
{
|
|
860
927
|
setHeight: (h) => (height = h),
|
|
861
928
|
setScrollTop: (st) => (heightManager.scrollTop = st),
|
|
862
|
-
|
|
929
|
+
// Guard: respect invariant in ReactiveListManager; avoid re-setting true
|
|
930
|
+
setInitialized: (i) => {
|
|
931
|
+
if (i && heightManager.initialized) return
|
|
932
|
+
heightManager.initialized = i
|
|
933
|
+
}
|
|
863
934
|
},
|
|
864
935
|
immediate
|
|
865
936
|
)
|
|
937
|
+
log('updateHeightAndScroll-exit', { immediate })
|
|
866
938
|
}
|
|
867
939
|
|
|
868
940
|
// Create itemResizeObserver immediately when in browser
|
|
869
941
|
if (BROWSER) {
|
|
870
942
|
// Watch for individual item size changes
|
|
871
943
|
itemResizeObserver = new ResizeObserver((entries) => {
|
|
872
|
-
|
|
944
|
+
// Batch via RAF to avoid thrash across instances
|
|
945
|
+
rafSchedule(() => {
|
|
946
|
+
log('item-resize-observer', { entries: entries.length })
|
|
873
947
|
let shouldRecalculate = false
|
|
874
948
|
void visibleItems() // Cache once to avoid reactive loops
|
|
875
949
|
|
|
@@ -903,9 +977,8 @@
|
|
|
903
977
|
}
|
|
904
978
|
|
|
905
979
|
if (shouldRecalculate) {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
})
|
|
980
|
+
log('item-resize-recalc')
|
|
981
|
+
updateHeight()
|
|
909
982
|
}
|
|
910
983
|
})
|
|
911
984
|
})
|
|
@@ -915,14 +988,25 @@
|
|
|
915
988
|
onMount(() => {
|
|
916
989
|
if (BROWSER) {
|
|
917
990
|
// Initial setup of heights and scroll position
|
|
991
|
+
log('onMount-enter', { mode, items: items.length })
|
|
918
992
|
updateHeightAndScroll()
|
|
919
993
|
// Ensure one initial measurement pass even if no ResizeObserver fires
|
|
920
994
|
tick().then(() =>
|
|
921
|
-
requestAnimationFrame(() =>
|
|
995
|
+
requestAnimationFrame(() =>
|
|
996
|
+
requestAnimationFrame(() => {
|
|
997
|
+
log('post-hydration-measure')
|
|
998
|
+
updateHeight()
|
|
999
|
+
})
|
|
1000
|
+
)
|
|
922
1001
|
)
|
|
923
1002
|
|
|
924
1003
|
// Watch for container size changes
|
|
925
1004
|
resizeObserver = new ResizeObserver(() => {
|
|
1005
|
+
if (!heightManager.initialized) {
|
|
1006
|
+
log('container-resize-ignored', 'not-initialized')
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
1009
|
+
log('container-resize')
|
|
926
1010
|
updateHeightAndScroll(true)
|
|
927
1011
|
})
|
|
928
1012
|
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
// RecomputeScheduler
|
|
2
|
+
// -------------------
|
|
3
|
+
// Coalesces recompute requests to the next animation frame in the browser.
|
|
4
|
+
// Falls back to setTimeout(0) in non-browser/jsdom to preserve deterministic tests.
|
|
5
|
+
// Supports temporary blocking to delay recomputation during critical sections.
|
|
1
6
|
export class RecomputeScheduler {
|
|
2
7
|
onRecompute;
|
|
3
8
|
isScheduled = false;
|
|
4
9
|
isPending = false;
|
|
5
10
|
blockDepth = 0;
|
|
6
11
|
timeoutId = null;
|
|
12
|
+
rafId = null;
|
|
7
13
|
constructor(onRecompute) {
|
|
8
14
|
this.onRecompute = onRecompute;
|
|
9
15
|
}
|
|
16
|
+
// Request a recompute. If blocked, mark as pending; otherwise schedule for next frame.
|
|
10
17
|
schedule = () => {
|
|
11
18
|
if (this.blockDepth > 0) {
|
|
12
19
|
this.isPending = true;
|
|
@@ -15,16 +22,30 @@ export class RecomputeScheduler {
|
|
|
15
22
|
if (this.isScheduled)
|
|
16
23
|
return;
|
|
17
24
|
this.isScheduled = true;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
// In jsdom or non-browser, fall back to immediate execution for determinism
|
|
26
|
+
const isBrowser = typeof window !== 'undefined' && typeof requestAnimationFrame === 'function';
|
|
27
|
+
if (!isBrowser) {
|
|
28
|
+
if (this.timeoutId) {
|
|
29
|
+
clearTimeout(this.timeoutId);
|
|
30
|
+
this.timeoutId = null;
|
|
31
|
+
}
|
|
32
|
+
this.timeoutId = setTimeout(() => {
|
|
33
|
+
this.timeoutId = null;
|
|
34
|
+
this.isScheduled = false;
|
|
35
|
+
this.onRecompute();
|
|
36
|
+
}, 0);
|
|
37
|
+
return;
|
|
21
38
|
}
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
// Browser path: coalesce with RAF for visual stability across instances
|
|
40
|
+
if (this.rafId !== null)
|
|
41
|
+
cancelAnimationFrame(this.rafId);
|
|
42
|
+
this.rafId = requestAnimationFrame(() => {
|
|
43
|
+
this.rafId = null;
|
|
24
44
|
this.isScheduled = false;
|
|
25
45
|
this.onRecompute();
|
|
26
|
-
}
|
|
46
|
+
});
|
|
27
47
|
};
|
|
48
|
+
// Temporarily block recomputes; any in-flight timers are canceled and a recompute is marked pending.
|
|
28
49
|
block = () => {
|
|
29
50
|
this.blockDepth += 1;
|
|
30
51
|
if (this.timeoutId) {
|
|
@@ -33,7 +54,14 @@ export class RecomputeScheduler {
|
|
|
33
54
|
this.isScheduled = false;
|
|
34
55
|
this.isPending = true;
|
|
35
56
|
}
|
|
57
|
+
if (this.rafId !== null) {
|
|
58
|
+
cancelAnimationFrame(this.rafId);
|
|
59
|
+
this.rafId = null;
|
|
60
|
+
this.isScheduled = false;
|
|
61
|
+
this.isPending = true;
|
|
62
|
+
}
|
|
36
63
|
};
|
|
64
|
+
// Unblock and run recompute immediately if one was pending.
|
|
37
65
|
unblock = () => {
|
|
38
66
|
if (this.blockDepth === 0)
|
|
39
67
|
return;
|
|
@@ -43,11 +71,16 @@ export class RecomputeScheduler {
|
|
|
43
71
|
this.onRecompute();
|
|
44
72
|
}
|
|
45
73
|
};
|
|
74
|
+
// Cancel any scheduled recompute and clear pending state.
|
|
46
75
|
cancel = () => {
|
|
47
76
|
if (this.timeoutId) {
|
|
48
77
|
clearTimeout(this.timeoutId);
|
|
49
78
|
this.timeoutId = null;
|
|
50
79
|
}
|
|
80
|
+
if (this.rafId !== null) {
|
|
81
|
+
cancelAnimationFrame(this.rafId);
|
|
82
|
+
this.rafId = null;
|
|
83
|
+
}
|
|
51
84
|
this.isScheduled = false;
|
|
52
85
|
this.isPending = false;
|
|
53
86
|
};
|
package/dist/utils/raf.d.ts
CHANGED
package/dist/utils/raf.js
CHANGED
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
|
|
31
31
|
*/
|
|
32
|
-
export
|
|
32
|
+
export const createRafScheduler = () => {
|
|
33
33
|
let scheduled = false;
|
|
34
34
|
let callback = null;
|
|
35
|
-
return (
|
|
36
|
-
callback =
|
|
35
|
+
return (_fn) => {
|
|
36
|
+
callback = _fn;
|
|
37
37
|
if (!scheduled) {
|
|
38
38
|
scheduled = true;
|
|
39
39
|
requestAnimationFrame(() => {
|
|
@@ -45,4 +45,4 @@ export function createRafScheduler() {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
-
}
|
|
48
|
+
};
|
|
@@ -28,13 +28,13 @@ import type { SvelteVirtualListDebugInfo } from '../types.js';
|
|
|
28
28
|
* 120
|
|
29
29
|
* );
|
|
30
30
|
*/
|
|
31
|
-
export declare
|
|
31
|
+
export declare const shouldShowDebugInfo: (prevRange: {
|
|
32
32
|
start: number;
|
|
33
33
|
end: number;
|
|
34
34
|
} | null, currentRange: {
|
|
35
35
|
start: number;
|
|
36
36
|
end: number;
|
|
37
|
-
}, prevHeight: number, currentHeight: number)
|
|
37
|
+
}, prevHeight: number, currentHeight: number) => boolean;
|
|
38
38
|
/**
|
|
39
39
|
* Creates a comprehensive debug information object for virtual list state analysis.
|
|
40
40
|
*
|
|
@@ -71,7 +71,7 @@ export declare function shouldShowDebugInfo(prevRange: {
|
|
|
71
71
|
*
|
|
72
72
|
* @throws {Error} Will throw if end index is less than start index in visibleRange
|
|
73
73
|
*/
|
|
74
|
-
export declare
|
|
74
|
+
export declare const createDebugInfo: (visibleRange: {
|
|
75
75
|
start: number;
|
|
76
76
|
end: number;
|
|
77
|
-
}, totalItems: number, processedItems: number, averageItemHeight: number, scrollTop: number, viewportHeight: number, totalHeight: number)
|
|
77
|
+
}, totalItems: number, processedItems: number, averageItemHeight: number, scrollTop: number, viewportHeight: number, totalHeight: number) => SvelteVirtualListDebugInfo;
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
* 120
|
|
28
28
|
* );
|
|
29
29
|
*/
|
|
30
|
-
export
|
|
30
|
+
export const shouldShowDebugInfo = (prevRange, currentRange, prevHeight, currentHeight) => {
|
|
31
31
|
if (!prevRange)
|
|
32
32
|
return true;
|
|
33
33
|
return (prevRange.start !== currentRange.start ||
|
|
34
34
|
prevRange.end !== currentRange.end ||
|
|
35
35
|
prevHeight !== currentHeight);
|
|
36
|
-
}
|
|
36
|
+
};
|
|
37
37
|
/**
|
|
38
38
|
* Creates a comprehensive debug information object for virtual list state analysis.
|
|
39
39
|
*
|
|
@@ -70,7 +70,7 @@ export function shouldShowDebugInfo(prevRange, currentRange, prevHeight, current
|
|
|
70
70
|
*
|
|
71
71
|
* @throws {Error} Will throw if end index is less than start index in visibleRange
|
|
72
72
|
*/
|
|
73
|
-
export
|
|
73
|
+
export const createDebugInfo = (visibleRange, totalItems, processedItems, averageItemHeight, scrollTop, viewportHeight, totalHeight) => {
|
|
74
74
|
const atTop = scrollTop <= 1; // Small tolerance for floating point precision
|
|
75
75
|
const atBottom = scrollTop >= totalHeight - viewportHeight - 1; // Small tolerance
|
|
76
76
|
return {
|
|
@@ -84,4 +84,4 @@ export function createDebugInfo(visibleRange, totalItems, processedItems, averag
|
|
|
84
84
|
atBottom,
|
|
85
85
|
totalHeight
|
|
86
86
|
};
|
|
87
|
-
}
|
|
87
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-virtual-list",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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",
|
|
@@ -60,23 +60,23 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@eslint/compat": "^1.4.0",
|
|
63
|
-
"@eslint/js": "^9.
|
|
63
|
+
"@eslint/js": "^9.37.0",
|
|
64
64
|
"@faker-js/faker": "^10.0.0",
|
|
65
|
-
"@playwright/test": "^1.
|
|
66
|
-
"@sveltejs/adapter-auto": "^6.1.
|
|
67
|
-
"@sveltejs/kit": "^2.
|
|
65
|
+
"@playwright/test": "^1.56.0",
|
|
66
|
+
"@sveltejs/adapter-auto": "^6.1.1",
|
|
67
|
+
"@sveltejs/kit": "^2.46.4",
|
|
68
68
|
"@sveltejs/package": "^2.5.4",
|
|
69
69
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
70
|
-
"@tailwindcss/vite": "^4.1.
|
|
71
|
-
"@testing-library/jest-dom": "^6.
|
|
70
|
+
"@tailwindcss/vite": "^4.1.14",
|
|
71
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
72
72
|
"@testing-library/svelte": "^5.2.8",
|
|
73
73
|
"@testing-library/user-event": "^14.6.1",
|
|
74
|
-
"@types/node": "^24.
|
|
75
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
76
|
-
"@typescript-eslint/parser": "^8.
|
|
74
|
+
"@types/node": "^24.7.1",
|
|
75
|
+
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
76
|
+
"@typescript-eslint/parser": "^8.46.0",
|
|
77
77
|
"@vitest/coverage-v8": "^3.2.4",
|
|
78
78
|
"concurrently": "^9.2.1",
|
|
79
|
-
"eslint": "^9.
|
|
79
|
+
"eslint": "^9.37.0",
|
|
80
80
|
"eslint-config-prettier": "^10.1.8",
|
|
81
81
|
"eslint-plugin-import": "^2.32.0",
|
|
82
82
|
"eslint-plugin-svelte": "^3.12.4",
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"prettier-plugin-sort-json": "^4.1.1",
|
|
90
90
|
"prettier-plugin-svelte": "^3.4.0",
|
|
91
91
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
92
|
-
"publint": "^0.3.
|
|
93
|
-
"svelte": "^5.39.
|
|
94
|
-
"svelte-check": "^4.3.
|
|
95
|
-
"tailwindcss": "^4.1.
|
|
92
|
+
"publint": "^0.3.14",
|
|
93
|
+
"svelte": "^5.39.11",
|
|
94
|
+
"svelte-check": "^4.3.3",
|
|
95
|
+
"tailwindcss": "^4.1.14",
|
|
96
96
|
"tw-animate-css": "^1.4.0",
|
|
97
|
-
"typescript": "^5.9.
|
|
98
|
-
"typescript-eslint": "^8.
|
|
99
|
-
"vite": "^7.1.
|
|
97
|
+
"typescript": "^5.9.3",
|
|
98
|
+
"typescript-eslint": "^8.46.0",
|
|
99
|
+
"vite": "^7.1.9",
|
|
100
100
|
"vitest": "^3.2.4"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
124
124
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
125
125
|
"dev": "vite dev",
|
|
126
|
-
"dev:all": "concurrently -k -n pkg,docs -c green,cyan \"pnpm -w -r --filter @humanspeak/svelte-virtual-list run dev:pkg\" \"pnpm --filter docs run dev\"",
|
|
126
|
+
"dev:all": "concurrently -k -n pkg,docs,sitemap -c green,cyan,magenta \"pnpm -w -r --filter @humanspeak/svelte-virtual-list run dev:pkg\" \"pnpm --filter docs run dev\" \"pnpm --filter docs run sitemap:watch\"",
|
|
127
127
|
"dev:pkg": "svelte-kit sync && svelte-package --watch",
|
|
128
128
|
"format": "prettier --write .",
|
|
129
129
|
"lint": "prettier --check . && eslint .",
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
"test:all": "pnpm run test && pnpm run test:e2e",
|
|
135
135
|
"test:e2e": "playwright test",
|
|
136
136
|
"test:e2e:debug": "playwright test --debug",
|
|
137
|
+
"test:e2e:ff": "playwright test --project=firefox",
|
|
137
138
|
"test:e2e:report": "playwright show-report",
|
|
138
139
|
"test:e2e:ui": "playwright test --ui",
|
|
139
140
|
"test:only": "vitest run --",
|