mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 02:32:18 +00:00
feat: add maxColumns prop to VirtualGrid for responsive column capping
This commit is contained in:
@@ -1,16 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="container" class="scroll-container">
|
<div
|
||||||
<div :style="{ height: `${(state.start / cols) * itemHeight}px` }" />
|
ref="container"
|
||||||
<div :style="gridStyle">
|
class="h-full overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-(--dialog-surface)"
|
||||||
<div v-for="item in renderedItems" :key="item.key" data-virtual-grid-item>
|
>
|
||||||
|
<div :style="topSpacerStyle" />
|
||||||
|
<div :style="mergedGridStyle">
|
||||||
|
<div
|
||||||
|
v-for="item in renderedItems"
|
||||||
|
:key="item.key"
|
||||||
|
class="transition-[width] duration-150 ease-out"
|
||||||
|
data-virtual-grid-item
|
||||||
|
>
|
||||||
<slot name="item" :item="item" />
|
<slot name="item" :item="item" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div :style="bottomSpacerStyle" />
|
||||||
:style="{
|
|
||||||
height: `${((items.length - state.end) / cols) * itemHeight}px`
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -28,19 +32,22 @@ type GridState = {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
gridStyle,
|
||||||
bufferRows = 1,
|
bufferRows = 1,
|
||||||
scrollThrottle = 64,
|
scrollThrottle = 64,
|
||||||
resizeDebounce = 64,
|
resizeDebounce = 64,
|
||||||
defaultItemHeight = 200,
|
defaultItemHeight = 200,
|
||||||
defaultItemWidth = 200
|
defaultItemWidth = 200,
|
||||||
|
maxColumns = Infinity
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
items: (T & { key: string })[]
|
items: (T & { key: string })[]
|
||||||
gridStyle: Partial<CSSProperties>
|
gridStyle: CSSProperties
|
||||||
bufferRows?: number
|
bufferRows?: number
|
||||||
scrollThrottle?: number
|
scrollThrottle?: number
|
||||||
resizeDebounce?: number
|
resizeDebounce?: number
|
||||||
defaultItemHeight?: number
|
defaultItemHeight?: number
|
||||||
defaultItemWidth?: number
|
defaultItemWidth?: number
|
||||||
|
maxColumns?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -59,7 +66,18 @@ const { y: scrollY } = useScroll(container, {
|
|||||||
eventListenerOptions: { passive: true }
|
eventListenerOptions: { passive: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
const cols = computed(() => Math.floor(width.value / itemWidth.value) || 1)
|
const cols = computed(() =>
|
||||||
|
Math.min(Math.floor(width.value / itemWidth.value) || 1, maxColumns)
|
||||||
|
)
|
||||||
|
|
||||||
|
const mergedGridStyle = computed<CSSProperties>(() => {
|
||||||
|
if (maxColumns === Infinity) return gridStyle
|
||||||
|
return {
|
||||||
|
...gridStyle,
|
||||||
|
gridTemplateColumns: `repeat(${maxColumns}, minmax(0, 1fr))`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
|
const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
|
||||||
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
|
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
|
||||||
const isValidGrid = computed(() => height.value && width.value && items?.length)
|
const isValidGrid = computed(() => height.value && width.value && items?.length)
|
||||||
@@ -83,6 +101,16 @@ const renderedItems = computed(() =>
|
|||||||
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
|
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function rowsToHeight(rows: number): string {
|
||||||
|
return `${(rows / cols.value) * itemHeight.value}px`
|
||||||
|
}
|
||||||
|
const topSpacerStyle = computed<CSSProperties>(() => ({
|
||||||
|
height: rowsToHeight(state.value.start)
|
||||||
|
}))
|
||||||
|
const bottomSpacerStyle = computed<CSSProperties>(() => ({
|
||||||
|
height: rowsToHeight(items.length - state.value.end)
|
||||||
|
}))
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
() => state.value.isNearEnd,
|
() => state.value.isNearEnd,
|
||||||
() => {
|
() => {
|
||||||
@@ -109,15 +137,6 @@ const onResize = debounce(updateItemSize, resizeDebounce)
|
|||||||
watch([width, height], onResize, { flush: 'post' })
|
watch([width, height], onResize, { flush: 'post' })
|
||||||
whenever(() => items, updateItemSize, { flush: 'post' })
|
whenever(() => items, updateItemSize, { flush: 'post' })
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
onResize.cancel() // Clear pending debounced calls
|
onResize.cancel()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.scroll-container {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--dialog-surface) transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -26,9 +26,10 @@
|
|||||||
<VirtualGrid
|
<VirtualGrid
|
||||||
v-else
|
v-else
|
||||||
:items="assetsWithKey"
|
:items="assetsWithKey"
|
||||||
:grid-style="gridStyle"
|
:grid-style
|
||||||
:default-item-height="320"
|
:default-item-height="320"
|
||||||
:default-item-width="240"
|
:default-item-width="240"
|
||||||
|
:max-columns
|
||||||
>
|
>
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<AssetCard
|
<AssetCard
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||||
import type { CSSProperties } from 'vue'
|
import type { CSSProperties } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
@@ -70,7 +72,20 @@ const assetsWithKey = computed(() =>
|
|||||||
assets.map((asset) => ({ ...asset, key: asset.id }))
|
assets.map((asset) => ({ ...asset, key: asset.id }))
|
||||||
)
|
)
|
||||||
|
|
||||||
const gridStyle: Partial<CSSProperties> = {
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
|
const is2Xl = breakpoints.greaterOrEqual('2xl')
|
||||||
|
const isXl = breakpoints.greaterOrEqual('xl')
|
||||||
|
const isLg = breakpoints.greaterOrEqual('lg')
|
||||||
|
const isMd = breakpoints.greaterOrEqual('md')
|
||||||
|
const maxColumns = computed(() => {
|
||||||
|
if (is2Xl.value) return 5
|
||||||
|
if (isXl.value) return 4
|
||||||
|
if (isLg.value) return 3
|
||||||
|
if (isMd.value) return 2
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const gridStyle: CSSProperties = {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(15rem, 1fr))',
|
gridTemplateColumns: 'repeat(auto-fill, minmax(15rem, 1fr))',
|
||||||
gap: '1rem',
|
gap: '1rem',
|
||||||
|
|||||||
Reference in New Issue
Block a user