Allow rectangular virtual grid items (#3093)

This commit is contained in:
Christian Byrne
2025-03-17 07:39:02 -07:00
committed by GitHub
parent ad98bcb87c
commit c82fe80716

View File

@@ -1,6 +1,6 @@
<template>
<div ref="container" class="scroll-container">
<div :style="{ height: `${(state.start / cols) * itemSize}px` }" />
<div :style="{ height: `${(state.start / cols) * itemHeight}px` }" />
<div :style="gridStyle">
<div v-for="item in renderedItems" :key="item.key" data-virtual-grid-item>
<slot name="item" :item="item"> </slot>
@@ -8,7 +8,7 @@
</div>
<div
:style="{
height: `${((props.items.length - state.end) / cols) * itemSize}px`
height: `${((items.length - state.end) / cols) * itemHeight}px`
}"
/>
</div>
@@ -19,22 +19,25 @@ import { useElementSize, useScroll } from '@vueuse/core'
import { clamp, debounce } from 'lodash'
import { type CSSProperties, computed, onBeforeUnmount, ref, watch } from 'vue'
const props = defineProps<{
const {
items,
bufferRows = 1,
scrollThrottle = 64,
resizeDebounce = 64,
defaultItemHeight = 200,
defaultItemWidth = 200
} = defineProps<{
items: (T & { key: string })[]
gridStyle: Partial<CSSProperties>
bufferRows?: number
scrollThrottle?: number
resizeDebounce?: number
defaultItemSize?: number
defaultItemHeight?: number
defaultItemWidth?: number
}>()
const {
bufferRows = 1,
scrollThrottle = 64,
resizeDebounce = 64,
defaultItemSize = 200
} = props
const itemSize = ref(defaultItemSize)
const itemHeight = ref(defaultItemHeight)
const itemWidth = ref(defaultItemWidth)
const container = ref<HTMLElement | null>(null)
const { width, height } = useElementSize(container)
const { y: scrollY } = useScroll(container, {
@@ -42,12 +45,10 @@ const { y: scrollY } = useScroll(container, {
eventListenerOptions: { passive: true }
})
const cols = computed(() => Math.floor(width.value / itemSize.value) || 1)
const viewRows = computed(() => Math.ceil(height.value / itemSize.value))
const offsetRows = computed(() => Math.floor(scrollY.value / itemSize.value))
const isValidGrid = computed(
() => height.value && width.value && props.items?.length
)
const cols = computed(() => Math.floor(width.value / itemWidth.value) || 1)
const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
const isValidGrid = computed(() => height.value && width.value && items?.length)
const state = computed<{ start: number; end: number }>(() => {
const fromRow = offsetRows.value - bufferRows
@@ -57,18 +58,19 @@ const state = computed<{ start: number; end: number }>(() => {
const toCol = toRow * cols.value
return {
start: clamp(fromCol, 0, props.items?.length),
end: clamp(toCol, fromCol, props.items?.length)
start: clamp(fromCol, 0, items?.length),
end: clamp(toCol, fromCol, items?.length)
}
})
const renderedItems = computed(() =>
isValidGrid.value ? props.items.slice(state.value.start, state.value.end) : []
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
)
const updateItemSize = () => {
if (container.value) {
const firstItem = container.value.querySelector('[data-virtual-grid-item]')
itemSize.value = firstItem?.clientHeight || defaultItemSize
itemHeight.value = firstItem?.clientHeight || defaultItemHeight
itemWidth.value = firstItem?.clientWidth || defaultItemWidth
}
}
const onResize = debounce(updateItemSize, resizeDebounce)