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, + ) +})