diff --git a/packages/stack/package.json b/packages/stack/package.json index 2ef6a661..7ada3171 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@react-native-community/bob": "^0.16.2", "@react-native-community/masked-view": "0.1.10", - "@react-navigation/stack": "^5.12.6", + "@react-navigation/stack": "^5.12.8", "@types/color": "^3.0.1", "@types/react": "^16.9.53", "@types/react-native": "^0.63.30", diff --git a/packages/stack/scripts/stack.patch b/packages/stack/scripts/stack.patch index 219b4962..64569572 100644 Binary files a/packages/stack/scripts/stack.patch and b/packages/stack/scripts/stack.patch differ diff --git a/packages/stack/src/navigators/__tests__/__snapshots__/NestedNavigator.test.tsx.snap b/packages/stack/src/navigators/__tests__/__snapshots__/NestedNavigator.test.tsx.snap index 87cced32..096456c0 100644 --- a/packages/stack/src/navigators/__tests__/__snapshots__/NestedNavigator.test.tsx.snap +++ b/packages/stack/src/navigators/__tests__/__snapshots__/NestedNavigator.test.tsx.snap @@ -152,7 +152,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = ` onGestureCanceled={[Function]} onGestureEnd={[Function]} onOpen={[Function]} - onTransitionStart={[Function]} + onTransition={[Function]} pointerEvents="box-none" style={ Array [ @@ -432,7 +432,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = ` onGestureCanceled={[Function]} onGestureEnd={[Function]} onOpen={[Function]} - onTransitionStart={[Function]} + onTransition={[Function]} pointerEvents="box-none" style={ Array [ diff --git a/packages/stack/src/navigators/__tests__/__snapshots__/StackNavigator.test.tsx.snap b/packages/stack/src/navigators/__tests__/__snapshots__/StackNavigator.test.tsx.snap index 79eee638..2fd21776 100644 --- a/packages/stack/src/navigators/__tests__/__snapshots__/StackNavigator.test.tsx.snap +++ b/packages/stack/src/navigators/__tests__/__snapshots__/StackNavigator.test.tsx.snap @@ -169,7 +169,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] = onGestureCanceled={[Function]} onGestureEnd={[Function]} onOpen={[Function]} - onTransitionStart={[Function]} + onTransition={[Function]} pointerEvents="box-none" style={ Array [ @@ -462,7 +462,7 @@ exports[`StackNavigator renders successfully 1`] = ` onGestureCanceled={[Function]} onGestureEnd={[Function]} onOpen={[Function]} - onTransitionStart={[Function]} + onTransition={[Function]} pointerEvents="box-none" style={ Array [ diff --git a/packages/stack/src/vendor/views/KeyboardManager.tsx b/packages/stack/src/vendor/views/KeyboardManager.tsx index 0f96ef92..60a4d451 100644 --- a/packages/stack/src/vendor/views/KeyboardManager.tsx +++ b/packages/stack/src/vendor/views/KeyboardManager.tsx @@ -1,15 +1,17 @@ import * as React from 'react'; -import { TextInput, Platform, Keyboard } from 'react-native'; +import { TextInput, Keyboard, HostComponent } from 'react-native'; type Props = { enabled: boolean; children: (props: { onPageChangeStart: () => void; - onPageChangeConfirm: () => void; + onPageChangeConfirm: (force: boolean) => void; onPageChangeCancel: () => void; }) => React.ReactNode; }; +type InputRef = React.ElementRef> | undefined; + export default class KeyboardManager extends React.Component { componentWillUnmount() { this.clearKeyboardTimeout(); @@ -17,7 +19,7 @@ export default class KeyboardManager extends React.Component { // 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 previouslyFocusedTextInput: InputRef = undefined; private startTimestamp: number = 0; private keyboardTimeout: any; @@ -35,7 +37,8 @@ export default class KeyboardManager extends React.Component { this.clearKeyboardTimeout(); - const input: any = TextInput.State.currentlyFocusedInput + // @ts-expect-error: blurTextInput accepts both number and ref, but types say only ref + const input: InputRef = TextInput.State.currentlyFocusedInput ? TextInput.State.currentlyFocusedInput() : TextInput.State.currentlyFocusedField(); @@ -49,25 +52,30 @@ export default class KeyboardManager extends React.Component { this.startTimestamp = Date.now(); }; - private handlePageChangeConfirm = () => { + private handlePageChangeConfirm = (force: boolean) => { if (!this.props.enabled) { return; } this.clearKeyboardTimeout(); - const input = this.previouslyFocusedTextInput; + 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 = this.previouslyFocusedTextInput; - if (input) { - if (Platform.OS === 'android') { - Keyboard.dismiss(); - } else { + if (input) { + // 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 TextInput.State.blurTextInput(input); } } // Cleanup the ID on successful page change - this.previouslyFocusedTextInput = null; + this.previouslyFocusedTextInput = undefined; }; private handlePageChangeCancel = () => { @@ -91,11 +99,11 @@ export default class KeyboardManager extends React.Component { if (Date.now() - this.startTimestamp < 100) { this.keyboardTimeout = setTimeout(() => { TextInput.State.focusTextInput(input); - this.previouslyFocusedTextInput = null; + this.previouslyFocusedTextInput = undefined; }, 100); } else { TextInput.State.focusTextInput(input); - this.previouslyFocusedTextInput = null; + this.previouslyFocusedTextInput = undefined; } } }; diff --git a/packages/stack/src/vendor/views/Stack/Card.tsx b/packages/stack/src/vendor/views/Stack/Card.tsx index 4c0a4031..6848f4be 100755 --- a/packages/stack/src/vendor/views/Stack/Card.tsx +++ b/packages/stack/src/vendor/views/Stack/Card.tsx @@ -41,7 +41,7 @@ type Props = ViewProps & { gestureDirection: GestureDirection; onOpen: () => void; onClose: () => void; - onTransitionStart?: (props: { closing: boolean }) => void; + onTransition?: (props: { closing: boolean; gesture: boolean }) => void; onGestureBegin?: () => void; onGestureCanceled?: () => void; onGestureEnd?: () => void; @@ -178,7 +178,7 @@ export default class Card extends React.Component { transitionSpec, onOpen, onClose, - onTransitionStart, + onTransition, } = this.props; const toValue = this.getAnimateToValue({ @@ -198,7 +198,7 @@ export default class Card extends React.Component { clearTimeout(this.pendingGestureCallback); - onTransitionStart?.({ closing }); + onTransition?.({ closing, gesture: velocity !== undefined }); animation(gesture, { ...spec.config, velocity, diff --git a/packages/stack/src/vendor/views/Stack/CardContainer.tsx b/packages/stack/src/vendor/views/Stack/CardContainer.tsx index 8790e8ef..7dcea3c1 100644 --- a/packages/stack/src/vendor/views/Stack/CardContainer.tsx +++ b/packages/stack/src/vendor/views/Stack/CardContainer.tsx @@ -47,7 +47,7 @@ type Props = TransitionPreset & { ) => void; onTransitionEnd?: (props: { route: Route }, closing: boolean) => void; onPageChangeStart?: () => void; - onPageChangeConfirm?: () => void; + onPageChangeConfirm?: (force: boolean) => void; onPageChangeCancel?: () => void; onGestureStart?: (props: { route: Route }) => void; onGestureEnd?: (props: { route: Route }) => void; @@ -117,42 +117,58 @@ function CardContainer({ scene, transitionSpec, }: Props) { - React.useEffect(() => { - onPageChangeConfirm?.(); - }, [active, onPageChangeConfirm]); - const handleOpen = () => { - onTransitionEnd?.({ route: scene.route }, false); - onOpenRoute({ route: scene.route }); + const { route } = scene; + + onTransitionEnd?.({ route }, false); + onOpenRoute({ route }); }; const handleClose = () => { - onTransitionEnd?.({ route: scene.route }, true); - onCloseRoute({ route: scene.route }); + const { route } = scene; + + onTransitionEnd?.({ route }, true); + onCloseRoute({ route }); }; const handleGestureBegin = () => { + const { route } = scene; + onPageChangeStart?.(); - onGestureStart?.({ route: scene.route }); + onGestureStart?.({ route }); }; const handleGestureCanceled = () => { + const { route } = scene; + onPageChangeCancel?.(); - onGestureCancel?.({ route: scene.route }); + onGestureCancel?.({ route }); }; const handleGestureEnd = () => { - onGestureEnd?.({ route: scene.route }); + const { route } = scene; + + onGestureEnd?.({ route }); }; - const handleTransitionStart = ({ closing }: { closing: boolean }) => { - if (active && closing) { - onPageChangeConfirm?.(); + const handleTransition = ({ + closing, + gesture, + }: { + closing: boolean; + gesture: boolean; + }) => { + const { route } = scene; + + if (!gesture) { + onPageChangeConfirm?.(true); + } else if (active && closing) { + onPageChangeConfirm?.(false); } else { onPageChangeCancel?.(); } - onTransitionStart?.({ route: scene.route }, closing); + onTransitionStart?.({ route }, closing); }; const insets = { @@ -202,7 +218,7 @@ function CardContainer({ overlay={cardOverlay} overlayEnabled={cardOverlayEnabled} shadowEnabled={cardShadowEnabled} - onTransitionStart={handleTransitionStart} + onTransition={handleTransition} onGestureBegin={handleGestureBegin} onGestureCanceled={handleGestureCanceled} onGestureEnd={handleGestureEnd} diff --git a/packages/stack/src/vendor/views/Stack/CardStack.tsx b/packages/stack/src/vendor/views/Stack/CardStack.tsx index 1b77ad7a..1d4ce463 100755 --- a/packages/stack/src/vendor/views/Stack/CardStack.tsx +++ b/packages/stack/src/vendor/views/Stack/CardStack.tsx @@ -62,7 +62,7 @@ type Props = { ) => void; onTransitionEnd: (props: { route: Route }, closing: boolean) => void; onPageChangeStart?: () => void; - onPageChangeConfirm?: () => void; + onPageChangeConfirm?: (force: boolean) => void; onPageChangeCancel?: () => void; onGestureStart?: (props: { route: Route }) => void; onGestureEnd?: (props: { route: Route }) => void; diff --git a/yarn.lock b/yarn.lock index ef9c4603..5439aecb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4179,10 +4179,10 @@ resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401" integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ== -"@react-navigation/stack@^5.12.6": - version "5.12.6" - resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-5.12.6.tgz#a6f2caf66da78ad2afa80f7a960c36db6b83bcff" - integrity sha512-pf9AigAIVtCQuCpZAZqBux4kNqQwj98ngvd6JEryFrqTQ1CYsUH6jfpQE7SKyHggVRFSQVMf24aCgwtRixBvjw== +"@react-navigation/stack@^5.12.8": + version "5.12.8" + resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-5.12.8.tgz#31e54e05d8a3ffaaa3e39a1a9b7969f8316a35bf" + integrity sha512-wUJFbU0v606RBXOUxHToCXJNmiwxtFYhN2TFvjxCZ3PJU+OWWx8HTmn99pT3rVH4Ax2cfO5BDUy9v+r74ZrIWw== dependencies: color "^3.1.3" react-native-iphone-x-helper "^1.3.0"