Skip to content

Commit

Permalink
fix: move keyboardHandlingEnabled to screen options
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed May 16, 2021
1 parent dc4ffc0 commit 82900cc
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 167 deletions.
19 changes: 10 additions & 9 deletions packages/stack/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,18 +292,19 @@ export type StackNavigationOptions = StackHeaderOptions &
* Defaults to `false` for the last screen for modals, otherwise `true`.
*/
detachPreviousScreen?: boolean;
/**
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen from this screen.
* Defaults to `true`.
*/
keyboardHandlingEnabled?: boolean;
};

export type StackNavigationConfig = {
/**
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
* Defaults to `true`.
*/
keyboardHandlingEnabled?: boolean;
/**
* Whether inactive screens should be detached from the view hierarchy to save memory.
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
* Defaults to `true` on Android, depends on the version of `react-native-screens` on iOS.
* This will have no effect if you disable `react-native-screens`.
*
* Defaults to `true`.
*/
detachInactiveScreens?: boolean;
};
Expand Down Expand Up @@ -335,7 +336,7 @@ export type StackCardInterpolationProps = {
progress: Animated.AnimatedInterpolation;
};
/**
* Values for the current screen the screen after this one in the stack.
* Values for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: {
Expand Down Expand Up @@ -414,7 +415,7 @@ export type StackHeaderInterpolationProps = {
progress: Animated.AnimatedInterpolation;
};
/**
* Values for the current screen the screen after this one in the stack.
* Values for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: {
Expand Down
105 changes: 105 additions & 0 deletions packages/stack/src/utils/useKeyboardManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as React from 'react';
import { TextInput, Keyboard, HostComponent } from 'react-native';

type InputRef = React.ElementRef<HostComponent<unknown>> | undefined;

export default function useKeyboardManager(isEnabled: () => boolean) {
// Numeric id of the previously focused text input
// When a gesture didn't change the tab, we can restore the focused input with this
const previouslyFocusedTextInputRef = React.useRef<InputRef>(undefined);
const startTimestampRef = React.useRef<number>(0);
const keyboardTimeoutRef = React.useRef<any>();

const clearKeyboardTimeout = React.useCallback(() => {
if (keyboardTimeoutRef.current !== undefined) {
clearTimeout(keyboardTimeoutRef.current);
keyboardTimeoutRef.current = undefined;
}
}, []);

const onPageChangeStart = React.useCallback(() => {
if (!isEnabled()) {
return;
}

clearKeyboardTimeout();

const input: InputRef = TextInput.State.currentlyFocusedInput();

// When a page change begins, blur the currently focused input
input?.blur();

// Store the id of this input so we can refocus it if change was cancelled
previouslyFocusedTextInputRef.current = input;

// Store timestamp for touch start
startTimestampRef.current = Date.now();
}, [clearKeyboardTimeout, isEnabled]);

const onPageChangeConfirm = React.useCallback(
(force: boolean) => {
if (!isEnabled()) {
return;
}

clearKeyboardTimeout();

if (force) {
// Always dismiss input, even if we don't have a ref to it
// We might not have the ref if onPageChangeStart was never called
// This can happen if page change was not from a gesture
Keyboard.dismiss();
} else {
const input = previouslyFocusedTextInputRef.current;

// Dismiss the keyboard only if an input was a focused before
// This makes sure we don't dismiss input on going back and focusing an input
input?.blur();
}

// Cleanup the ID on successful page change
previouslyFocusedTextInputRef.current = undefined;
},
[clearKeyboardTimeout, isEnabled]
);

const onPageChangeCancel = React.useCallback(() => {
if (!isEnabled()) {
return;
}

clearKeyboardTimeout();

// The page didn't change, we should restore the focus of text input
const input = previouslyFocusedTextInputRef.current;

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() - startTimestampRef.current < 100) {
keyboardTimeoutRef.current = setTimeout(() => {
input?.focus();
previouslyFocusedTextInputRef.current = undefined;
}, 100);
} else {
input?.focus();
previouslyFocusedTextInputRef.current = undefined;
}
}
}, [clearKeyboardTimeout, isEnabled]);

React.useEffect(() => {
return () => clearKeyboardTimeout();
}, [clearKeyboardTimeout]);

return {
onPageChangeStart,
onPageChangeConfirm,
onPageChangeCancel,
};
}
113 changes: 0 additions & 113 deletions packages/stack/src/views/KeyboardManager.tsx

This file was deleted.

21 changes: 15 additions & 6 deletions packages/stack/src/views/Stack/CardContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card';
import { forModalPresentationIOS } from '../../TransitionConfigs/CardStyleInterpolators';
import ModalPresentationContext from '../../utils/ModalPresentationContext';
import useKeyboardManager from '../../utils/useKeyboardManager';
import type { Layout, Scene } from '../../types';

type Props = {
Expand All @@ -37,9 +38,6 @@ type Props = {
closing: boolean
) => void;
onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void;
onPageChangeStart?: () => void;
onPageChangeConfirm?: (force: boolean) => void;
onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
Expand Down Expand Up @@ -70,9 +68,6 @@ function CardContainer({
layout,
onCloseRoute,
onOpenRoute,
onPageChangeCancel,
onPageChangeConfirm,
onPageChangeStart,
onGestureCancel,
onGestureEnd,
onGestureStart,
Expand All @@ -88,6 +83,20 @@ function CardContainer({
}: Props) {
const parentHeaderHeight = React.useContext(HeaderHeightContext);

const {
onPageChangeStart,
onPageChangeCancel,
onPageChangeConfirm,
} = useKeyboardManager(
React.useCallback(() => {
const { options, navigation } = scene.descriptor;

return (
navigation.isFocused() && options.keyboardHandlingEnabled !== false
);
}, [scene.descriptor])
);

const handleOpen = () => {
const { route } = scene.descriptor;

Expand Down
9 changes: 0 additions & 9 deletions packages/stack/src/views/Stack/CardStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ type Props = {
closing: boolean
) => void;
onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
onPageChangeStart?: () => void;
onPageChangeConfirm?: (force: boolean) => void;
onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
Expand Down Expand Up @@ -430,9 +427,6 @@ export default class CardStack extends React.Component<Props, State> {
isParentHeaderShown,
onTransitionStart,
onTransitionEnd,
onPageChangeStart,
onPageChangeConfirm,
onPageChangeCancel,
onGestureStart,
onGestureEnd,
onGestureCancel,
Expand Down Expand Up @@ -603,9 +597,6 @@ export default class CardStack extends React.Component<Props, State> {
safeAreaInsetRight={safeAreaInsetRight}
safeAreaInsetBottom={safeAreaInsetBottom}
safeAreaInsetLeft={safeAreaInsetLeft}
onPageChangeStart={onPageChangeStart}
onPageChangeConfirm={onPageChangeConfirm}
onPageChangeCancel={onPageChangeCancel}
onGestureStart={onGestureStart}
onGestureCancel={onGestureCancel}
onGestureEnd={onGestureEnd}
Expand Down

0 comments on commit 82900cc

Please sign in to comment.