feature(VirtualList): remove tanstack virtual list solution, add self written one
This commit is contained in:
@@ -55,53 +55,15 @@ interface Props {
|
||||
|
||||
let { items, itemHeight = 80, overscan = 5, class: className, children }: Props = $props();
|
||||
|
||||
let activeIndex = $state(0);
|
||||
const itemRefs = new Map<number, HTMLElement>();
|
||||
|
||||
const virtual = createVirtualizer(() => ({
|
||||
const virtualizer = createVirtualizer(() => ({
|
||||
count: items.length,
|
||||
estimateSize: typeof itemHeight === 'function' ? itemHeight : () => itemHeight,
|
||||
overscan,
|
||||
}));
|
||||
|
||||
function registerItem(node: HTMLElement, index: number) {
|
||||
itemRefs.set(index, node);
|
||||
return {
|
||||
destroy() {
|
||||
itemRefs.delete(index);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function focusItem(index: number) {
|
||||
activeIndex = index;
|
||||
virtual.scrollToIndex(index, { align: 'auto' });
|
||||
await tick();
|
||||
itemRefs.get(index)?.focus();
|
||||
}
|
||||
|
||||
async function handleKeydown(event: KeyboardEvent) {
|
||||
let nextIndex = activeIndex;
|
||||
if (event.key === 'ArrowDown') nextIndex++;
|
||||
else if (event.key === 'ArrowUp') nextIndex--;
|
||||
else if (event.key === 'Home') nextIndex = 0;
|
||||
else if (event.key === 'End') nextIndex = items.length - 1;
|
||||
else return;
|
||||
|
||||
if (nextIndex >= 0 && nextIndex < items.length) {
|
||||
event.preventDefault();
|
||||
await focusItem(nextIndex);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Scroll container with single tab stop pattern:
|
||||
- tabindex="0" on container, tabindex="-1" on items
|
||||
- Arrow keys navigate within, Tab moves out
|
||||
-->
|
||||
<div
|
||||
bind:this={virtual.scrollElement}
|
||||
use:virtualizer.container
|
||||
class={cn(
|
||||
'relative overflow-auto border rounded-md bg-background',
|
||||
'outline-none focus-visible:ring-2 ring-ring ring-offset-2',
|
||||
@@ -110,29 +72,15 @@ async function handleKeydown(event: KeyboardEvent) {
|
||||
)}
|
||||
role="listbox"
|
||||
tabindex="0"
|
||||
onkeydown={handleKeydown}
|
||||
onfocusin={(e => e.target === virtual.scrollElement && focusItem(activeIndex))}
|
||||
>
|
||||
<!-- Total scrollable height placeholder -->
|
||||
<div
|
||||
class="relative w-full"
|
||||
style:height="{virtual.totalSize}px"
|
||||
>
|
||||
{#each virtual.items as row (row.key)}
|
||||
<!-- Individual item positioned absolutely via GPU-accelerated transform -->
|
||||
<div
|
||||
use:registerItem={row.index}
|
||||
data-index={row.index}
|
||||
role="option"
|
||||
aria-selected={activeIndex === row.index}
|
||||
tabindex="-1"
|
||||
onmousedown={() => (activeIndex = row.index)}
|
||||
class="absolute top-0 left-0 w-full outline-none focus:bg-accent focus:text-accent-foreground"
|
||||
style:height="{row.size}px"
|
||||
style:transform="translateY({row.start}px)"
|
||||
>
|
||||
{@render children({ item: items[row.index], index: row.index })}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#each virtualizer.items as item (item.key)}
|
||||
<div
|
||||
use:virtualizer.measureElement
|
||||
data-index={item.index}
|
||||
class="absolute top-0 left-0 w-full translate-y-[var(--offset)] will-change-transform"
|
||||
style:--offset="{item.start}px"
|
||||
>
|
||||
{@render children({ item: items[item.index], index: item.index })}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user