diff --git a/apps/desktop/src/app/chat/composer/ime-keyboard.test.ts b/apps/desktop/src/app/chat/composer/ime-keyboard.test.ts new file mode 100644 index 000000000000..24797132593a --- /dev/null +++ b/apps/desktop/src/app/chat/composer/ime-keyboard.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' + +import { shouldLetImeHandleKeyDown } from './ime-keyboard' + +describe('shouldLetImeHandleKeyDown', () => { + it('lets the IME consume Enter while native composition is active', () => { + expect(shouldLetImeHandleKeyDown({ key: 'Enter', isComposing: true })).toBe(true) + }) + + it('lets the IME consume Enter for Process-key candidate confirmation', () => { + expect(shouldLetImeHandleKeyDown({ key: 'Process', isComposing: false })).toBe(true) + }) + + it('lets the IME consume Enter for keyCode 229 candidate confirmation', () => { + expect(shouldLetImeHandleKeyDown({ key: 'Enter', keyCode: 229, isComposing: false })).toBe(true) + }) + + it('lets the IME consume Enter for which 229 candidate confirmation', () => { + expect(shouldLetImeHandleKeyDown({ key: 'Enter', which: 229, isComposing: false })).toBe(true) + }) + + it('does not treat normal Enter as IME input', () => { + expect(shouldLetImeHandleKeyDown({ key: 'Enter', keyCode: 13, isComposing: false })).toBe(false) + }) +}) diff --git a/apps/desktop/src/app/chat/composer/ime-keyboard.ts b/apps/desktop/src/app/chat/composer/ime-keyboard.ts new file mode 100644 index 000000000000..a42136c96a16 --- /dev/null +++ b/apps/desktop/src/app/chat/composer/ime-keyboard.ts @@ -0,0 +1,10 @@ +export interface ImeKeyboardEventLike { + isComposing?: boolean + key?: string + keyCode?: number + which?: number +} + +export function shouldLetImeHandleKeyDown(event: ImeKeyboardEventLike): boolean { + return Boolean(event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.which === 229) +} diff --git a/apps/desktop/src/app/chat/composer/index.tsx b/apps/desktop/src/app/chat/composer/index.tsx index 2288a7b7f82b..917eef0b91e0 100644 --- a/apps/desktop/src/app/chat/composer/index.tsx +++ b/apps/desktop/src/app/chat/composer/index.tsx @@ -54,6 +54,7 @@ import { useAtCompletions } from './hooks/use-at-completions' import { useSlashCompletions } from './hooks/use-slash-completions' import { useVoiceConversation } from './hooks/use-voice-conversation' import { useVoiceRecorder } from './hooks/use-voice-recorder' +import { shouldLetImeHandleKeyDown } from './ime-keyboard' import { dragHasAttachments, droppedFileInlineRef, @@ -657,11 +658,9 @@ export function ChatBar({ const handleEditorKeyDown = (event: KeyboardEvent) => { // IME composition: Enter confirms composed text, not a message submission. - // We check both composingRef (set by compositionstart/compositionend, robust - // across browsers) and nativeEvent.isComposing (Chromium fallback). Without - // this guard, pressing Enter to finalise a Korean/Japanese/Chinese IME - // preedit fires submitDraft() and splits the message mid-word. - if (composingRef.current || event.nativeEvent.isComposing) { + // Some Chinese IMEs report candidate confirmation as keyCode/which 229 or + // key "Process" after isComposing has already flipped false. + if (composingRef.current || shouldLetImeHandleKeyDown(event.nativeEvent)) { return }