[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
This commit is contained in:
Nicolas Gallagher
2020-05-27 13:53:48 -07:00
parent 52f903229e
commit 0901be6e5c
2 changed files with 52 additions and 39 deletions
@@ -42,6 +42,7 @@ function createKeyboardEvent(
ctrlKey = false, ctrlKey = false,
isComposing = false, isComposing = false,
key = '', key = '',
keyCode = 0,
metaKey = false, metaKey = false,
preventDefault = () => {}, preventDefault = () => {},
shiftKey = false shiftKey = false
@@ -52,6 +53,7 @@ function createKeyboardEvent(
ctrlKey, ctrlKey,
isComposing, isComposing,
key, key,
keyCode,
metaKey, metaKey,
preventDefault, preventDefault,
shiftKey shiftKey
@@ -271,14 +273,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'ArrowLeft', key: 'ArrowLeft',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -293,14 +295,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'Backspace', key: 'Backspace',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -315,14 +317,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'Enter', key: 'Enter',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -337,14 +339,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'Escape', key: 'Escape',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -359,14 +361,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: ' ', key: ' ',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -381,14 +383,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'Tab', key: 'Tab',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -403,14 +405,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
key: 'a', key: 'a',
metaKey: false, metaKey: false,
shiftKey: false, shiftKey: false,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -433,14 +435,14 @@ describe('components/TextInput', () => {
expect(onKeyPress).toHaveBeenCalledTimes(1); expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith( expect(onKeyPress).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
nativeEvent: { nativeEvent: expect.objectContaining({
altKey: true, altKey: true,
ctrlKey: true, ctrlKey: true,
key: ' ', key: ' ',
metaKey: true, metaKey: true,
shiftKey: true, shiftKey: true,
target: expect.anything() target: expect.anything()
} })
}) })
); );
}); });
@@ -500,6 +502,17 @@ describe('components/TextInput', () => {
} }
}); });
test('single-line input while composing', () => {
const onSubmitEditing = jest.fn();
const { container } = render(
<TextInput defaultValue="12345" onSubmitEditing={onSubmitEditing} />
);
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', () => { test('multi-line input', () => {
const onSubmitEditing = jest.fn(); const onSubmitEditing = jest.fn();
const { container } = render( const { container } = render(
@@ -567,17 +580,15 @@ describe('components/TextInput', () => {
test('set cursor location', () => { test('set cursor location', () => {
const cursorLocation = { start: 3, end: 3 }; const cursorLocation = { start: 3, end: 3 };
const { container: defaultContainer } = render(<TextInput defaultValue="12345" />); const { container: defaultContainer } = render(<TextInput defaultValue="12345" />);
const { container: customContainer } = render(
<TextInput defaultValue="12345" selection={cursorLocation} />
);
const inputDefaultSelection = findInput(defaultContainer); const inputDefaultSelection = findInput(defaultContainer);
const inputCustomSelection = findInput(customContainer);
// default selection is 0 // default selection is 0
expect(inputDefaultSelection.selectionStart).toEqual(0); expect(inputDefaultSelection.selectionStart).toEqual(0);
expect(inputDefaultSelection.selectionEnd).toEqual(0); expect(inputDefaultSelection.selectionEnd).toEqual(0);
const { container: customContainer } = render(
<TextInput defaultValue="12345" selection={cursorLocation} />
);
const inputCustomSelection = findInput(customContainer);
// custom selection sets cursor at custom position // custom selection sets cursor at custom position
expect(inputCustomSelection.selectionStart).toEqual(cursorLocation.start); expect(inputCustomSelection.selectionStart).toEqual(cursorLocation.start);
expect(inputCustomSelection.selectionEnd).toEqual(cursorLocation.end); expect(inputCustomSelection.selectionEnd).toEqual(cursorLocation.end);
+19 -17
View File
@@ -104,6 +104,12 @@ const forwardPropsList = {
const pickProps = props => pick(props, 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<TextInputProps, *>((props, forwardedRef) => { const TextInput = forwardRef<TextInputProps, *>((props, forwardedRef) => {
const { const {
autoCapitalize = 'sentences', autoCapitalize = 'sentences',
@@ -267,31 +273,27 @@ const TextInput = forwardRef<TextInputProps, *>((props, forwardedRef) => {
const blurOnSubmitDefault = !multiline; const blurOnSubmitDefault = !multiline;
const shouldBlurOnSubmit = blurOnSubmit == null ? blurOnSubmitDefault : blurOnSubmit; const shouldBlurOnSubmit = blurOnSubmit == null ? blurOnSubmitDefault : blurOnSubmit;
if (onKeyPress) { const nativeEvent = e.nativeEvent;
const keyValue = e.key; const isComposing = isEventComposing(nativeEvent);
if (keyValue) { if (onKeyPress) {
e.nativeEvent = { onKeyPress(e);
altKey: e.altKey,
ctrlKey: e.ctrlKey,
key: keyValue,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
target: e.target
};
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) { if ((blurOnSubmit || !multiline) && onSubmitEditing) {
// prevent "Enter" from inserting a newline // prevent "Enter" from inserting a newline or submitting a form
e.preventDefault(); e.preventDefault();
e.nativeEvent = { target: e.target, text: e.target.value }; nativeEvent.text = e.target.value;
onSubmitEditing(e); onSubmitEditing(e);
} }
if (shouldBlurOnSubmit && hostRef.current != null) { if (shouldBlurOnSubmit && hostRef.current != null) {
// $FlowFixMe
hostRef.current.blur(); hostRef.current.blur();
} }
} }