From 4426a3f86965df7db7876febe79c241380e0bc64 Mon Sep 17 00:00:00 2001 From: greymoth <246701683+greymoth-jp@users.noreply.github.com> Date: Tue, 30 Jun 2026 01:32:23 +0900 Subject: [PATCH] fix: ignore the Enter that confirms an IME composition when selecting a mention On browsers that report the IME commit keydown as `which === ENTER` with `isComposing` true (Safari being the common one), pressing Enter to confirm a composed CJK mention selected the highlighted option instead of committing the text. Guard the Enter branch in onInternalKeyDown with event.nativeEvent.isComposing so a composing Enter falls through to the IME. --- src/Mentions.tsx | 7 +++++ tests/Composition.spec.tsx | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/Composition.spec.tsx diff --git a/src/Mentions.tsx b/src/Mentions.tsx index 1b745f33..141a6040 100644 --- a/src/Mentions.tsx +++ b/src/Mentions.tsx @@ -402,6 +402,13 @@ const InternalMentions = forwardRef( } else if (which === KeyCode.ESC) { stopMeasure(); } else if (which === KeyCode.ENTER) { + // The Enter key that only confirms an IME composition should not select + // the active option. On some browsers (e.g. Safari) this keydown still + // reports `which === ENTER` while `isComposing` is true, the same case + // rc-select guards against in its input. + if (event.nativeEvent.isComposing) { + return; + } // Measure hit event.preventDefault(); // loading skip diff --git a/tests/Composition.spec.tsx b/tests/Composition.spec.tsx new file mode 100644 index 00000000..aec134cc --- /dev/null +++ b/tests/Composition.spec.tsx @@ -0,0 +1,63 @@ +import { act, createEvent, fireEvent, render } from '@testing-library/react'; +import { KeyCode } from '@rc-component/util'; +import React from 'react'; +import Mentions from '../src'; +import { simulateInput } from './util'; + +// The keydown that confirms an IME composition still reports `which === ENTER` +// on some browsers (e.g. Safari) while `isComposing` is true. +function imeEnterKeyDown(element: HTMLElement) { + const event = createEvent.keyDown(element, { keyCode: KeyCode.ENTER }); + Object.defineProperties(event, { + which: { get: () => KeyCode.ENTER }, + isComposing: { get: () => true }, + }); + act(() => { + fireEvent(element, event); + }); +} + +const options = [ + { value: 'bamboo', label: 'Bamboo' }, + { value: 'cat', label: 'Cat' }, +]; + +describe('Mentions.Composition', () => { + it('does not select the active option when Enter only confirms the IME composition', () => { + const onChange = jest.fn(); + const onSelect = jest.fn(); + const { container } = render( + , + ); + + simulateInput(container, '@'); + + // Typing the trigger fires onChange, so only watch what the Enter does. + onChange.mockClear(); + onSelect.mockClear(); + + imeEnterKeyDown(container.querySelector('textarea')); + + expect(onSelect).not.toHaveBeenCalled(); + expect(onChange).not.toHaveBeenCalled(); + }); + + it('still selects the active option on a normal Enter', () => { + const onSelect = jest.fn(); + const { container } = render( + , + ); + + simulateInput(container, '@'); + + fireEvent.keyDown(container.querySelector('textarea'), { + keyCode: KeyCode.ENTER, + which: KeyCode.ENTER, + }); + + expect(onSelect).toHaveBeenCalledWith( + expect.objectContaining({ value: 'bamboo' }), + '@', + ); + }); +});