Files
veridian/app/components/RowVirtualizerDynamic.vue
T
2026-03-03 10:23:25 -06:00

79 lines
2.4 KiB
Vue

<script setup lang="ts">
import { useVirtualizer } from '@tanstack/vue-virtual';
const props = defineProps<{
items: any[];
keyField?: string;
scrollElement: HTMLElement | null;
minItemSize: number;
overscan: number;
prerender?: number;
}>();
const containerRef = ref<HTMLElement | null>(null);
const scrollMargin = ref(0);
const updateScrollMargin = () => {
if (containerRef.value && props.scrollElement) {
const containerRect = containerRef.value.getBoundingClientRect();
const scrollRect = props.scrollElement.getBoundingClientRect();
scrollMargin.value = containerRect.top - scrollRect.top + props.scrollElement.scrollTop;
}
};
const resizeObserver = new ResizeObserver(updateScrollMargin);
onMounted(() => {
resizeObserver.observe(containerRef.value!);
updateScrollMargin();
});
onUnmounted(() => {
resizeObserver.disconnect();
});
const rowVirtualizer = useVirtualizer(computed(() => ({
count: props.items.length,
getScrollElement: () => props.scrollElement,
estimateSize: () => props.minItemSize,
overscan: props.overscan,
scrollMargin: scrollMargin.value,
getItemKey: (index: number) => props.keyField ? props.items[index]?.[props.keyField] || index : index,
initialRect: {
width: 0,
height: props.prerender ? props.minItemSize * props.prerender : 0
},
})));
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
const measureElement = (el: Element) => {
if (!el) {
return
}
rowVirtualizer.value.measureElement(el)
}
watch(() => props.items, () => {
rowVirtualizer.value.measure();
}, { deep: false });
</script>
<template>
<div ref="containerRef" :style="{ height: `${totalSize}px`, width: '100%', position: 'relative' }">
<div v-for="virtualRow in virtualRows" :ref="(el) => measureElement(el as Element)"
:key="(virtualRow.key as any | number)" :data-index="virtualRow.index" :style="{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start - scrollMargin}px)`,
}">
<slot :item="props.items[virtualRow.index]" :index="virtualRow.index" />
</div>
</div>
</template>