Skip to content

Commit c36ca18

Browse files
add
1 parent 8d290e9 commit c36ca18

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,8 @@
8888
"vite": "^8.0.0",
8989
"vitest": "^4.1.0",
9090
"zod": "^4.3.6"
91+
},
92+
"dependencies": {
93+
"@tanstack/svelte-virtual": "^3.13.23"
9194
}
9295
}

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)