From d01628975d9f4d2d3ac9f9792df1bc5946a87061 Mon Sep 17 00:00:00 2001 From: "chihao.ou" Date: Fri, 9 Oct 2026 20:00:54 +0800 Subject: [PATCH] fix(select): preserve keyboard input during IME composition --- frontend/src/components/common/Select.vue | 1 + .../common/__tests__/Select.ime.spec.ts | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 frontend/src/components/common/__tests__/Select.ime.spec.ts diff --git a/frontend/src/components/common/Select.vue b/frontend/src/components/common/Select.vue index c062bd4fdc..68825af14e 100644 --- a/frontend/src/components/common/Select.vue +++ b/frontend/src/components/common/Select.vue @@ -446,6 +446,7 @@ const onTriggerKeyDown = () => { } const onDropdownKeyDown = (e: KeyboardEvent) => { + if (e.isComposing) return switch (e.key) { case 'ArrowDown': e.preventDefault() diff --git a/frontend/src/components/common/__tests__/Select.ime.spec.ts b/frontend/src/components/common/__tests__/Select.ime.spec.ts new file mode 100644 index 0000000000..7d92ab201f --- /dev/null +++ b/frontend/src/components/common/__tests__/Select.ime.spec.ts @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { enableAutoUnmount, mount } from '@vue/test-utils' +import { nextTick } from 'vue' +import Select from '../Select.vue' + +vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) })) +enableAutoUnmount(afterEach) +afterEach(() => { document.body.innerHTML = '' }) + +describe('Select IME keyboard input', () => { + it.each(['Enter', 'Escape', 'ArrowDown', 'ArrowUp'])('leaves composing %s to the input method', async (key) => { + const wrapper = mount(Select, { + attachTo: document.body, + props: { modelValue: null, searchable: true, options: [ + { value: 'first', label: 'First' }, { value: 'second', label: 'Second' } + ] } + }) + await wrapper.get('button').trigger('click') + await nextTick() + const input = document.querySelector('.select-search-input')! + expect(document.activeElement).toBe(input) + const composing = new KeyboardEvent('keydown', { key, isComposing: true, bubbles: true, cancelable: true }) + input.dispatchEvent(composing) + await nextTick() + expect(composing.defaultPrevented).toBe(false) + expect(wrapper.emitted('update:modelValue')).toBeUndefined() + expect(wrapper.get('button').attributes('aria-expanded')).toBe('true') + input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true })) + await nextTick() + expect(wrapper.emitted('update:modelValue')).toEqual([['first']]) + }) +})