mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 09:30:30 +08:00
When navigating from ScreenA to ScreenB and then back to ScreenA, react-navigation unconditionally calls `Keyboard.dismiss()`. If the user is fast enough and taps on a `TextInput` after coming back from ScreenB, the keyboard opens, just to be dismissed again. This would also happen if some logic automatically focuses the `TextInput` in ScreenA. This behaviour can be seen observed in https://snack.expo.io/lTDZhVNhV.
111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
import * as React from 'react';
|
|
import { TextInput, Platform, Keyboard } from 'react-native';
|
|
|
|
type Props = {
|
|
enabled: boolean;
|
|
children: (props: {
|
|
onPageChangeStart: () => void;
|
|
onPageChangeConfirm: () => void;
|
|
onPageChangeCancel: () => void;
|
|
}) => React.ReactNode;
|
|
};
|
|
|
|
export default class KeyboardManager extends React.Component<Props> {
|
|
componentWillUnmount() {
|
|
this.clearKeyboardTimeout();
|
|
}
|
|
|
|
// Numeric id of the previously focused text input
|
|
// When a gesture didn't change the tab, we can restore the focused input with this
|
|
private previouslyFocusedTextInput: any | null = null;
|
|
private startTimestamp: number = 0;
|
|
private keyboardTimeout: any;
|
|
|
|
private clearKeyboardTimeout = () => {
|
|
if (this.keyboardTimeout !== undefined) {
|
|
clearTimeout(this.keyboardTimeout);
|
|
this.keyboardTimeout = undefined;
|
|
}
|
|
};
|
|
|
|
private handlePageChangeStart = () => {
|
|
if (!this.props.enabled) {
|
|
return;
|
|
}
|
|
|
|
this.clearKeyboardTimeout();
|
|
|
|
const input: any = TextInput.State.currentlyFocusedInput
|
|
? TextInput.State.currentlyFocusedInput()
|
|
: TextInput.State.currentlyFocusedField();
|
|
|
|
// When a page change begins, blur the currently focused input
|
|
TextInput.State.blurTextInput(input);
|
|
|
|
// Store the id of this input so we can refocus it if change was cancelled
|
|
this.previouslyFocusedTextInput = input;
|
|
|
|
// Store timestamp for touch start
|
|
this.startTimestamp = Date.now();
|
|
};
|
|
|
|
private handlePageChangeConfirm = () => {
|
|
if (!this.props.enabled) {
|
|
return;
|
|
}
|
|
|
|
this.clearKeyboardTimeout();
|
|
|
|
const input = this.previouslyFocusedTextInput;
|
|
|
|
if (input) {
|
|
if (Platform.OS === 'android') {
|
|
Keyboard.dismiss();
|
|
} else {
|
|
TextInput.State.blurTextInput(input);
|
|
}
|
|
}
|
|
|
|
// Cleanup the ID on successful page change
|
|
this.previouslyFocusedTextInput = null;
|
|
};
|
|
|
|
private handlePageChangeCancel = () => {
|
|
if (!this.props.enabled) {
|
|
return;
|
|
}
|
|
|
|
this.clearKeyboardTimeout();
|
|
|
|
// The page didn't change, we should restore the focus of text input
|
|
const input = this.previouslyFocusedTextInput;
|
|
|
|
if (input) {
|
|
// If the interaction was super short we should make sure keyboard won't hide again.
|
|
|
|
// Too fast input refocus will result only in keyboard flashing on screen and hiding right away.
|
|
// During first ~100ms keyboard will be dismissed no matter what,
|
|
// so we have to make sure it won't interrupt input refocus logic.
|
|
// That's why when the interaction is shorter than 100ms we add delay so it won't hide once again.
|
|
// Subtracting timestamps makes us sure the delay is executed only when needed.
|
|
if (Date.now() - this.startTimestamp < 100) {
|
|
this.keyboardTimeout = setTimeout(() => {
|
|
TextInput.State.focusTextInput(input);
|
|
this.previouslyFocusedTextInput = null;
|
|
}, 100);
|
|
} else {
|
|
TextInput.State.focusTextInput(input);
|
|
this.previouslyFocusedTextInput = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
render() {
|
|
return this.props.children({
|
|
onPageChangeStart: this.handlePageChangeStart,
|
|
onPageChangeConfirm: this.handlePageChangeConfirm,
|
|
onPageChangeCancel: this.handlePageChangeCancel,
|
|
});
|
|
}
|
|
}
|