feat(VirtualList): incoroprate new logic related to window scroll and separation of isVisible flag

This commit is contained in:
Ilia Mashkov
2026-02-02 12:16:04 +03:00
parent 4a94f7bd09
commit eaf9d069c5

View File

@@ -76,6 +76,18 @@ interface Props {
* ```
*/
onNearBottom?: (lastVisibleIndex: number) => void;
/**
* Snippet for rendering individual list items.
*
* The snippet receives an object containing:
* - `item`: The item from the items array (type T)
* - `index`: The current item's index in the array
*
* This pattern provides type safety and flexibility for
* rendering different item types without prop drilling.
*
* @template T - The type of items in the list
*/
/**
* Snippet for rendering individual list items.
*
@@ -89,8 +101,13 @@ interface Props {
* @template T - The type of items in the list
*/
children: Snippet<
[{ item: T; index: number; isVisible: boolean; proximity: number }]
[{ item: T; index: number; isFullyVisible: boolean; isPartiallyVisible: boolean; proximity: number }]
>;
/**
* Whether to use the window as the scroll container.
* @default false
*/
useWindowScroll?: boolean;
}
let {
@@ -102,6 +119,7 @@ let {
onVisibleItemsChange,
onNearBottom,
children,
useWindowScroll = false,
}: Props = $props();
// Reference to the ScrollArea viewport element for attaching the virtualizer
@@ -112,6 +130,7 @@ const virtualizer = createVirtualizer(() => ({
data: items,
estimateSize: typeof itemHeight === 'function' ? itemHeight : () => itemHeight,
overscan,
useWindowScroll,
}));
// Attach virtualizer.container action to the viewport when it becomes available
@@ -139,30 +158,61 @@ $effect(() => {
});
</script>
<ScrollArea
bind:viewportRef
class={cn('relative rounded-md bg-background', 'h-150 w-full', className)}
orientation="vertical"
>
<div style:height="{virtualizer.totalSize}px" class="relative w-full">
{#each virtualizer.items as item (item.key)}
<div
use:virtualizer.measureElement
data-index={item.index}
class="absolute top-0 left-0 w-full"
style:transform="translateY({item.start}px)"
animate:flip={{ delay: 0, duration: 300, easing: quintOut }}
>
{#if item.index < items.length}
{@render children({
{#if useWindowScroll}
<div class={cn('relative w-full', className)} bind:this={viewportRef}>
<div style:height="{virtualizer.totalSize}px" class="relative w-full">
{#each virtualizer.items as item (item.key)}
<div
use:virtualizer.measureElement
data-index={item.index}
class="absolute top-0 left-0 w-full"
style:transform="translateY({item.start}px)"
>
{#if item.index < items.length}
{@render children({
// TODO: Fix indenation rule for this case
item: items[item.index],
index: item.index,
isVisible: item.isVisible,
isFullyVisible: item.isFullyVisible,
isPartiallyVisible: item.isPartiallyVisible,
proximity: item.proximity,
})}
{/if}
</div>
{/each}
{/if}
</div>
{/each}
</div>
</div>
</ScrollArea>
{:else}
<ScrollArea
bind:viewportRef
class={cn(
'relative rounded-md bg-background',
'h-150 w-full',
className,
)}
orientation="vertical"
>
<div style:height="{virtualizer.totalSize}px" class="relative w-full">
{#each virtualizer.items as item (item.key)}
<div
use:virtualizer.measureElement
data-index={item.index}
class="absolute top-0 left-0 w-full"
style:transform="translateY({item.start}px)"
animate:flip={{ delay: 0, duration: 300, easing: quintOut }}
>
{#if item.index < items.length}
{@render children({
// TODO: Fix indenation rule for this case
item: items[item.index],
index: item.index,
isFullyVisible: item.isFullyVisible,
isPartiallyVisible: item.isPartiallyVisible,
proximity: item.proximity,
})}
{/if}
</div>
{/each}
</div>
</ScrollArea>
{/if}