From 5cc361652837916fd814157d714facc18816c110 Mon Sep 17 00:00:00 2001 From: greymoth <246701683+greymoth-jp@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:46:55 +0900 Subject: [PATCH] fix(defineShortcuts): ignore key events during IME composition --- src/runtime/composables/defineShortcuts.ts | 7 ++++++ test/composables/defineShortcuts.spec.ts | 28 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/runtime/composables/defineShortcuts.ts b/src/runtime/composables/defineShortcuts.ts index 10b1ce4503..22119f911e 100644 --- a/src/runtime/composables/defineShortcuts.ts +++ b/src/runtime/composables/defineShortcuts.ts @@ -108,6 +108,13 @@ export function defineShortcuts(config: MaybeRef, options: Shor const shiftableCodes = shiftableKeys.map(k => convertKeyToCode(k)) const onKeyDown = (e: KeyboardEvent) => { + // Ignore keydowns dispatched during IME composition (for example picking a + // Japanese or Chinese candidate), otherwise the composed keystrokes can match + // and fire a shortcut. Same condition as the useIMEGuard composable. + if (e.isComposing || e.keyCode === 229) { + return + } + // Input autocomplete triggers a keydown event if (!e.key) { return diff --git a/test/composables/defineShortcuts.spec.ts b/test/composables/defineShortcuts.spec.ts index 82b98380c1..bc45565e07 100644 --- a/test/composables/defineShortcuts.spec.ts +++ b/test/composables/defineShortcuts.spec.ts @@ -456,4 +456,32 @@ describe('defineShortcuts', () => { expect(handler).not.toHaveBeenCalled() }) }) + + describe('IME composition', () => { + it('does NOT trigger a shortcut while composing', async () => { + const handler = vi.fn() + await registerShortcuts({ a: handler }) + + fireKeydown('a', { isComposing: true }) + expect(handler).not.toHaveBeenCalled() + }) + + it('does NOT accumulate chained input while composing', async () => { + const handler = vi.fn() + await registerShortcuts({ 'g-i': handler }) + + fireKeydown('g', { isComposing: true }) + fireKeydown('i', { isComposing: true }) + expect(handler).not.toHaveBeenCalled() + }) + + it('triggers once composition has ended', async () => { + const handler = vi.fn() + await registerShortcuts({ a: handler }) + + fireKeydown('a', { isComposing: true }) + fireKeydown('a') + expect(handler).toHaveBeenCalledOnce() + }) + }) })