Skip to content

Commit 56ae8e7

Browse files
committed
fix(components): calc virtualizer estimateSize based on item description
1 parent 1d1c638 commit 56ae8e7

File tree

5 files changed

+80
-29
lines changed

5 files changed

+80
-29
lines changed

src/runtime/components/CommandPalette.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,10 @@ import { useFuse } from '@vueuse/integrations/useFuse'
204204
import { useAppConfig } from '#imports'
205205
import { useLocale } from '../composables/useLocale'
206206
import { omit, get } from '../utils'
207-
import { tv } from '../utils/tv'
208207
import { highlight } from '../utils/fuse'
209208
import { pickLinkProps } from '../utils/link'
209+
import { getEstimateSize } from '../utils/virtualizer'
210+
import { tv } from '../utils/tv'
210211
import UIcon from './Icon.vue'
211212
import UAvatar from './Avatar.vue'
212213
import UButton from './Button.vue'
@@ -238,7 +239,13 @@ const appConfig = useAppConfig() as CommandPalette['AppConfig']
238239
239240
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'disabled', 'multiple', 'modelValue', 'defaultValue', 'highlightOnHover'), emits)
240241
const inputProps = useForwardProps(reactivePick(props, 'loading'))
241-
const virtualizerProps = toRef(() => !!props.virtualize && defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, { estimateSize: 32 }))
242+
const virtualizerProps = toRef(() => {
243+
if (!props.virtualize) return false
244+
245+
return defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
246+
estimateSize: getEstimateSize(filteredItems.value, 'md', props.descriptionKey as string)
247+
})
248+
})
242249
243250
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: CommandPaletteItem, group?: CommandPaletteGroup, index: number }>({
244251
props: {

src/runtime/components/InputMenu.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ import { useFormField } from '../composables/useFormField'
202202
import { useLocale } from '../composables/useLocale'
203203
import { usePortal } from '../composables/usePortal'
204204
import { compare, get, getDisplayValue, isArrayOfArray } from '../utils'
205+
import { getEstimateSize } from '../utils/virtualizer'
205206
import { tv } from '../utils/tv'
206207
import UIcon from './Icon.vue'
207208
import UAvatar from './Avatar.vue'
@@ -232,15 +233,13 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
232233
const portalProps = usePortal(toRef(() => props.portal))
233234
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
234235
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
235-
const virtualizerProps = toRef(() => !!props.virtualize && defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
236-
estimateSize: ({
237-
xs: 24,
238-
sm: 28,
239-
md: 32,
240-
lg: 36,
241-
xl: 40
242-
})[props.size || 'md']
243-
}))
236+
const virtualizerProps = toRef(() => {
237+
if (!props.virtualize) return false
238+
239+
return defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
240+
estimateSize: getEstimateSize(items.value, inputSize.value || 'md', props.descriptionKey as string)
241+
})
242+
})
244243
245244
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
246245
const { orientation, size: fieldGroupSize } = useFieldGroup<InputProps>(props)

src/runtime/components/SelectMenu.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ import { useFormField } from '../composables/useFormField'
194194
import { useLocale } from '../composables/useLocale'
195195
import { usePortal } from '../composables/usePortal'
196196
import { compare, get, getDisplayValue, isArrayOfArray } from '../utils'
197+
import { getEstimateSize } from '../utils/virtualizer'
197198
import { tv } from '../utils/tv'
198199
import UIcon from './Icon.vue'
199200
import UAvatar from './Avatar.vue'
@@ -225,15 +226,13 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaul
225226
const portalProps = usePortal(toRef(() => props.portal))
226227
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
227228
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
228-
const virtualizerProps = toRef(() => !!props.virtualize && defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
229-
estimateSize: ({
230-
xs: 24,
231-
sm: 28,
232-
md: 32,
233-
lg: 36,
234-
xl: 40
235-
})[props.size || 'md']
236-
}))
229+
const virtualizerProps = toRef(() => {
230+
if (!props.virtualize) return false
231+
232+
return defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
233+
estimateSize: getEstimateSize(items.value, props.size || 'md', props.descriptionKey as string)
234+
})
235+
})
237236
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t('selectMenu.search'), variant: 'none' }) as InputProps)
238237
239238
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)

src/runtime/components/Tree.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ import { reactivePick, createReusableTemplate } from '@vueuse/core'
148148
import { defu } from 'defu'
149149
import { useAppConfig } from '#imports'
150150
import { get } from '../utils'
151+
import { getEstimateSize } from '../utils/virtualizer'
151152
import { tv } from '../utils/tv'
152153
import UIcon from './Icon.vue'
153154
@@ -187,15 +188,13 @@ const flattenedPaddingFormula = computed(() => {
187188
return (level: number) => `calc(var(--spacing) * ${(level - 1) * config.perLevel + config.base})`
188189
})
189190
190-
const virtualizerProps = toRef(() => !!props.virtualize && defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
191-
estimateSize: ({
192-
xs: 24,
193-
sm: 28,
194-
md: 32,
195-
lg: 36,
196-
xl: 40
197-
})[props.size || 'md']
198-
}))
191+
const virtualizerProps = toRef(() => {
192+
if (!props.virtualize) return false
193+
194+
return defu(typeof props.virtualize === 'boolean' ? {} : props.virtualize, {
195+
estimateSize: getEstimateSize(props.items || [], props.size || 'md')
196+
})
197+
})
199198
200199
const [DefineTreeTemplate, ReuseTreeTemplate] = createReusableTemplate<{ items?: TreeItem[], level: number }, TreeSlots<T>>()
201200
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: TreeItem, index: number, level: number }, TreeSlots<T>>({

src/runtime/utils/virtualizer.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { get } from './index'
2+
3+
function hasDescription(item: any, descriptionKey: string): boolean {
4+
if (typeof item !== 'object' || item === null) {
5+
return false
6+
}
7+
const value = get(item, descriptionKey)
8+
return value !== undefined && value !== null && value !== ''
9+
}
10+
11+
function getSize(size: 'xs' | 'sm' | 'md' | 'lg' | 'xl', hasDescription: boolean): number {
12+
if (hasDescription) {
13+
return ({
14+
xs: 44,
15+
sm: 48,
16+
md: 52,
17+
lg: 56,
18+
xl: 60
19+
})[size]
20+
}
21+
22+
return ({
23+
xs: 24,
24+
sm: 28,
25+
md: 32,
26+
lg: 36,
27+
xl: 40
28+
})[size]
29+
}
30+
31+
/**
32+
* Get estimate size for virtualizers that checks each item individually
33+
* NOTE: This requires Reka UI to support functions for estimateSize (https://github.com/unovue/reka-ui/pull/2288)
34+
* Until then, we check if ANY item has a description and use that for all items
35+
*/
36+
export function getEstimateSize(items: any[], size: 'xs' | 'sm' | 'md' | 'lg' | 'xl', descriptionKey?: string): number {
37+
// TODO: Once Reka UI supports functions, uncomment and change return type to: number | ((index: number) => number)
38+
// return (index: number) => {
39+
// const item = items[index]
40+
// const hasDescription = descriptionKey ? hasDescription(item, descriptionKey) : false
41+
// return getSize(size, hasDescription)
42+
// }
43+
44+
// For now, check if ANY item has a description and use a static size
45+
const anyHasDescription = descriptionKey ? items.some(item => hasDescription(item, descriptionKey)) : false
46+
return getSize(size, anyHasDescription)
47+
}

0 commit comments

Comments
 (0)