Skip to content
This repository was archived by the owner on Nov 27, 2022. It is now read-only.

Commit ccfa884

Browse files
committed
feat: improve keyboard handling during tab change
Currently the keyboard is dismissed only when a swipe gesture starts. This commit changes the behaviour so that: - Keyboard is dismissed if the tab index changes - When a gesture begines, keyboard is dismissed, but if the index didn't change at end of the gesture, it's restored This is closer to iOS native behaviour and is based on previous work by [@skevy](https://github.com/skevy) in react-navgation: react-navigation/react-navigation#3951 The old behaviour is left as the 'on-drag' option for backward compatibility.
1 parent 3cfa23a commit ccfa884

File tree

4 files changed

+59
-17
lines changed

4 files changed

+59
-17
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ Boolean indicating whether to remove invisible views (such as unfocused screens)
280280

281281
String indicating whether the keyboard gets dismissed in response to a drag gesture. Possible values are:
282282

283-
- `'on-drag'` (default): the keyboard is dismissed when a drag begins.
283+
- `'auto'` (default): the keyboard is dismissed when the index changes.
284+
- `'on-drag'`: the keyboard is dismissed when a drag begins.
284285
- `'none'`: drags do not dismiss the keyboard.
285286

286287
##### `swipeEnabled`

src/Pager.tsx

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { StyleSheet, Keyboard, I18nManager } from 'react-native';
2+
import { StyleSheet, TextInput, Keyboard, I18nManager } from 'react-native';
33
import { PanGestureHandler, State } from 'react-native-gesture-handler';
44
import Animated, { Easing } from 'react-native-reanimated';
55
import memoize from './memoize';
@@ -215,6 +215,12 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
215215
// Remember to set it before transition needs to occur
216216
private isSwipeGesture: Animated.Value<Binary> = new Value(FALSE);
217217

218+
// Track the index value when a swipe gesture has ended
219+
// This lets us know if a gesture end triggered a tab switch or not
220+
private indexAtSwipeEnd: Animated.Value<number> = new Value(
221+
this.props.navigationState.index
222+
);
223+
218224
// Mappings to some prop values
219225
// We use them in animation calculations, so we need live animated nodes
220226
private routesLength = new Value(this.props.navigationState.routes.length);
@@ -291,6 +297,10 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
291297
// It also needs to be reset right after componentDidUpdate fires
292298
private pendingIndexValue: number | undefined = undefined;
293299

300+
// Numeric id of the previously focused text input
301+
// When a gesture didn't change the tab, we can restore the focused input with this
302+
private previouslyFocusedTextInput: number | null = null;
303+
294304
// Listeners for the entered screen
295305
private enterListeners: Listener[] = [];
296306

@@ -301,7 +311,7 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
301311
};
302312

303313
private jumpTo = (key: string) => {
304-
const { navigationState } = this.props;
314+
const { navigationState, keyboardDismissMode, onIndexChange } = this.props;
305315

306316
const index = navigationState.routes.findIndex(route => route.key === key);
307317

@@ -311,7 +321,13 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
311321
if (navigationState.index === index) {
312322
this.jumpToIndex(index);
313323
} else {
314-
this.props.onIndexChange(index);
324+
onIndexChange(index);
325+
326+
// When the index changes, the focused input will no longer be in current tab
327+
// So we should dismiss the keyboard
328+
if (keyboardDismissMode === 'auto') {
329+
Keyboard.dismiss();
330+
}
315331
}
316332
};
317333

@@ -471,19 +487,43 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
471487
// Listen to updates for this value only when it changes
472488
// Without `onChange`, this will fire even if the value didn't change
473489
// We don't want to call the listeners if the value didn't change
474-
call([this.isSwiping], ([value]: readonly Binary[]) => {
475-
const { keyboardDismissMode, onSwipeStart, onSwipeEnd } = this.props;
476-
477-
if (value === TRUE) {
478-
onSwipeStart && onSwipeStart();
479-
480-
if (keyboardDismissMode === 'on-drag') {
481-
Keyboard.dismiss();
490+
call(
491+
[this.isSwiping, this.indexAtSwipeEnd, this.index],
492+
([isSwiping, indexAtSwipeEnd, currentIndex]: readonly number[]) => {
493+
const { keyboardDismissMode, onSwipeStart, onSwipeEnd } = this.props;
494+
495+
if (isSwiping === TRUE) {
496+
onSwipeStart && onSwipeStart();
497+
498+
if (keyboardDismissMode === 'auto') {
499+
const input = TextInput.State.currentlyFocusedField();
500+
501+
// When a gesture begins, blur the currently focused input
502+
TextInput.State.blurTextInput(input);
503+
504+
// Store the id of this input so we can refocus it if gesture was cancelled
505+
this.previouslyFocusedTextInput = input;
506+
} else if (keyboardDismissMode === 'on-drag') {
507+
Keyboard.dismiss();
508+
}
509+
} else {
510+
onSwipeEnd && onSwipeEnd();
511+
512+
if (keyboardDismissMode === 'auto') {
513+
if (indexAtSwipeEnd === currentIndex) {
514+
// The index didn't change, we should restore the focus of text input
515+
const input = this.previouslyFocusedTextInput;
516+
517+
if (input) {
518+
TextInput.State.focusTextInput(input);
519+
}
520+
}
521+
522+
this.previouslyFocusedTextInput = null;
523+
}
482524
}
483-
} else {
484-
onSwipeEnd && onSwipeEnd();
485525
}
486-
})
526+
)
487527
),
488528
onChange(
489529
this.nextIndex,
@@ -518,6 +558,7 @@ export default class Pager<T extends Route> extends React.Component<Props<T>> {
518558
],
519559
[
520560
set(this.isSwiping, FALSE),
561+
set(this.indexAtSwipeEnd, this.index),
521562
this.transitionTo(
522563
cond(
523564
and(

src/TabView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default class TabView<T extends Route> extends React.Component<
5858
<TabBar {...props} />
5959
),
6060
renderLazyPlaceholder: () => null,
61-
keyboardDismissMode: 'on-drag',
61+
keyboardDismissMode: 'auto',
6262
swipeEnabled: true,
6363
lazy: false,
6464
lazyPreloadDistance: 0,

src/types.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export type EventEmitterProps = {
3737
};
3838

3939
export type PagerCommonProps = {
40-
keyboardDismissMode: 'none' | 'on-drag';
40+
keyboardDismissMode: 'none' | 'on-drag' | 'auto';
4141
swipeEnabled: boolean;
4242
swipeVelocityImpact?: number;
4343
onSwipeStart?: () => void;

0 commit comments

Comments
 (0)