diff --git a/packages/vuetify/src/components/VCombobox/VCombobox.tsx b/packages/vuetify/src/components/VCombobox/VCombobox.tsx index 1677dd86d0f..c1430231e6b 100644 --- a/packages/vuetify/src/components/VCombobox/VCombobox.tsx +++ b/packages/vuetify/src/components/VCombobox/VCombobox.tsx @@ -72,7 +72,7 @@ export const makeVComboboxProps = propsFactory({ delimiters: Array as PropType, ...makeFilterProps({ filterKeys: ['title'] }), - ...makeSelectProps({ hideNoData: true, returnObject: true }), + ...makeSelectProps({ hideNoData: false, returnObject: true }), ...omit(makeVTextFieldProps({ modelValue: null, role: 'combobox', @@ -127,6 +127,7 @@ export const VCombobox = genericComponent() const vVirtualScrollRef = ref() const selectionIndex = shallowRef(-1) @@ -194,6 +195,9 @@ export const VCombobox = genericComponent !model.value.some(s => s.value === filteredItem.value)) } + if (filteredItems.value.length === 0 && showAllItemsForNoMatch.value) { + return items.value + } return filteredItems.value }) @@ -214,6 +218,7 @@ export const VCombobox = genericComponent menu.value ? props.closeText : props.openText) watch(_search, value => { + showAllItemsForNoMatch.value = false if (cleared) { // wait for clear to finish, VTextField sets _search to null // then search computed triggers and updates _search to '' @@ -222,6 +227,7 @@ export const VCombobox = genericComponent { - if (!props.hideSelected && menu.value && model.value.length) { + watch(menu, val => { + if (!props.hideSelected && val && model.value.length) { const index = displayItems.value.findIndex( item => model.value.some(s => (props.valueComparator || deepEqual)(s.value, item.value)) ) @@ -448,7 +454,13 @@ export const VCombobox = genericComponent= 0 && vVirtualScrollRef.value?.scrollToIndex(index) }) } - }) + + if (val && search.value && filteredItems.value.length === 0) { + showAllItemsForNoMatch.value = true + } + + isPristine.value = !search.value + }, { immediate: true }) watch(() => props.items, (newVal, oldVal) => { if (menu.value) return @@ -536,7 +548,6 @@ export const VCombobox = genericComponent ))} - { ({ item, index, itemRef }) => { const itemProps = mergeProps(item.props, { @@ -692,7 +703,7 @@ export const VCombobox = genericComponent ( <> { slots['append-inner']?.(...args) } - { (!props.hideNoData || props.items.length) && props.menuIcon ? ( + { props.menuIcon ? ( { expect(model.value).toEqual(expected) }) + it('should show only matching items when reopening the menu', async () => { + const { element } = render(() => ( + + )) + + await userEvent.click(element) + await userEvent.keyboard('c') + await expect(screen.findAllByRole('option')).resolves.toHaveLength(2) + await userEvent.keyboard('al') + await expect(screen.findAllByRole('option')).resolves.toHaveLength(1) + await userEvent.click(document.body) + await expect.poll(() => screen.queryAllByRole('option')).toHaveLength(0) + await userEvent.click(element) + await expect.poll(() => screen.queryAllByRole('option')).toHaveLength(1) + }) + + it('should show only matching items when opening for the first time', async () => { + const model = ref('flor') + const { element } = render(() => ( + + )) + + await userEvent.click(element) + expect(screen.getAllByRole('option')).toHaveLength(1) + await userEvent.click(document.body) + await expect.poll(() => screen.queryAllByRole('option')).toHaveLength(0) + + // expect same behavior when re-opening the menu + await userEvent.click(element) + await expect.poll(() => screen.queryAllByRole('option')).toHaveLength(1) + }) + describe('Showcase', () => { generate({ stories }) })