mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-28 22:31:33 +08:00
1004 lines
34 KiB
Diff
1004 lines
34 KiB
Diff
diff -ruN node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx
|
|
--- node_modules/@react-navigation/stack/src/index.tsx 2020-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/index.tsx 1970-01-01 01:00:00.000000000 +0100
|
|
@@ -1,57 +0,0 @@
|
|
-import * as CardStyleInterpolators from './TransitionConfigs/CardStyleInterpolators';
|
|
-import * as HeaderStyleInterpolators from './TransitionConfigs/HeaderStyleInterpolators';
|
|
-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'),
|
|
- // eslint-disable-next-line import/no-commonjs
|
|
- require('./views/assets/back-icon-mask.png'),
|
|
-];
|
|
-
|
|
-/**
|
|
- * Views
|
|
- */
|
|
-export { default as StackView } from './views/Stack/StackView';
|
|
-export { default as Header } from './views/Header/Header';
|
|
-export { default as HeaderTitle } from './views/Header/HeaderTitle';
|
|
-export { default as HeaderBackButton } from './views/Header/HeaderBackButton';
|
|
-
|
|
-/**
|
|
- * Transition presets
|
|
- */
|
|
-export {
|
|
- CardStyleInterpolators,
|
|
- HeaderStyleInterpolators,
|
|
- TransitionSpecs,
|
|
- TransitionPresets,
|
|
-};
|
|
-
|
|
-/**
|
|
- * Utilities
|
|
- */
|
|
-export { default as StackGestureContext } from './utils/StackGestureContext';
|
|
-export { default as StackCardAnimationContext } from './utils/StackCardAnimationContext';
|
|
-
|
|
-/**
|
|
- * Types
|
|
- */
|
|
-export {
|
|
- StackNavigationOptions,
|
|
- StackNavigationProp,
|
|
- StackHeaderProps,
|
|
- StackHeaderLeftButtonProps,
|
|
- StackHeaderTitleProps,
|
|
- StackCardInterpolatedStyle,
|
|
- StackCardInterpolationProps,
|
|
- StackCardStyleInterpolator,
|
|
- StackHeaderInterpolatedStyle,
|
|
- StackHeaderInterpolationProps,
|
|
- StackHeaderStyleInterpolator,
|
|
-} from './types';
|
|
diff -ruN node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx
|
|
--- node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100
|
|
@@ -1,77 +0,0 @@
|
|
-import * as React from 'react';
|
|
-import {
|
|
- useNavigationBuilder,
|
|
- createNavigatorFactory,
|
|
- DefaultNavigatorOptions,
|
|
- EventArg,
|
|
-} from '@react-navigation/native';
|
|
-import {
|
|
- StackRouter,
|
|
- StackRouterOptions,
|
|
- StackNavigationState,
|
|
- StackActions,
|
|
-} from '@react-navigation/routers';
|
|
-import StackView from '../views/Stack/StackView';
|
|
-import {
|
|
- StackNavigationConfig,
|
|
- StackNavigationOptions,
|
|
- StackNavigationEventMap,
|
|
-} from '../types';
|
|
-
|
|
-type Props = DefaultNavigatorOptions<StackNavigationOptions> &
|
|
- StackRouterOptions &
|
|
- StackNavigationConfig;
|
|
-
|
|
-function StackNavigator({
|
|
- initialRouteName,
|
|
- children,
|
|
- screenOptions,
|
|
- ...rest
|
|
-}: Props) {
|
|
- const { state, descriptors, navigation } = useNavigationBuilder<
|
|
- StackNavigationState,
|
|
- StackRouterOptions,
|
|
- StackNavigationOptions,
|
|
- StackNavigationEventMap
|
|
- >(StackRouter, {
|
|
- initialRouteName,
|
|
- children,
|
|
- screenOptions,
|
|
- });
|
|
-
|
|
- React.useEffect(
|
|
- () =>
|
|
- navigation.addListener &&
|
|
- navigation.addListener('tabPress', (e: EventArg<'tabPress'>) => {
|
|
- 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.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 (
|
|
- <StackView
|
|
- {...rest}
|
|
- state={state}
|
|
- descriptors={descriptors}
|
|
- navigation={navigation}
|
|
- />
|
|
- );
|
|
-}
|
|
-
|
|
-export default createNavigatorFactory<
|
|
- StackNavigationOptions,
|
|
- typeof StackNavigator
|
|
->(StackNavigator);
|
|
diff -ruN node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx
|
|
--- node_modules/@react-navigation/stack/src/types.tsx 2020-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/types.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -8,13 +8,28 @@
|
|
} from 'react-native';
|
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
|
import {
|
|
+ NavigationRoute,
|
|
+ NavigationState,
|
|
+ NavigationScreenProp,
|
|
NavigationProp,
|
|
- ParamListBase,
|
|
- Descriptor,
|
|
- Route,
|
|
- NavigationHelpers,
|
|
-} from '@react-navigation/native';
|
|
-import { StackNavigationState } from '@react-navigation/routers';
|
|
+ NavigationParams,
|
|
+ NavigationNavigateAction,
|
|
+ NavigationAction,
|
|
+ NavigationEventCallback,
|
|
+ NavigationEventSubscription,
|
|
+ NavigationDescriptor,
|
|
+} from 'react-navigation';
|
|
+
|
|
+// @ts-ignore
|
|
+export type Route<T extends string> = NavigationRoute;
|
|
+
|
|
+export type NavigationStackState = NavigationState;
|
|
+
|
|
+export type NavigationStackEventName =
|
|
+ | 'willFocus'
|
|
+ | 'didFocus'
|
|
+ | 'willBlur'
|
|
+ | 'didBlur';
|
|
|
|
export type StackNavigationEventMap = {
|
|
/**
|
|
@@ -27,42 +42,29 @@
|
|
transitionEnd: { closing: boolean };
|
|
};
|
|
|
|
-export type StackNavigationHelpers = NavigationHelpers<
|
|
- ParamListBase,
|
|
- StackNavigationEventMap
|
|
->;
|
|
+export type StackNavigationHelpers = NavigationProp<NavigationStackState>
|
|
|
|
export type StackNavigationProp<
|
|
- ParamList extends ParamListBase,
|
|
- RouteName extends keyof ParamList = string
|
|
-> = NavigationProp<
|
|
- ParamList,
|
|
- RouteName,
|
|
- StackNavigationState,
|
|
- StackNavigationOptions,
|
|
- StackNavigationEventMap
|
|
-> & {
|
|
- /**
|
|
- * Push a new screen onto the stack.
|
|
- *
|
|
- * @param name Name of the route for the tab.
|
|
- * @param [params] Params object for the route.
|
|
- */
|
|
- push<RouteName extends keyof ParamList>(
|
|
- ...args: ParamList[RouteName] extends undefined | any
|
|
- ? [RouteName] | [RouteName, ParamList[RouteName]]
|
|
- : [RouteName, ParamList[RouteName]]
|
|
- ): void;
|
|
-
|
|
- /**
|
|
- * Pop a screen from the stack.
|
|
- */
|
|
- pop(count?: number): void;
|
|
-
|
|
- /**
|
|
- * Pop to the first route in the stack, dismissing all other screens.
|
|
- */
|
|
- popToTop(): void;
|
|
+State = NavigationRoute,
|
|
+Params = NavigationParams
|
|
+> = NavigationScreenProp<State, Params> & {
|
|
+ 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 };
|
|
@@ -232,24 +234,27 @@
|
|
/**
|
|
* Navigation prop for the header.
|
|
*/
|
|
- navigation: StackNavigationProp<ParamListBase>;
|
|
+ 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<TransitionPreset> & {
|
|
/**
|
|
@@ -322,6 +327,8 @@
|
|
bottom?: number;
|
|
left?: number;
|
|
};
|
|
+ onTransitionStart?: (props: TransitionCallbackProps) => void;
|
|
+ onTransitionEnd?: (props: TransitionCallbackProps) => void;
|
|
};
|
|
|
|
export type StackNavigationConfig = {
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/Header.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -1,5 +1,5 @@
|
|
import * as React from 'react';
|
|
-import { StackActions } from '@react-navigation/routers';
|
|
+import { StackActions } from 'react-navigation';
|
|
|
|
import HeaderSegment from './HeaderSegment';
|
|
import { StackHeaderProps, StackHeaderTitleProps } from '../../types';
|
|
@@ -21,7 +21,7 @@
|
|
? options.headerTitle
|
|
: options.title !== undefined
|
|
? options.title
|
|
- : scene.route.name;
|
|
+ : scene.route.routeName;
|
|
|
|
let leftLabel;
|
|
|
|
@@ -37,7 +37,7 @@
|
|
? o.headerTitle
|
|
: o.title !== undefined
|
|
? o.title
|
|
- : previous.route.name;
|
|
+ : previous.route.routeName;
|
|
}
|
|
|
|
return (
|
|
@@ -55,11 +55,8 @@
|
|
}
|
|
onGoBack={
|
|
previous
|
|
- ? () =>
|
|
- navigation.dispatch({
|
|
- ...StackActions.pop(),
|
|
- source: scene.route.key,
|
|
- })
|
|
+ // @ts-ignore
|
|
+ ? () => navigation.dispatch(StackActions.pop({ key: scene.route.key }))
|
|
: undefined
|
|
}
|
|
styleInterpolator={styleInterpolator}
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/HeaderBackButton.tsx 2020-01-03 21:46:29.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 { StackHeaderLeftButtonProps } from '../../types';
|
|
|
|
type Props = StackHeaderLeftButtonProps;
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/HeaderBackground.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -1,6 +1,6 @@
|
|
import * as React from 'react';
|
|
import { Animated, StyleSheet, Platform, ViewProps } from 'react-native';
|
|
-import { useTheme } from '@react-navigation/native';
|
|
+import useTheme from '../../../utils/useTheme';
|
|
|
|
export default function HeaderBackground({ style, ...rest }: ViewProps) {
|
|
const { colors } = useTheme();
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/HeaderContainer.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -1,16 +1,13 @@
|
|
import * as React from 'react';
|
|
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
|
-import {
|
|
- NavigationContext,
|
|
- Route,
|
|
- ParamListBase,
|
|
-} from '@react-navigation/native';
|
|
-import { StackNavigationState } from '@react-navigation/routers';
|
|
+import { NavigationContext } from 'react-navigation';
|
|
+import { NavigationState as StackNavigationState } from 'react-navigation';
|
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
|
|
|
import Header from './Header';
|
|
import { forStatic } from '../../TransitionConfigs/HeaderStyleInterpolators';
|
|
import {
|
|
+ Route,
|
|
Layout,
|
|
Scene,
|
|
StackHeaderStyleInterpolator,
|
|
@@ -93,9 +90,7 @@
|
|
insets,
|
|
scene,
|
|
previous,
|
|
- navigation: scene.descriptor.navigation as StackNavigationProp<
|
|
- ParamListBase
|
|
- >,
|
|
+ navigation: scene.descriptor.navigation as StackNavigationProp,
|
|
styleInterpolator: isHeaderStatic ? forStatic : styleInterpolator,
|
|
};
|
|
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/HeaderSegment.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -8,7 +8,7 @@
|
|
ViewStyle,
|
|
} from 'react-native';
|
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
|
-import { Route } from '@react-navigation/native';
|
|
+import { NavigationRoute } from 'react-navigation';
|
|
import HeaderBackButton from './HeaderBackButton';
|
|
import HeaderBackground from './HeaderBackground';
|
|
import memoize from '../../utils/memoize';
|
|
@@ -28,7 +28,7 @@
|
|
onGoBack?: () => void;
|
|
title?: string;
|
|
leftLabel?: string;
|
|
- scene: Scene<Route<string>>;
|
|
+ scene: Scene<NavigationRoute>;
|
|
styleInterpolator: StackHeaderStyleInterpolator;
|
|
};
|
|
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Header/HeaderTitle.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -1,6 +1,6 @@
|
|
import * as React from 'react';
|
|
import { Animated, StyleSheet, Platform, TextProps } from 'react-native';
|
|
-import { useTheme } from '@react-navigation/native';
|
|
+import useTheme from '../../../utils/useTheme';
|
|
|
|
type Props = TextProps & {
|
|
tintColor?: string;
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Stack/Card.tsx 2020-01-03 21:48:42.000000000 +0100
|
|
@@ -453,7 +453,7 @@
|
|
pointerEvents="none"
|
|
/>
|
|
) : null}
|
|
- <View ref={this.content} style={[styles.content, contentStyle]}>
|
|
+ <View ref={this.content} style={[styles.content, contentStyle] as any}>
|
|
<StackCardAnimationContext.Provider value={animationContext}>
|
|
{children}
|
|
</StackCardAnimationContext.Provider>
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Stack/CardContainer.tsx 2020-01-03 21:49:43.000000000 +0100
|
|
@@ -1,10 +1,16 @@
|
|
import * as React from 'react';
|
|
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
|
-import { StackNavigationState } from '@react-navigation/routers';
|
|
-import { Route, useTheme } from '@react-navigation/native';
|
|
+import { NavigationState as StackNavigationState } from 'react-navigation';
|
|
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
|
import Card from './Card';
|
|
-import { Scene, Layout, StackHeaderMode, TransitionPreset } from '../../types';
|
|
+import useTheme from '../../../utils/useTheme';
|
|
+import {
|
|
+ Route,
|
|
+ Scene,
|
|
+ Layout,
|
|
+ StackHeaderMode,
|
|
+ TransitionPreset,
|
|
+} from '../../types';
|
|
|
|
type Props = TransitionPreset & {
|
|
index: number;
|
|
@@ -152,7 +158,7 @@
|
|
? { marginTop: floatingHeaderHeight }
|
|
: null
|
|
}
|
|
- contentStyle={[{ backgroundColor: colors.background }, cardStyle]}
|
|
+ contentStyle={[{ backgroundColor: colors.background }, cardStyle] as any}
|
|
style={StyleSheet.absoluteFill}
|
|
>
|
|
<View style={styles.container}>
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Stack/CardStack.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -11,8 +11,7 @@
|
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
|
// eslint-disable-next-line import/no-unresolved
|
|
import { ScreenContainer, Screen, screensEnabled } from 'react-native-screens'; // Import with * as to prevent getters being called
|
|
-import { Route } from '@react-navigation/native';
|
|
-import { StackNavigationState } from '@react-navigation/routers';
|
|
+import { NavigationState as StackNavigationState } from 'react-navigation';
|
|
|
|
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
|
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
|
@@ -25,6 +24,7 @@
|
|
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
|
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
|
import {
|
|
+ Route,
|
|
Layout,
|
|
StackHeaderMode,
|
|
StackCardMode,
|
|
diff -ruN 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-01-03 21:46:12.000000000 +0100
|
|
+++ src/vendor/views/Stack/StackView.tsx 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -1,8 +1,11 @@
|
|
import * as React from 'react';
|
|
import { Platform } from 'react-native';
|
|
import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
|
|
-import { Route } from '@react-navigation/native';
|
|
-import { StackActions, StackNavigationState } from '@react-navigation/routers';
|
|
+import {
|
|
+ StackActions,
|
|
+ NavigationState as StackNavigationState,
|
|
+ SceneView,
|
|
+} from 'react-navigation';
|
|
|
|
import CardStack from './CardStack';
|
|
import KeyboardManager from '../KeyboardManager';
|
|
@@ -11,6 +14,7 @@
|
|
} from '../Header/HeaderContainer';
|
|
import SafeAreaProviderCompat from '../SafeAreaProviderCompat';
|
|
import {
|
|
+ Route,
|
|
StackNavigationHelpers,
|
|
StackNavigationConfig,
|
|
StackDescriptorMap,
|
|
@@ -20,6 +24,7 @@
|
|
state: StackNavigationState;
|
|
navigation: StackNavigationHelpers;
|
|
descriptors: StackDescriptorMap;
|
|
+ screenProps: unknown;
|
|
};
|
|
|
|
type State = {
|
|
@@ -257,14 +262,31 @@
|
|
return null;
|
|
}
|
|
|
|
- return descriptor.render();
|
|
+ const { navigation, getComponent } = descriptor;
|
|
+ const SceneComponent = getComponent();
|
|
+
|
|
+ return (
|
|
+ <SceneView
|
|
+ screenProps={this.props.screenProps}
|
|
+ navigation={navigation}
|
|
+ component={SceneComponent}
|
|
+ />
|
|
+ );
|
|
};
|
|
|
|
private renderHeader = (props: HeaderContainerProps) => {
|
|
return <HeaderContainer {...props} />;
|
|
};
|
|
|
|
+ private handleTransitionComplete = ({ route }: { route: Route<string> }) => {
|
|
+ // TODO: remove when the new event system lands
|
|
+ this.props.navigation.dispatch(
|
|
+ StackActions.completeTransition({ toChildKey: route.key })
|
|
+ );
|
|
+ };
|
|
+
|
|
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
|
+ this.handleTransitionComplete({ route });
|
|
this.setState(state => ({
|
|
routes: state.replacingRouteKeys.length
|
|
? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key))
|
|
@@ -283,15 +305,19 @@
|
|
// This will happen in when the route was closed from the card component
|
|
// e.g. When the close animation triggered from a gesture ends
|
|
// For the cleanup, the card needs to call this function again from its componentDidUpdate
|
|
- navigation.dispatch({
|
|
- ...StackActions.pop(),
|
|
- source: route.key,
|
|
- target: state.key,
|
|
- });
|
|
+ // @ts-ignore
|
|
+ navigation.dispatch(StackActions.pop({ key: route.key }));
|
|
} else {
|
|
// Otherwise, the animation was triggered due to a route removal
|
|
// In this case, we need to clean up any state tracking the route and pop it immediately
|
|
|
|
+ // While closing route we need to point to the previous one assuming that
|
|
+ // this previous one in routes array
|
|
+ const index = this.state.routes.findIndex(r => r.key === route.key);
|
|
+ this.handleTransitionComplete({
|
|
+ route: this.state.routes[Math.max(index - 1, 0)],
|
|
+ });
|
|
+
|
|
// @ts-ignore
|
|
this.setState(state => ({
|
|
routes: state.routes.filter(r => r.key !== route.key),
|
|
@@ -308,22 +334,26 @@
|
|
private handleTransitionStart = (
|
|
{ route }: { route: Route<string> },
|
|
closing: boolean
|
|
- ) =>
|
|
- this.props.navigation.emit({
|
|
- type: 'transitionStart',
|
|
- data: { closing },
|
|
- target: route.key,
|
|
- });
|
|
+ ) => {
|
|
+ const { descriptors } = this.props;
|
|
+ const descriptor = descriptors[route.key];
|
|
+
|
|
+ descriptor &&
|
|
+ descriptor.options.onTransitionStart &&
|
|
+ descriptor.options.onTransitionStart({ closing });
|
|
+ };
|
|
|
|
private handleTransitionEnd = (
|
|
{ route }: { route: Route<string> },
|
|
closing: boolean
|
|
- ) =>
|
|
- this.props.navigation.emit({
|
|
- type: 'transitionEnd',
|
|
- data: { closing },
|
|
- target: route.key,
|
|
- });
|
|
+ ) => {
|
|
+ const { descriptors } = this.props;
|
|
+ const descriptor = descriptors[route.key];
|
|
+
|
|
+ descriptor &&
|
|
+ descriptor.options.onTransitionStart &&
|
|
+ descriptor.options.onTransitionStart({ closing });
|
|
+ };
|
|
|
|
render() {
|
|
const {
|
|
diff -ruN node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx.orig src/vendor/views/Stack/StackView.tsx.orig
|
|
--- node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx.orig 1970-01-01 01:00:00.000000000 +0100
|
|
+++ src/vendor/views/Stack/StackView.tsx.orig 2020-01-03 21:46:29.000000000 +0100
|
|
@@ -0,0 +1,383 @@
|
|
+import * as React from 'react';
|
|
+import { Platform } from 'react-native';
|
|
+import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
|
|
+import { Route } from '@react-navigation/native';
|
|
+import { StackActions, StackNavigationState } from '@react-navigation/routers';
|
|
+
|
|
+import CardStack from './CardStack';
|
|
+import KeyboardManager from '../KeyboardManager';
|
|
+import HeaderContainer, {
|
|
+ Props as HeaderContainerProps,
|
|
+} from '../Header/HeaderContainer';
|
|
+import SafeAreaProviderCompat from '../SafeAreaProviderCompat';
|
|
+import {
|
|
+ StackNavigationHelpers,
|
|
+ StackNavigationConfig,
|
|
+ StackDescriptorMap,
|
|
+} from '../../types';
|
|
+
|
|
+type Props = StackNavigationConfig & {
|
|
+ state: StackNavigationState;
|
|
+ navigation: StackNavigationHelpers;
|
|
+ descriptors: StackDescriptorMap;
|
|
+};
|
|
+
|
|
+type State = {
|
|
+ // Local copy of the routes which are actually rendered
|
|
+ routes: Route<string>[];
|
|
+ // Previous routes, to compare whether routes have changed or not
|
|
+ previousRoutes: Route<string>[];
|
|
+ // Previous descriptors, to compare whether descriptors have changed or not
|
|
+ previousDescriptors: StackDescriptorMap;
|
|
+ // List of routes being opened, we need to animate pushing of these new routes
|
|
+ openingRouteKeys: string[];
|
|
+ // List of routes being closed, we need to animate popping of these routes
|
|
+ closingRouteKeys: string[];
|
|
+ // List of routes being replaced, we need to keep a copy until the new route animates in
|
|
+ replacingRouteKeys: string[];
|
|
+ // Since the local routes can vary from the routes from props, we need to keep the descriptors for old routes
|
|
+ // Otherwise we won't be able to access the options for routes that were removed
|
|
+ descriptors: StackDescriptorMap;
|
|
+};
|
|
+
|
|
+class StackView extends React.Component<Props, State> {
|
|
+ static getDerivedStateFromProps(
|
|
+ props: Readonly<Props>,
|
|
+ state: Readonly<State>
|
|
+ ) {
|
|
+ // If there was no change in routes, we don't need to compute anything
|
|
+ if (props.state.routes === state.previousRoutes && state.routes.length) {
|
|
+ if (props.descriptors !== state.previousDescriptors) {
|
|
+ const descriptors = state.routes.reduce<StackDescriptorMap>(
|
|
+ (acc, route) => {
|
|
+ acc[route.key] =
|
|
+ props.descriptors[route.key] || state.descriptors[route.key];
|
|
+
|
|
+ return acc;
|
|
+ },
|
|
+ {}
|
|
+ );
|
|
+
|
|
+ return {
|
|
+ previousDescriptors: props.descriptors,
|
|
+ descriptors,
|
|
+ };
|
|
+ }
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ // Here we determine which routes were added or removed to animate them
|
|
+ // We keep a copy of the route being removed in local state to be able to animate it
|
|
+
|
|
+ let routes =
|
|
+ props.state.index < props.state.routes.length - 1
|
|
+ ? // Remove any extra routes from the state
|
|
+ // The last visible route should be the focused route, i.e. at current index
|
|
+ props.state.routes.slice(0, props.state.index + 1)
|
|
+ : props.state.routes;
|
|
+
|
|
+ // Now we need to determine which routes were added and removed
|
|
+ let {
|
|
+ openingRouteKeys,
|
|
+ closingRouteKeys,
|
|
+ replacingRouteKeys,
|
|
+ previousRoutes,
|
|
+ } = state;
|
|
+
|
|
+ const previousFocusedRoute = previousRoutes[previousRoutes.length - 1] as
|
|
+ | Route<string>
|
|
+ | undefined;
|
|
+ const nextFocusedRoute = routes[routes.length - 1];
|
|
+
|
|
+ const isAnimationEnabled = (key: string) => {
|
|
+ const descriptor = props.descriptors[key] || state.descriptors[key];
|
|
+
|
|
+ return descriptor ? descriptor.options.animationEnabled !== false : true;
|
|
+ };
|
|
+
|
|
+ if (
|
|
+ previousFocusedRoute &&
|
|
+ previousFocusedRoute.key !== nextFocusedRoute.key
|
|
+ ) {
|
|
+ // We only need to animate routes if the focused route changed
|
|
+ // Animating previous routes won't be visible coz the focused route is on top of everything
|
|
+
|
|
+ if (!previousRoutes.find(r => r.key === nextFocusedRoute.key)) {
|
|
+ // A new route has come to the focus, we treat this as a push
|
|
+ // A replace can also trigger this, the animation should look like push
|
|
+
|
|
+ if (
|
|
+ isAnimationEnabled(nextFocusedRoute.key) &&
|
|
+ !openingRouteKeys.includes(nextFocusedRoute.key)
|
|
+ ) {
|
|
+ // In this case, we need to animate pushing the focused route
|
|
+ // We don't care about animating any other added routes because they won't be visible
|
|
+ openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
|
|
+
|
|
+ closingRouteKeys = closingRouteKeys.filter(
|
|
+ key => key !== nextFocusedRoute.key
|
|
+ );
|
|
+ replacingRouteKeys = replacingRouteKeys.filter(
|
|
+ key => key !== nextFocusedRoute.key
|
|
+ );
|
|
+
|
|
+ if (!routes.find(r => r.key === previousFocusedRoute.key)) {
|
|
+ // The previous focused route isn't present in state, we treat this as a replace
|
|
+
|
|
+ replacingRouteKeys = [
|
|
+ ...replacingRouteKeys,
|
|
+ previousFocusedRoute.key,
|
|
+ ];
|
|
+
|
|
+ openingRouteKeys = openingRouteKeys.filter(
|
|
+ key => key !== previousFocusedRoute.key
|
|
+ );
|
|
+ closingRouteKeys = closingRouteKeys.filter(
|
|
+ key => key !== previousFocusedRoute.key
|
|
+ );
|
|
+
|
|
+ // Keep the old route in state because it's visible under the new route, and removing it will feel abrupt
|
|
+ // We need to insert it just before the focused one (the route being pushed)
|
|
+ // After the push animation is completed, routes being replaced will be removed completely
|
|
+ routes = routes.slice();
|
|
+ routes.splice(routes.length - 1, 0, previousFocusedRoute);
|
|
+ }
|
|
+ }
|
|
+ } else if (!routes.find(r => r.key === previousFocusedRoute.key)) {
|
|
+ // The previously focused route was removed, we treat this as a pop
|
|
+
|
|
+ if (
|
|
+ isAnimationEnabled(previousFocusedRoute.key) &&
|
|
+ !closingRouteKeys.includes(previousFocusedRoute.key)
|
|
+ ) {
|
|
+ // Sometimes a route can be closed before the opening animation finishes
|
|
+ // So we also need to remove it from the opening list
|
|
+ closingRouteKeys = [...closingRouteKeys, previousFocusedRoute.key];
|
|
+
|
|
+ openingRouteKeys = openingRouteKeys.filter(
|
|
+ key => key !== previousFocusedRoute.key
|
|
+ );
|
|
+ replacingRouteKeys = replacingRouteKeys.filter(
|
|
+ key => key !== previousFocusedRoute.key
|
|
+ );
|
|
+
|
|
+ // Keep a copy of route being removed in the state to be able to animate it
|
|
+ routes = [...routes, previousFocusedRoute];
|
|
+ }
|
|
+ } else {
|
|
+ // Looks like some routes were re-arranged and no focused routes were added/removed
|
|
+ // i.e. the currently focused route already existed and the previously focused route still exists
|
|
+ // We don't know how to animate this
|
|
+ }
|
|
+ } else if (replacingRouteKeys.length || closingRouteKeys.length) {
|
|
+ // Keep the routes we are closing or replacing if animation is enabled for them
|
|
+ routes = routes.slice();
|
|
+ routes.splice(
|
|
+ routes.length - 1,
|
|
+ 0,
|
|
+ ...state.routes.filter(({ key }) =>
|
|
+ isAnimationEnabled(key)
|
|
+ ? replacingRouteKeys.includes(key) || closingRouteKeys.includes(key)
|
|
+ : false
|
|
+ )
|
|
+ );
|
|
+ }
|
|
+
|
|
+ if (!routes.length) {
|
|
+ throw new Error(`There should always be at least one route.`);
|
|
+ }
|
|
+
|
|
+ const descriptors = routes.reduce<StackDescriptorMap>((acc, route) => {
|
|
+ acc[route.key] =
|
|
+ props.descriptors[route.key] || state.descriptors[route.key];
|
|
+
|
|
+ return acc;
|
|
+ }, {});
|
|
+
|
|
+ return {
|
|
+ routes,
|
|
+ previousRoutes: props.state.routes,
|
|
+ previousDescriptors: props.descriptors,
|
|
+ openingRouteKeys,
|
|
+ closingRouteKeys,
|
|
+ replacingRouteKeys,
|
|
+ descriptors,
|
|
+ };
|
|
+ }
|
|
+
|
|
+ state: State = {
|
|
+ routes: [],
|
|
+ previousRoutes: [],
|
|
+ previousDescriptors: {},
|
|
+ openingRouteKeys: [],
|
|
+ closingRouteKeys: [],
|
|
+ replacingRouteKeys: [],
|
|
+ descriptors: {},
|
|
+ };
|
|
+
|
|
+ private getGesturesEnabled = ({ route }: { route: Route<string> }) => {
|
|
+ const descriptor = this.state.descriptors[route.key];
|
|
+
|
|
+ if (descriptor) {
|
|
+ const { gestureEnabled, animationEnabled } = descriptor.options;
|
|
+
|
|
+ if (animationEnabled === false) {
|
|
+ // When animation is disabled, also disable gestures
|
|
+ // The gesture to dismiss a route will look weird when not animated
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return gestureEnabled !== undefined
|
|
+ ? gestureEnabled
|
|
+ : Platform.OS !== 'android';
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ };
|
|
+
|
|
+ private getPreviousRoute = ({ route }: { route: Route<string> }) => {
|
|
+ const { closingRouteKeys, replacingRouteKeys } = this.state;
|
|
+ const routes = this.state.routes.filter(
|
|
+ r =>
|
|
+ r.key === route.key ||
|
|
+ (!closingRouteKeys.includes(r.key) &&
|
|
+ !replacingRouteKeys.includes(r.key))
|
|
+ );
|
|
+ const index = routes.findIndex(r => r.key === route.key);
|
|
+
|
|
+ return routes[index - 1];
|
|
+ };
|
|
+
|
|
+ private renderScene = ({ route }: { route: Route<string> }) => {
|
|
+ const descriptor =
|
|
+ this.state.descriptors[route.key] || this.props.descriptors[route.key];
|
|
+
|
|
+ if (!descriptor) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return descriptor.render();
|
|
+ };
|
|
+
|
|
+ private renderHeader = (props: HeaderContainerProps) => {
|
|
+ return <HeaderContainer {...props} />;
|
|
+ };
|
|
+
|
|
+ private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
|
+ this.setState(state => ({
|
|
+ routes: state.replacingRouteKeys.length
|
|
+ ? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key))
|
|
+ : state.routes,
|
|
+ openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
|
|
+ closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key),
|
|
+ replacingRouteKeys: [],
|
|
+ }));
|
|
+ };
|
|
+
|
|
+ private handleCloseRoute = ({ route }: { route: Route<string> }) => {
|
|
+ const { state, navigation } = this.props;
|
|
+
|
|
+ if (state.routes.find(r => r.key === route.key)) {
|
|
+ // 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
|
|
+ // For the cleanup, the card needs to call this function again from its componentDidUpdate
|
|
+ navigation.dispatch({
|
|
+ ...StackActions.pop(),
|
|
+ source: route.key,
|
|
+ target: state.key,
|
|
+ });
|
|
+ } else {
|
|
+ // Otherwise, the animation was triggered due to a route removal
|
|
+ // In this case, we need to clean up any state tracking the route and pop it immediately
|
|
+
|
|
+ // @ts-ignore
|
|
+ this.setState(state => ({
|
|
+ routes: state.routes.filter(r => r.key !== route.key),
|
|
+ openingRouteKeys: state.openingRouteKeys.filter(
|
|
+ key => key !== route.key
|
|
+ ),
|
|
+ closingRouteKeys: state.closingRouteKeys.filter(
|
|
+ key => key !== route.key
|
|
+ ),
|
|
+ }));
|
|
+ }
|
|
+ };
|
|
+
|
|
+ private handleTransitionStart = (
|
|
+ { route }: { route: Route<string> },
|
|
+ closing: boolean
|
|
+ ) =>
|
|
+ this.props.navigation.emit({
|
|
+ type: 'transitionStart',
|
|
+ data: { closing },
|
|
+ target: route.key,
|
|
+ });
|
|
+
|
|
+ private handleTransitionEnd = (
|
|
+ { route }: { route: Route<string> },
|
|
+ closing: boolean
|
|
+ ) =>
|
|
+ this.props.navigation.emit({
|
|
+ type: 'transitionEnd',
|
|
+ data: { closing },
|
|
+ target: route.key,
|
|
+ });
|
|
+
|
|
+ render() {
|
|
+ const {
|
|
+ state,
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
+ navigation,
|
|
+ keyboardHandlingEnabled,
|
|
+ mode = 'card',
|
|
+ ...rest
|
|
+ } = this.props;
|
|
+
|
|
+ const {
|
|
+ routes,
|
|
+ descriptors,
|
|
+ openingRouteKeys,
|
|
+ closingRouteKeys,
|
|
+ } = this.state;
|
|
+
|
|
+ const headerMode =
|
|
+ mode !== 'modal' && Platform.OS === 'ios' ? 'float' : 'screen';
|
|
+
|
|
+ return (
|
|
+ <SafeAreaProviderCompat>
|
|
+ <SafeAreaConsumer>
|
|
+ {insets => (
|
|
+ <KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
|
+ {props => (
|
|
+ <CardStack
|
|
+ mode={mode}
|
|
+ insets={insets as EdgeInsets}
|
|
+ getPreviousRoute={this.getPreviousRoute}
|
|
+ getGesturesEnabled={this.getGesturesEnabled}
|
|
+ routes={routes}
|
|
+ openingRouteKeys={openingRouteKeys}
|
|
+ closingRouteKeys={closingRouteKeys}
|
|
+ onOpenRoute={this.handleOpenRoute}
|
|
+ onCloseRoute={this.handleCloseRoute}
|
|
+ onTransitionStart={this.handleTransitionStart}
|
|
+ onTransitionEnd={this.handleTransitionEnd}
|
|
+ renderHeader={this.renderHeader}
|
|
+ renderScene={this.renderScene}
|
|
+ headerMode={headerMode}
|
|
+ state={state}
|
|
+ descriptors={descriptors}
|
|
+ {...rest}
|
|
+ {...props}
|
|
+ />
|
|
+ )}
|
|
+ </KeyboardManager>
|
|
+ )}
|
|
+ </SafeAreaConsumer>
|
|
+ </SafeAreaProviderCompat>
|
|
+ );
|
|
+ }
|
|
+}
|
|
+
|
|
+export default StackView;
|