From a3f3274be5d977d1e1710b4e44982594abeab491 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello <3691490+PeterDaveHello@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:55:57 +0800 Subject: [PATCH] Keep IME candidate confirmation from sending or stopping chat CJK input methods use Enter to confirm candidate text. Treating that keydown as an input action can submit incomplete text or stop an active response. Read composition state from both direct and synthetic native events, retain the legacy keyCode 229 fallback, and test each Enter path alone. --- src/components/InputBox/index.jsx | 3 +- src/components/InputBox/input-action.mjs | 10 +++ .../unit/components/input-box-action.test.mjs | 68 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/components/InputBox/input-action.mjs create mode 100644 tests/unit/components/input-box-action.test.mjs diff --git a/src/components/InputBox/index.jsx b/src/components/InputBox/index.jsx index e2252dadb..97eb1b90a 100644 --- a/src/components/InputBox/index.jsx +++ b/src/components/InputBox/index.jsx @@ -11,6 +11,7 @@ import { MIN_CONVERSATION_HEIGHT, MIN_INPUT_HEIGHT, } from './resize.mjs' +import { shouldHandleInputAction } from './input-action.mjs' export function InputBox({ onSubmit, enabled, postMessage, reverseResizeDir }) { const { t } = useTranslation() @@ -93,7 +94,7 @@ export function InputBox({ onSubmit, enabled, postMessage, reverseResizeDir }) { const handleKeyDownOrClick = (e) => { e.stopPropagation() - if (e.type === 'click' || (e.keyCode === 13 && e.shiftKey === false)) { + if (shouldHandleInputAction(e)) { e.preventDefault() if (enabled) { if (!value) return diff --git a/src/components/InputBox/input-action.mjs b/src/components/InputBox/input-action.mjs new file mode 100644 index 000000000..20d979c22 --- /dev/null +++ b/src/components/InputBox/input-action.mjs @@ -0,0 +1,10 @@ +export function shouldHandleInputAction(event) { + if (event.type === 'click') return true + if (event.type !== 'keydown') return false + + const isComposing = event.isComposing || event.nativeEvent?.isComposing + if (isComposing || event.keyCode === 229) return false + + const isEnter = event.key === 'Enter' || event.keyCode === 13 + return isEnter && !event.shiftKey +} diff --git a/tests/unit/components/input-box-action.test.mjs b/tests/unit/components/input-box-action.test.mjs new file mode 100644 index 000000000..815c812a4 --- /dev/null +++ b/tests/unit/components/input-box-action.test.mjs @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { shouldHandleInputAction } from '../../../src/components/InputBox/input-action.mjs' + +test('input actions handle button clicks and both plain Enter forms', () => { + assert.equal(shouldHandleInputAction({ type: 'click' }), true) + assert.equal( + shouldHandleInputAction({ type: 'keydown', key: 'Enter', shiftKey: false }), + true, + ) + assert.equal( + shouldHandleInputAction({ type: 'keydown', keyCode: 13, shiftKey: false }), + true, + ) +}) + +test('input actions preserve Shift+Enter line breaks', () => { + assert.equal( + shouldHandleInputAction({ type: 'keydown', key: 'Enter', keyCode: 13, shiftKey: true }), + false, + ) +}) + +test('input actions ignore directly exposed active IME composition', () => { + assert.equal( + shouldHandleInputAction({ + type: 'keydown', + key: 'Enter', + keyCode: 13, + shiftKey: false, + isComposing: true, + }), + false, + ) +}) + +test('input actions ignore active IME composition on synthetic native events', () => { + assert.equal( + shouldHandleInputAction({ + type: 'keydown', + key: 'Enter', + keyCode: 13, + shiftKey: false, + nativeEvent: { isComposing: true }, + }), + false, + ) +}) + +test('input actions ignore the legacy IME keyCode 229 fallback', () => { + assert.equal( + shouldHandleInputAction({ + type: 'keydown', + key: 'Enter', + keyCode: 229, + shiftKey: false, + isComposing: false, + }), + false, + ) +}) + +test('input actions ignore unrelated keyboard events', () => { + assert.equal( + shouldHandleInputAction({ type: 'keydown', key: 'a', keyCode: 65, shiftKey: false }), + false, + ) +})