From bab4005fae1e9152f24ddfca7afda95c128e6c8d Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 13 Nov 2026 14:28:34 +0100 Subject: [PATCH 1/3] fix(Listbox): emit `change` on uncontrolled deselect --- src/runtime/components/Listbox.vue | 3 ++- test/components/Listbox.spec.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/runtime/components/Listbox.vue b/src/runtime/components/Listbox.vue index 26d7c61d46..b23eefa2e2 100644 --- a/src/runtime/components/Listbox.vue +++ b/src/runtime/components/Listbox.vue @@ -248,7 +248,8 @@ const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.listbox || {}) } })) function onUpdate(value: any) { - if (toRaw(props.modelValue) === value) { + // An uncontrolled deselect emits `undefined` too, so only skip unchanged controlled values. + if (props.modelValue !== undefined && toRaw(props.modelValue) === value) { return } diff --git a/test/components/Listbox.spec.ts b/test/components/Listbox.spec.ts index 3c04f98bbb..d38f09ed07 100644 --- a/test/components/Listbox.spec.ts +++ b/test/components/Listbox.spec.ts @@ -124,6 +124,14 @@ describe('Listbox', () => { await input.setValue(items[0]) expect(wrapper.emitted()).toMatchObject({ change: [[{ type: 'change' }]] }) }) + + test('change event on uncontrolled deselect', async () => { + const wrapper = mount(Listbox, { props: { items: ['A', 'B'], defaultValue: 'A' } }) + await wrapper.findAll('[role="option"]')[0]!.trigger('click') + + expect(wrapper.emitted('update:modelValue')).toEqual([[undefined]]) + expect(wrapper.emitted('change')).toHaveLength(1) + }) }) describe('form integration', async () => { From 53e3b3fdc8140ddf3495b4af21d6c2c3ec3f889e Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 13 Nov 2026 14:37:05 +0100 Subject: [PATCH 2/3] test(Listbox): use typed items in the deselect test --- playgrounds/nuxt/app/pages/index.vue | 25 ++++++++++++++++++------- test/components/Listbox.spec.ts | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/playgrounds/nuxt/app/pages/index.vue b/playgrounds/nuxt/app/pages/index.vue index d19ae7622c..bd06281849 100644 --- a/playgrounds/nuxt/app/pages/index.vue +++ b/playgrounds/nuxt/app/pages/index.vue @@ -1,12 +1,23 @@ + + diff --git a/test/components/Listbox.spec.ts b/test/components/Listbox.spec.ts index d38f09ed07..c046e3aee7 100644 --- a/test/components/Listbox.spec.ts +++ b/test/components/Listbox.spec.ts @@ -126,7 +126,7 @@ describe('Listbox', () => { }) test('change event on uncontrolled deselect', async () => { - const wrapper = mount(Listbox, { props: { items: ['A', 'B'], defaultValue: 'A' } }) + const wrapper = mount(Listbox, { props: { items, valueKey: 'value', defaultValue: 'FR' } }) await wrapper.findAll('[role="option"]')[0]!.trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([[undefined]]) From 42bbfe044e44b51daebbc6e214ebc21ef631ce79 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 13 Nov 2026 15:13:11 +0100 Subject: [PATCH 3/3] chore: drop unrelated playground change --- playgrounds/nuxt/app/pages/index.vue | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/playgrounds/nuxt/app/pages/index.vue b/playgrounds/nuxt/app/pages/index.vue index bd06281849..d19ae7622c 100644 --- a/playgrounds/nuxt/app/pages/index.vue +++ b/playgrounds/nuxt/app/pages/index.vue @@ -1,23 +1,12 @@ - -