mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-27 00:06:55 +00:00
[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:
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user