Files
react-navigation/packages/stack/src/views/KeyboardManager.tsx
marhaupe 1169ed0946 fix: android textinput gets blurred after navigating back
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.
2020-11-04 22:35:57 +01:00

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