Skip to content

Commit 8d4af3a

Browse files
feat: use tanstack virtual list (#580)
* add * feat: move to `devDependency`
1 parent 8d290e9 commit 8d4af3a

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

src/frontend/package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"test": "npm run test:unit -- --run",
1616
"test:unit": "vitest"
1717
},
18+
"dependencies": {},
1819
"devDependencies": {
1920
"@codemirror/commands": "^6.10.3",
2021
"@codemirror/language": "^6.12.2",
@@ -35,6 +36,7 @@
3536
"@tailwindcss/vite": "^4.2.1",
3637
"@tanstack/svelte-query": "^6.1.0",
3738
"@tanstack/svelte-query-devtools": "^6.0.4",
39+
"@tanstack/svelte-virtual": "^3.13.23",
3840
"@types/node": "^25",
3941
"@types/nprogress": "^0.2.3",
4042
"@types/qrcode": "^1.5.6",

src/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
33
import { Scrollbar } from './index';
44
import { cn, type WithoutChild } from '$lib/utils.js';
5+
import { createVirtualizer, type VirtualizerOptions } from '@tanstack/svelte-virtual';
6+
import type { VirtualItem } from '@tanstack/virtual-core';
7+
import type { Snippet } from 'svelte';
58
69
let {
710
ref = $bindable(null),
@@ -10,14 +13,29 @@
1013
orientation = 'vertical',
1114
scrollbarXClasses = '',
1215
scrollbarYClasses = '',
16+
virtualOptions,
17+
item,
1318
children,
1419
...restProps
1520
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
1621
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
1722
scrollbarXClasses?: string | undefined;
1823
scrollbarYClasses?: string | undefined;
1924
viewportRef?: HTMLElement | null;
25+
virtualOptions?:
26+
| Omit<VirtualizerOptions<HTMLElement, HTMLElement>, 'getScrollElement'>
27+
| undefined;
28+
item?: Snippet<[VirtualItem]> | undefined;
2029
} = $props();
30+
31+
const virtualizer = $derived(
32+
virtualOptions && viewportRef
33+
? createVirtualizer({ ...virtualOptions, getScrollElement: () => viewportRef })
34+
: null
35+
);
36+
37+
const virtualItems = $derived($virtualizer?.getVirtualItems() ?? []);
38+
const totalSize = $derived($virtualizer?.getTotalSize() ?? 0);
2139
</script>
2240

2341
<ScrollAreaPrimitive.Root
@@ -31,7 +49,19 @@
3149
data-slot="scroll-area-viewport"
3250
class="size-full rounded-[inherit] ring-ring/10 outline-ring/50 transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 dark:ring-ring/20 dark:outline-ring/40"
3351
>
34-
{@render children?.()}
52+
{#if virtualizer && item}
53+
<div style="height: {totalSize}px; position: relative;">
54+
{#each virtualItems as row (row.index)}
55+
<div
56+
style="position: absolute; top: 0; transform: translateY({row.start}px); width: 100%; height: {row.size}px;"
57+
>
58+
{@render item(row)}
59+
</div>
60+
{/each}
61+
</div>
62+
{:else}
63+
{@render children?.()}
64+
{/if}
3565
</ScrollAreaPrimitive.Viewport>
3666
{#if orientation === 'vertical' || orientation === 'both'}
3767
<Scrollbar orientation="vertical" class={scrollbarYClasses} />

0 commit comments

Comments
 (0)