From 4ed4859b19e7f0e138a970a2dc845f265d1d9f5d Mon Sep 17 00:00:00 2001 From: ManoharPaturi <186662190+manoharpaturi@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:32:31 +0530 Subject: [PATCH] fix(core): guard TextInput onEnter against IME conversion commits --- .changeset/textinput-ime-enter.md | 6 +++ packages/core/src/TextInput/TextInput.doc.mjs | 3 +- .../core/src/TextInput/TextInput.test.tsx | 45 +++++++++++++++++++ packages/core/src/TextInput/TextInput.tsx | 9 +++- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .changeset/textinput-ime-enter.md diff --git a/.changeset/textinput-ime-enter.md b/.changeset/textinput-ime-enter.md new file mode 100644 index 0000000000000..bbb638bc1ccad --- /dev/null +++ b/.changeset/textinput-ime-enter.md @@ -0,0 +1,6 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] TextInput's `onEnter` no longer fires for the Enter that commits an IME conversion (Japanese/Chinese/Korean input); `onKeyDown` still receives the raw event. (#6082) +@ManoharPaturi diff --git a/packages/core/src/TextInput/TextInput.doc.mjs b/packages/core/src/TextInput/TextInput.doc.mjs index 51d58fefbb719..50c06ce677d42 100644 --- a/packages/core/src/TextInput/TextInput.doc.mjs +++ b/packages/core/src/TextInput/TextInput.doc.mjs @@ -74,7 +74,8 @@ export const docs = { { name: 'onEnter', type: '() => void', - description: 'Callback fired when the user presses the Enter key.', + description: + 'Callback fired when the user presses the Enter key. IME-safe: Enter used to commit a Japanese/Chinese/Korean conversion does not fire it.', }, { name: 'onKeyDown', diff --git a/packages/core/src/TextInput/TextInput.test.tsx b/packages/core/src/TextInput/TextInput.test.tsx index 4ff1a9c1b00bd..daaa13abc44f7 100644 --- a/packages/core/src/TextInput/TextInput.test.tsx +++ b/packages/core/src/TextInput/TextInput.test.tsx @@ -538,6 +538,51 @@ describe('TextInput', () => { expect(handleEnter).toHaveBeenCalledTimes(1); }); + it('does not call onEnter when Enter commits an IME conversion', () => { + // The browser fires this composing keydown for the Enter that commits an + // IME candidate (isComposing: true, or the legacy keyCode 229) before + // compositionend writes the conversion. It must NOT trigger onEnter — + // submit/save actions would fire before the user intends to submit. + // See utils/ime.ts and #6082. + const handleEnter = vi.fn(); + const {container} = render( + {}} + onEnter={handleEnter} + />, + ); + const input = container.querySelector('input')!; + fireEvent.keyDown(input, {key: 'Enter', isComposing: true}); + expect(handleEnter).not.toHaveBeenCalled(); + fireEvent.keyDown(input, {key: 'Enter', keyCode: 229}); + expect(handleEnter).not.toHaveBeenCalled(); + + // A real, non-composing Enter after composition ends still submits. + fireEvent.keyDown(input, {key: 'Enter'}); + expect(handleEnter).toHaveBeenCalledTimes(1); + }); + + it('still calls onKeyDown for composing keydowns', () => { + // onKeyDown is the raw escape hatch: it keeps receiving IME keydowns so + // consumers with custom composition handling are unaffected by the + // onEnter guard. + const handleKeyDown = vi.fn(); + const {container} = render( + {}} + onKeyDown={handleKeyDown} + onEnter={() => {}} + />, + ); + const input = container.querySelector('input')!; + fireEvent.keyDown(input, {key: 'Enter', isComposing: true}); + expect(handleKeyDown).toHaveBeenCalledTimes(1); + }); + it('does not call onEnter for other keys', async () => { const user = userEvent.setup(); const handleEnter = vi.fn(); diff --git a/packages/core/src/TextInput/TextInput.tsx b/packages/core/src/TextInput/TextInput.tsx index 3885e869498ba..2ac903b5da945 100644 --- a/packages/core/src/TextInput/TextInput.tsx +++ b/packages/core/src/TextInput/TextInput.tsx @@ -47,7 +47,7 @@ import {renderIconSlot, type IconType} from '../Icon'; import {Spinner} from '../Spinner'; import {useTooltip} from '../Tooltip'; import {VisuallyHidden} from '../VisuallyHidden'; -import {getInputARIA} from '../utils'; +import {getInputARIA, isImeKeyEvent} from '../utils'; const styles = stylex.create({ input: { @@ -431,7 +431,12 @@ export function TextInput({ onKeyDown={ onEnter || onKeyDown ? e => { - if (e.key === 'Enter') { + // The composing keydown fires before compositionend, so without + // this guard pressing Enter to commit a Japanese/Chinese/Korean + // IME conversion would trigger onEnter (submit/save actions) + // before the user intends to submit. onKeyDown still receives + // the raw event. See utils/ime.ts (#6082). + if (e.key === 'Enter' && !isImeKeyEvent(e.nativeEvent)) { onEnter?.(); } onKeyDown?.(e);

AltStyle によって変換されたページ (->オリジナル) /