diff --git a/packages/vuetify/src/components/VCombobox/VCombobox.tsx b/packages/vuetify/src/components/VCombobox/VCombobox.tsx index 0dcec9ad76b..8a439953184 100644 --- a/packages/vuetify/src/components/VCombobox/VCombobox.tsx +++ b/packages/vuetify/src/components/VCombobox/VCombobox.tsx @@ -133,13 +133,13 @@ export const VCombobox = genericComponent() const selectionIndex = shallowRef(-1) let cleared = false - const { items, transformIn, transformOut } = useItems(props) + const { items, transformIn, transformOut, emptyValues } = useItems(props) const { textColorClasses, textColorStyles } = useTextColor(() => vTextFieldRef.value?.color) const model = useProxiedModel( props, 'modelValue', [], - v => transformIn(wrapInArray(v)), + v => transformIn(wrapInArray(v, emptyValues)), v => { const transformed = transformOut(v) return props.multiple ? transformed : (transformed[0] ?? null) diff --git a/packages/vuetify/src/components/VCombobox/__tests__/VCombobox.spec.browser.tsx b/packages/vuetify/src/components/VCombobox/__tests__/VCombobox.spec.browser.tsx index c00d34b9b1c..97811c5de67 100644 --- a/packages/vuetify/src/components/VCombobox/__tests__/VCombobox.spec.browser.tsx +++ b/packages/vuetify/src/components/VCombobox/__tests__/VCombobox.spec.browser.tsx @@ -380,6 +380,20 @@ describe('VCombobox', () => { id: 'item2', }]) }) + + it('should show placeholder if initial value is empty string', () => { + const emptyString = ref('') + + const { getByPlaceholderText } = render(() => ( + + )) + + expect(getByPlaceholderText('select something')).toBeVisible() + }) }) describe('readonly', () => { diff --git a/packages/vuetify/src/composables/list-items.ts b/packages/vuetify/src/composables/list-items.ts index c93cc44ed4f..7084279f012 100644 --- a/packages/vuetify/src/composables/list-items.ts +++ b/packages/vuetify/src/composables/list-items.ts @@ -122,16 +122,25 @@ export function transformItems ( export function useItems (props: ItemProps) { const items = computed(() => transformItems(props, props.items)) - const hasNullItem = computed(() => items.value.some(item => item.value === null)) + const hasNullItem = shallowRef(false) + const emptyValues = shallowRef([]) const itemsMap = shallowRef>(new Map()) const keylessItems = shallowRef([]) watchEffect(() => { const _items = items.value + let _hasNullItem = false + const _emptyValues = new Set() const map = new Map() const keyless = [] for (let i = 0; i < _items.length; i++) { const item = _items[i] + if (item.value === null) { + _hasNullItem = true + } + if (item.value === '' || item.value == null) { + _emptyValues.add(item.value) + } if (isPrimitive(item.value) || item.value === null) { let values = map.get(item.value) if (!values) { @@ -143,6 +152,8 @@ export function useItems (props: ItemProps) { keyless.push(item) } } + hasNullItem.value = _hasNullItem + emptyValues.value = ['', null, undefined].filter(v => !_emptyValues.has(v)) itemsMap.value = map keylessItems.value = keyless }) @@ -204,5 +215,5 @@ export function useItems (props: ItemProps) { : value.map(({ value }) => value) } - return { items, transformIn, transformOut } + return { items, transformIn, transformOut, emptyValues } } diff --git a/packages/vuetify/src/util/helpers.ts b/packages/vuetify/src/util/helpers.ts index 8662354797f..7c1688a1148 100644 --- a/packages/vuetify/src/util/helpers.ts +++ b/packages/vuetify/src/util/helpers.ts @@ -389,11 +389,12 @@ export function arrayDiff (a: any[], b: any[]): any[] { type IfAny = 0 extends (1 & T) ? Y : N; export function wrapInArray ( - v: T | null | undefined + v: T | null | undefined, + emptyValues?: Ref ): T extends readonly any[] ? IfAny : NonNullable[] { - return v == null + return v == null || emptyValues?.value?.includes(v) ? [] as any : Array.isArray(v) ? v as any : [v] as any