diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx --- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/index.tsx 2020-10-26 16:08:35.000000000 +0100 @@ -3,11 +3,6 @@ import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs'; import * as TransitionPresets from './TransitionConfigs/TransitionPresets'; -/** - * Navigators - */ -export { default as createStackNavigator } from './navigators/createStackNavigator'; - export const Assets = [ // eslint-disable-next-line import/no-commonjs require('./views/assets/back-icon.png'), @@ -49,9 +44,10 @@ * Types */ export type { - StackNavigationOptions, - StackNavigationProp, - StackScreenProps, + NavigationStackState, + StackNavigationProp as NavigationStackProp, + StackNavigationOptions as NavigationStackOptions, + StackNavigationConfig as NavigationStackConfig, StackHeaderProps, StackHeaderLeftButtonProps, StackHeaderTitleProps, diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx --- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100 @@ -1,101 +0,0 @@ -import * as React from 'react'; -import { Platform } from 'react-native'; -import { - useNavigationBuilder, - createNavigatorFactory, - DefaultNavigatorOptions, - EventArg, - StackRouter, - StackRouterOptions, - StackNavigationState, - StackActions, - ParamListBase, - StackActionHelpers, -} from '@react-navigation/native'; -import StackView from '../views/Stack/StackView'; -import type { - StackNavigationConfig, - StackNavigationOptions, - StackNavigationEventMap, -} from '../types'; - -type Props = DefaultNavigatorOptions & - StackRouterOptions & - StackNavigationConfig; - -function StackNavigator({ - initialRouteName, - children, - screenOptions, - ...rest -}: Props) { - const defaultOptions = { - gestureEnabled: Platform.OS === 'ios', - animationEnabled: - Platform.OS !== 'web' && - Platform.OS !== 'windows' && - Platform.OS !== 'macos', - }; - - const { state, descriptors, navigation } = useNavigationBuilder< - StackNavigationState, - StackRouterOptions, - StackActionHelpers, - StackNavigationOptions, - StackNavigationEventMap - >(StackRouter, { - initialRouteName, - children, - screenOptions: - typeof screenOptions === 'function' - ? (...args) => ({ - ...defaultOptions, - ...screenOptions(...args), - }) - : { - ...defaultOptions, - ...screenOptions, - }, - }); - - React.useEffect( - () => - navigation.addListener?.('tabPress', (e) => { - const isFocused = navigation.isFocused(); - - // Run the operation in the next frame so we're sure all listeners have been run - // This is necessary to know if preventDefault() has been called - requestAnimationFrame(() => { - if ( - state.index > 0 && - isFocused && - !(e as EventArg<'tabPress', true>).defaultPrevented - ) { - // When user taps on already focused tab and we're inside the tab, - // reset the stack to replicate native behaviour - navigation.dispatch({ - ...StackActions.popToTop(), - target: state.key, - }); - } - }); - }), - [navigation, state.index, state.key] - ); - - return ( - - ); -} - -export default createNavigatorFactory< - StackNavigationState, - StackNavigationOptions, - StackNavigationEventMap, - typeof StackNavigator ->(StackNavigator); diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx --- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/types.tsx 2020-10-26 16:13:50.000000000 +0100 @@ -8,15 +8,29 @@ } from 'react-native'; import type { EdgeInsets } from 'react-native-safe-area-context'; import type { + NavigationRoute, + NavigationState, + NavigationScreenProp, NavigationProp, - ParamListBase, - Descriptor, - Route, - NavigationHelpers, - StackNavigationState, - StackActionHelpers, - RouteProp, -} from '@react-navigation/native'; + NavigationParams, + NavigationNavigateAction, + NavigationAction, + NavigationEventCallback, + NavigationEventSubscription, + NavigationDescriptor, +} from 'react-navigation'; + +// @ts-ignore +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type Route = NavigationRoute; + +export type NavigationStackState = NavigationState; + +export type NavigationStackEventName = + | 'willFocus' + | 'didFocus' + | 'willBlur' + | 'didBlur'; export type StackNavigationEventMap = { /** @@ -41,30 +55,29 @@ gestureCancel: { data: undefined }; }; -export type StackNavigationHelpers = NavigationHelpers< - ParamListBase, - StackNavigationEventMap -> & - StackActionHelpers; +export type StackNavigationHelpers = NavigationProp; export type StackNavigationProp< - ParamList extends ParamListBase, - RouteName extends keyof ParamList = string -> = NavigationProp< - ParamList, - RouteName, - StackNavigationState, - StackNavigationOptions, - StackNavigationEventMap -> & - StackActionHelpers; - -export type StackScreenProps< - ParamList extends ParamListBase, - RouteName extends keyof ParamList = string -> = { - navigation: StackNavigationProp; - route: RouteProp; + State = NavigationRoute, + Params = NavigationParams +> = NavigationScreenProp & { + push: ( + routeName: string, + params?: NavigationParams, + action?: NavigationNavigateAction + ) => boolean; + replace: ( + routeName: string, + params?: NavigationParams, + action?: NavigationNavigateAction + ) => boolean; + reset: (actions: NavigationAction[], index: number) => boolean; + pop: (n?: number, params?: { immediate?: boolean }) => boolean; + popToTop: (params?: { immediate?: boolean }) => boolean; + addListener: ( + event: NavigationStackEventName, + callback: NavigationEventCallback + ) => NavigationEventSubscription; }; export type Layout = { width: number; height: number }; @@ -241,24 +254,27 @@ /** * Navigation prop for the header. */ - navigation: StackNavigationProp; + navigation: StackNavigationProp; /** * Interpolated styles for various elements in the header. */ styleInterpolator: StackHeaderStyleInterpolator; }; -export type StackDescriptor = Descriptor< - ParamListBase, - string, - StackNavigationState, - StackNavigationOptions +export type StackDescriptor = NavigationDescriptor< + NavigationParams, + StackNavigationOptions, + StackNavigationProp >; export type StackDescriptorMap = { [key: string]: StackDescriptor; }; +export type TransitionCallbackProps = { + closing: boolean; +}; + export type StackNavigationOptions = StackHeaderOptions & Partial & { /** @@ -352,6 +368,8 @@ * Defaults to `false` for the last screen when mode='modal', otherwise `true`. */ detachPreviousScreen?: boolean; + onTransitionStart?: (props: TransitionCallbackProps) => void; + onTransitionEnd?: (props: TransitionCallbackProps) => void; }; export type StackNavigationConfig = { @@ -365,7 +383,7 @@ /** * 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`. + * Defaults to `true` on Android, `false` on iOS. */ detachInactiveScreens?: boolean; }; diff -Naur ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx src/vendor/utils/PreviousSceneContext.tsx --- ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/utils/PreviousSceneContext.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -1,6 +1,5 @@ import * as React from 'react'; -import type { Route } from '@react-navigation/native'; -import type { Scene } from '../types'; +import type { Route, Scene } from '../types'; const PreviousSceneContext = React.createContext< Scene> | undefined diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/Header.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -1,12 +1,15 @@ import * as React from 'react'; -import { StackActions } from '@react-navigation/native'; +import { Dimensions } from 'react-native'; +import { StackActions } from 'react-navigation'; +import { getStatusBarHeight } from 'react-native-iphone-x-helper'; + +import HeaderSegment, { getDefaultHeaderHeight } from './HeaderSegment'; -import HeaderSegment from './HeaderSegment'; import HeaderTitle from './HeaderTitle'; import debounce from '../../utils/debounce'; import type { StackHeaderProps, StackHeaderTitleProps } from '../../types'; -export default React.memo(function Header(props: StackHeaderProps) { +const Header = React.memo(function Header(props: StackHeaderProps) { const { scene, previous, @@ -22,7 +25,7 @@ ? options.headerTitle : options.title !== undefined ? options.title - : scene.route.name; + : scene.route.routeName; let leftLabel; @@ -38,17 +41,20 @@ ? o.headerTitle : o.title !== undefined ? o.title - : previous.route.name; + : previous.route.routeName; } // eslint-disable-next-line react-hooks/exhaustive-deps const goBack = React.useCallback( debounce(() => { - if (navigation.isFocused() && navigation.canGoBack()) { - navigation.dispatch({ - ...StackActions.pop(), - source: scene.route.key, - }); + const key = navigation.isFirstRouteInParent() + ? // If we're the first route, we're going back to a parent navigator + // So we need to get the key of the route we're nested in + navigation.dangerouslyGetParent()?.state.key + : scene.route.key; + + if (key !== undefined) { + navigation.dispatch(StackActions.pop({ key })); } }, 50), [navigation, scene.route.key] @@ -64,7 +70,10 @@ leftLabel={leftLabel} headerTitle={ typeof options.headerTitle !== 'function' - ? (props: StackHeaderTitleProps) => + ? (props: StackHeaderTitleProps) => { + // @ts-ignore + return ; + } : options.headerTitle } onGoBack={previous ? goBack : undefined} @@ -72,3 +81,18 @@ /> ); }); + +Object.defineProperty(Header, 'HEIGHT', { + get() { + console.warn( + "Deprecation in 'createStackNavigator': 'Header.HEIGHT' will be removed in a future version. Use 'useHeaderHeight' or 'HeaderHeightContext' instead" + ); + + return getDefaultHeaderHeight( + Dimensions.get('window'), + getStatusBarHeight(true) + ); + }, +}); + +export default Header; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/HeaderBackButton.tsx 2020-10-26 16:14:24.000000000 +0100 @@ -8,9 +8,9 @@ StyleSheet, LayoutChangeEvent, } from 'react-native'; -import { useTheme } from '@react-navigation/native'; import MaskedView from '../MaskedView'; import TouchableItem from '../TouchableItem'; +import useTheme from '../../../utils/useTheme'; import type { StackHeaderLeftButtonProps } from '../../types'; type Props = StackHeaderLeftButtonProps; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/HeaderBackground.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -7,7 +7,7 @@ StyleProp, ViewStyle, } from 'react-native'; -import { useTheme } from '@react-navigation/native'; +import useTheme from '../../../utils/useTheme'; type Props = ViewProps & { style?: Animated.WithAnimatedValue>; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/HeaderContainer.tsx 2020-10-26 16:15:18.000000000 +0100 @@ -1,11 +1,6 @@ import * as React from 'react'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; -import { - NavigationContext, - NavigationRouteContext, - Route, - ParamListBase, -} from '@react-navigation/native'; +import { NavigationContext } from 'react-navigation'; import type { EdgeInsets } from 'react-native-safe-area-context'; import Header from './Header'; @@ -19,6 +14,7 @@ import PreviousSceneContext from '../../utils/PreviousSceneContext'; import type { Layout, + Route, Scene, StackHeaderStyleInterpolator, StackNavigationProp, @@ -105,9 +101,7 @@ insets, scene, previous, - navigation: scene.descriptor.navigation as StackNavigationProp< - ParamListBase - >, + navigation: scene.descriptor.navigation as StackNavigationProp, styleInterpolator: mode === 'float' ? isHeaderStatic @@ -126,7 +120,7 @@ key={scene.route.key} value={scene.descriptor.navigation} > - + <> {header !== undefined ? header(props) :
} - + ); })} diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/HeaderSegment.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -8,7 +8,7 @@ ViewStyle, } from 'react-native'; import type { EdgeInsets } from 'react-native-safe-area-context'; -import type { Route } from '@react-navigation/native'; +import type { NavigationRoute } from 'react-navigation'; import HeaderBackButton from './HeaderBackButton'; import HeaderBackground from './HeaderBackground'; import HeaderShownContext from '../../utils/HeaderShownContext'; @@ -29,7 +29,7 @@ onGoBack?: () => void; title?: string; leftLabel?: string; - scene: Scene>; + scene: Scene; styleInterpolator: StackHeaderStyleInterpolator; }; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Header/HeaderTitle.tsx 2020-10-26 16:14:52.000000000 +0100 @@ -7,7 +7,7 @@ StyleProp, TextStyle, } from 'react-native'; -import { useTheme } from '@react-navigation/native'; +import useTheme from '../../../utils/useTheme'; type Props = Omit & { tintColor?: string; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx --- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Stack/Card.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -162,7 +162,7 @@ private interactionHandle: number | undefined; - private pendingGestureCallback: number | undefined; + private pendingGestureCallback: any; private lastToValue: number | undefined; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Stack/CardContainer.tsx 2020-10-26 16:08:40.000000000 +0100 @@ -1,12 +1,13 @@ import * as React from 'react'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; -import { Route, useTheme } from '@react-navigation/native'; import type { Props as HeaderContainerProps } from '../Header/HeaderContainer'; import Card from './Card'; import HeaderHeightContext from '../../utils/HeaderHeightContext'; import HeaderShownContext from '../../utils/HeaderShownContext'; import PreviousSceneContext from '../../utils/PreviousSceneContext'; +import useTheme from '../../../utils/useTheme'; import type { + Route, Scene, Layout, StackHeaderMode, diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Stack/CardStack.tsx 2020-10-26 16:17:14.000000000 +0100 @@ -7,11 +7,7 @@ Platform, } from 'react-native'; import type { EdgeInsets } from 'react-native-safe-area-context'; -import type { - ParamListBase, - Route, - StackNavigationState, -} from '@react-navigation/native'; +import type { NavigationState as StackNavigationState } from 'react-navigation'; import { MaybeScreenContainer, @@ -32,6 +28,7 @@ Layout, StackHeaderMode, StackCardMode, + Route, Scene, StackDescriptorMap, StackNavigationOptions, @@ -45,7 +42,7 @@ type Props = { mode: StackCardMode; insets: EdgeInsets; - state: StackNavigationState; + state: StackNavigationState; descriptors: StackDescriptorMap; routes: Route[]; openingRouteKeys: string[]; diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx --- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-10-26 16:07:00.000000000 +0100 +++ src/vendor/views/Stack/StackView.tsx 2020-10-26 16:20:15.000000000 +0100 @@ -2,12 +2,11 @@ import { View, Platform, StyleSheet } from 'react-native'; import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context'; import { - NavigationHelpersContext, StackActions, - StackNavigationState, - Route, - ParamListBase, -} from '@react-navigation/native'; + NavigationState as StackNavigationState, + NavigationActions, + SceneView, +} from 'react-navigation'; import { GestureHandlerRootView } from '../GestureHandler'; import CardStack from './CardStack'; @@ -17,6 +16,7 @@ } from '../Header/HeaderContainer'; import SafeAreaProviderCompat from '../SafeAreaProviderCompat'; import type { + Route, StackNavigationHelpers, StackNavigationConfig, StackDescriptorMap, @@ -24,9 +24,10 @@ import HeaderShownContext from '../../utils/HeaderShownContext'; type Props = StackNavigationConfig & { - state: StackNavigationState; + state: StackNavigationState; navigation: StackNavigationHelpers; descriptors: StackDescriptorMap; + screenProps: unknown; }; type State = { @@ -295,7 +296,9 @@ return false; } - return gestureEnabled !== false; + return gestureEnabled !== undefined + ? gestureEnabled + : Platform.OS !== 'android'; } return false; @@ -323,26 +326,49 @@ return null; } - return descriptor.render(); + const { navigation, getComponent } = descriptor; + const SceneComponent = getComponent(); + + return ( + + ); }; private renderHeader = (props: HeaderContainerProps) => { return ; }; + private handleTransitionComplete = () => { + const { state, navigation } = this.props; + + if (state.isTransitioning) { + navigation.dispatch( + StackActions.completeTransition({ + key: navigation.state.key, + toChildKey: state.routes[state.index].key, + }) + ); + } + }; + private handleOpenRoute = ({ route }: { route: Route }) => { const { state, navigation } = this.props; const { closingRouteKeys, replacingRouteKeys } = this.state; + this.handleTransitionComplete(); + if ( closingRouteKeys.some((key) => key === route.key) && replacingRouteKeys.every((key) => key !== route.key) && - state.routeNames.includes(route.name) && !state.routes.some((r) => r.key === route.key) ) { // If route isn't present in current state, but was closing, assume that a close animation was cancelled // So we need to add this route back to the state - navigation.navigate(route); + navigation.dispatch(NavigationActions.navigate(route)); } else { this.setState((state) => ({ routes: state.replacingRouteKeys.length @@ -368,12 +394,11 @@ // If a route exists in state, trigger a pop // This will happen in when the route was closed from the card component // e.g. When the close animation triggered from a gesture ends - navigation.dispatch({ - ...StackActions.pop(), - source: route.key, - target: state.key, - }); + // @ts-ignore + navigation.dispatch(StackActions.pop({ key: route.key, prune: false })); } else { + this.handleTransitionComplete(); + // We need to clean up any state tracking the route and pop it immediately this.setState((state) => ({ routes: state.routes.filter((r) => r.key !== route.key), @@ -390,47 +415,41 @@ private handleTransitionStart = ( { route }: { route: Route }, closing: boolean - ) => - this.props.navigation.emit({ - type: 'transitionStart', - data: { closing }, - target: route.key, - }); + ) => { + const { descriptors } = this.props; + const descriptor = + descriptors[route.key] || this.state.descriptors[route.key]; + + descriptor?.options.onTransitionStart?.({ closing }); + }; private handleTransitionEnd = ( { route }: { route: Route }, closing: boolean - ) => - this.props.navigation.emit({ - type: 'transitionEnd', - data: { closing }, - target: route.key, - }); - - private handleGestureStart = ({ route }: { route: Route }) => { - this.props.navigation.emit({ - type: 'gestureStart', - target: route.key, - }); - }; - - private handleGestureEnd = ({ route }: { route: Route }) => { - this.props.navigation.emit({ - type: 'gestureEnd', - target: route.key, - }); - }; - - private handleGestureCancel = ({ route }: { route: Route }) => { - this.props.navigation.emit({ - type: 'gestureCancel', - target: route.key, - }); + ) => { + const { descriptors } = this.props; + const descriptor = + descriptors[route.key] || this.state.descriptors[route.key]; + + descriptor?.options.onTransitionStart?.({ closing }); + }; + + private handleGestureStart = () => { + // Do nothing + }; + + private handleGestureEnd = () => { + // Do nothing + }; + + private handleGestureCancel = () => { + // Do nothing }; render() { const { state, + // eslint-disable-next-line @typescript-eslint/no-unused-vars navigation, keyboardHandlingEnabled, mode = 'card', @@ -450,7 +469,7 @@ } = this.state; return ( - + <> @@ -491,7 +510,7 @@ - + ); } }