feat(VirtualList): incorporate shadcn scroll area to replace default scoll bar

This commit is contained in:
Ilia Mashkov
2026-01-31 11:53:18 +03:00
parent c246f70fe9
commit 91fa08074b

View File

@@ -6,9 +6,11 @@
- Keyboard navigation (ArrowUp/Down, Home, End) - Keyboard navigation (ArrowUp/Down, Home, End)
- Fixed or dynamic item heights - Fixed or dynamic item heights
- ARIA listbox/option pattern with single tab stop - ARIA listbox/option pattern with single tab stop
- Custom shadcn ScrollArea scrollbar
--> -->
<script lang="ts" generics="T"> <script lang="ts" generics="T">
import { createVirtualizer } from '$shared/lib'; import { createVirtualizer } from '$shared/lib';
import { ScrollArea } from '$shared/shadcn/ui/scroll-area';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
@@ -100,6 +102,9 @@ let {
children, children,
}: Props = $props(); }: Props = $props();
// Reference to the ScrollArea viewport element for attaching the virtualizer
let viewportRef = $state<HTMLElement | null>(null);
const virtualizer = createVirtualizer(() => ({ const virtualizer = createVirtualizer(() => ({
count: items.length, count: items.length,
data: items, data: items,
@@ -107,6 +112,14 @@ const virtualizer = createVirtualizer(() => ({
overscan, overscan,
})); }));
// Attach virtualizer.container action to the viewport when it becomes available
$effect(() => {
if (viewportRef) {
const { destroy } = virtualizer.container(viewportRef);
return destroy;
}
});
$effect(() => { $effect(() => {
const visibleItems = virtualizer.items.map(item => items[item.index]); const visibleItems = virtualizer.items.map(item => items[item.index]);
onVisibleItemsChange?.(visibleItems); onVisibleItemsChange?.(visibleItems);
@@ -124,40 +137,28 @@ $effect(() => {
}); });
</script> </script>
<div <ScrollArea
use:virtualizer.container bind:viewportRef
class={cn( class={cn('relative rounded-md bg-background', 'h-150 w-full', className)}
'relative overflow-auto rounded-md bg-background', orientation="vertical"
'h-150 w-full',
'scroll-smooth',
className,
)}
onfocusin={(e => {
// Prevent the browser from jumping the scroll when an inner element gets focus
e.preventDefault();
})}
> >
<div <div style:height="{virtualizer.totalSize}px" class="relative w-full">
style:height="{virtualizer.totalSize}px" {#each virtualizer.items as item (item.key)}
class="w-full pointer-events-none" <div
> use:virtualizer.measureElement
</div> data-index={item.index}
class="absolute top-0 left-0 w-full"
{#each virtualizer.items as item (item.key)} style:transform="translateY({item.start}px)"
<div >
use:virtualizer.measureElement {#if item.index < items.length}
data-index={item.index} {@render children({
class="absolute top-0 left-0 w-full"
style:transform="translateY({item.start}px)"
>
{#if item.index < items.length}
{@render children({
item: items[item.index], item: items[item.index],
index: item.index, index: item.index,
isVisible: item.isVisible, isVisible: item.isVisible,
proximity: item.proximity, proximity: item.proximity,
})} })}
{/if} {/if}
</div> </div>
{/each} {/each}
</div> </div>
</ScrollArea>