From 0901be6e5cba996039ebcffe2de08554f5ac4fa2 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Wed, 27 May 2020 13:53:48 -0700 Subject: [PATCH] [fix] TextInput onKeyPress and onSubmitEditing events onKeyPress forwards the synthetic keydown event. onSubmitEditing is only called if IME composition is not in progress. Fix #1332 --- .../exports/TextInput/__tests__/index-test.js | 55 +++++++++++-------- .../src/exports/TextInput/index.js | 36 ++++++------ 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js b/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js index f8d2c3f6..1f3ab24a 100644 --- a/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js @@ -42,6 +42,7 @@ function createKeyboardEvent( ctrlKey = false, isComposing = false, key = '', + keyCode = 0, metaKey = false, preventDefault = () => {}, shiftKey = false @@ -52,6 +53,7 @@ function createKeyboardEvent( ctrlKey, isComposing, key, + keyCode, metaKey, preventDefault, shiftKey @@ -271,14 +273,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'ArrowLeft', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -293,14 +295,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'Backspace', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -315,14 +317,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'Enter', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -337,14 +339,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'Escape', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -359,14 +361,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: ' ', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -381,14 +383,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'Tab', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -403,14 +405,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: false, ctrlKey: false, key: 'a', metaKey: false, shiftKey: false, target: expect.anything() - } + }) }) ); }); @@ -433,14 +435,14 @@ describe('components/TextInput', () => { expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toBeCalledWith( expect.objectContaining({ - nativeEvent: { + nativeEvent: expect.objectContaining({ altKey: true, ctrlKey: true, key: ' ', metaKey: true, shiftKey: true, target: expect.anything() - } + }) }) ); }); @@ -500,6 +502,17 @@ describe('components/TextInput', () => { } }); + test('single-line input while composing', () => { + const onSubmitEditing = jest.fn(); + const { container } = render( + + ); + const input = findInput(container); + input.dispatchEvent(keydown({ key: 'Enter', isComposing: true, keyCode: 13 })); + input.dispatchEvent(keydown({ key: 'Enter', isComposing: false, keyCode: 229 })); + expect(onSubmitEditing).not.toHaveBeenCalled(); + }); + test('multi-line input', () => { const onSubmitEditing = jest.fn(); const { container } = render( @@ -567,17 +580,15 @@ describe('components/TextInput', () => { test('set cursor location', () => { const cursorLocation = { start: 3, end: 3 }; const { container: defaultContainer } = render(); - const { container: customContainer } = render( - - ); - const inputDefaultSelection = findInput(defaultContainer); - const inputCustomSelection = findInput(customContainer); - // default selection is 0 expect(inputDefaultSelection.selectionStart).toEqual(0); expect(inputDefaultSelection.selectionEnd).toEqual(0); + const { container: customContainer } = render( + + ); + const inputCustomSelection = findInput(customContainer); // custom selection sets cursor at custom position expect(inputCustomSelection.selectionStart).toEqual(cursorLocation.start); expect(inputCustomSelection.selectionEnd).toEqual(cursorLocation.end); diff --git a/packages/react-native-web/src/exports/TextInput/index.js b/packages/react-native-web/src/exports/TextInput/index.js index 13f46a48..4e1a8f6f 100644 --- a/packages/react-native-web/src/exports/TextInput/index.js +++ b/packages/react-native-web/src/exports/TextInput/index.js @@ -104,6 +104,12 @@ const forwardPropsList = { const pickProps = props => pick(props, forwardPropsList); +// If an Input Method Editor is processing key input, the 'keyCode' is 229. +// https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode +function isEventComposing(nativeEvent) { + return nativeEvent.isComposing || nativeEvent.keyCode === 229; +} + const TextInput = forwardRef((props, forwardedRef) => { const { autoCapitalize = 'sentences', @@ -267,31 +273,27 @@ const TextInput = forwardRef((props, forwardedRef) => { const blurOnSubmitDefault = !multiline; const shouldBlurOnSubmit = blurOnSubmit == null ? blurOnSubmitDefault : blurOnSubmit; - if (onKeyPress) { - const keyValue = e.key; + const nativeEvent = e.nativeEvent; + const isComposing = isEventComposing(nativeEvent); - if (keyValue) { - e.nativeEvent = { - altKey: e.altKey, - ctrlKey: e.ctrlKey, - key: keyValue, - metaKey: e.metaKey, - shiftKey: e.shiftKey, - target: e.target - }; - onKeyPress(e); - } + if (onKeyPress) { + onKeyPress(e); } - if (!e.isDefaultPrevented() && e.key === 'Enter' && !e.shiftKey) { + if ( + e.key === 'Enter' && + !e.shiftKey && + // Do not call submit if composition is occuring. + !isComposing && + !e.isDefaultPrevented() + ) { if ((blurOnSubmit || !multiline) && onSubmitEditing) { - // prevent "Enter" from inserting a newline + // prevent "Enter" from inserting a newline or submitting a form e.preventDefault(); - e.nativeEvent = { target: e.target, text: e.target.value }; + nativeEvent.text = e.target.value; onSubmitEditing(e); } if (shouldBlurOnSubmit && hostRef.current != null) { - // $FlowFixMe hostRef.current.blur(); } }