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