mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-27 01:34:25 +08:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee40dd7d24 | ||
|
|
18a48105c2 | ||
|
|
fbac47b696 | ||
|
|
9aab47dac2 | ||
|
|
67309c00a6 | ||
|
|
86a724cfe3 | ||
|
|
eb78128439 | ||
|
|
c39ec7a10c | ||
|
|
0ff3347e97 | ||
|
|
b9d55a6330 | ||
|
|
315e43701b | ||
|
|
1d573bc246 | ||
|
|
3bfb0b90d0 | ||
|
|
8a129afe13 | ||
|
|
ab2a63fe92 | ||
|
|
c411210ecc | ||
|
|
01e7296520 | ||
|
|
8f3e0997c5 | ||
|
|
3f3ef6485c | ||
|
|
b12abb553f | ||
|
|
e02841a979 | ||
|
|
e147f34555 | ||
|
|
81e0ce136e | ||
|
|
8ba727c2cf | ||
|
|
9a86ef8362 | ||
|
|
4fe7c92847 | ||
|
|
afecaaed7f | ||
|
|
6373b802dd | ||
|
|
138151433d | ||
|
|
2744cb32b7 | ||
|
|
439b4222ce | ||
|
|
ba0b1861e5 | ||
|
|
318788ca60 | ||
|
|
498a39c200 | ||
|
|
b31ebef5b0 | ||
|
|
f4fe588e08 | ||
|
|
403af82c3f | ||
|
|
0c2360dc36 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -9,6 +9,8 @@ If you have a question, feature request, or an idea for improving the library or
|
|||||||
- [Get help on Discord chat (#react-navigation on Reactiflux)](https://discord.gg/4xEK3nD) or [on StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation)
|
- [Get help on Discord chat (#react-navigation on Reactiflux)](https://discord.gg/4xEK3nD) or [on StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation)
|
||||||
- Search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue.
|
- Search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue.
|
||||||
|
|
||||||
|
Bugs with react-navigation must be reproducible *without any external libraries that operate on it*. This means that if you are attempting to use Redux or MobX with it and you think you have found a bug, you must be able to reproduce it without Redux or MobX in this report. Redux related issues belong in [react-navigation-redux-helpers](https://github.com/react-navigation/react-navigation-redux-helpers), and we do not have any first-class integration with MobX at the moment.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Current Behavior
|
### Current Behavior
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import MultipleDrawer from './MultipleDrawer';
|
|||||||
import TabsInDrawer from './TabsInDrawer';
|
import TabsInDrawer from './TabsInDrawer';
|
||||||
import ModalStack from './ModalStack';
|
import ModalStack from './ModalStack';
|
||||||
import StacksInTabs from './StacksInTabs';
|
import StacksInTabs from './StacksInTabs';
|
||||||
|
import SwitchWithStacks from './SwitchWithStacks';
|
||||||
import StacksOverTabs from './StacksOverTabs';
|
import StacksOverTabs from './StacksOverTabs';
|
||||||
import StacksWithKeys from './StacksWithKeys';
|
import StacksWithKeys from './StacksWithKeys';
|
||||||
import SimpleStack from './SimpleStack';
|
import SimpleStack from './SimpleStack';
|
||||||
@@ -39,6 +40,10 @@ const ExampleInfo = {
|
|||||||
name: 'Stack Example',
|
name: 'Stack Example',
|
||||||
description: 'A card stack',
|
description: 'A card stack',
|
||||||
},
|
},
|
||||||
|
SwitchWithStacks: {
|
||||||
|
name: 'Switch Example',
|
||||||
|
description: 'A switch with stacks inside',
|
||||||
|
},
|
||||||
SimpleTabs: {
|
SimpleTabs: {
|
||||||
name: 'Tabs Example',
|
name: 'Tabs Example',
|
||||||
description: 'Tabs following platform conventions',
|
description: 'Tabs following platform conventions',
|
||||||
@@ -109,28 +114,29 @@ const ExampleInfo = {
|
|||||||
name: 'Animated Tabs Example',
|
name: 'Animated Tabs Example',
|
||||||
description: 'Tab transitions have custom animations',
|
description: 'Tab transitions have custom animations',
|
||||||
},
|
},
|
||||||
// TabsWithNavigationFocus: {
|
TabsWithNavigationFocus: {
|
||||||
// name: 'withNavigationFocus',
|
name: 'withNavigationFocus',
|
||||||
// description: 'Receive the focus prop to know when a screen is focused',
|
description: 'Receive the focus prop to know when a screen is focused',
|
||||||
// },
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExampleRoutes = {
|
const ExampleRoutes = {
|
||||||
SimpleStack: SimpleStack,
|
SimpleStack,
|
||||||
SimpleTabs: SimpleTabs,
|
SwitchWithStacks,
|
||||||
Drawer: Drawer,
|
SimpleTabs,
|
||||||
|
Drawer,
|
||||||
// MultipleDrawer: {
|
// MultipleDrawer: {
|
||||||
// screen: MultipleDrawer,
|
// screen: MultipleDrawer,
|
||||||
// },
|
// },
|
||||||
StackWithHeaderPreset: StackWithHeaderPreset,
|
StackWithHeaderPreset,
|
||||||
StackWithTranslucentHeader: StackWithTranslucentHeader,
|
StackWithTranslucentHeader,
|
||||||
TabsInDrawer: TabsInDrawer,
|
TabsInDrawer,
|
||||||
CustomTabs: CustomTabs,
|
CustomTabs,
|
||||||
CustomTransitioner: CustomTransitioner,
|
CustomTransitioner,
|
||||||
ModalStack: ModalStack,
|
ModalStack,
|
||||||
StacksWithKeys: StacksWithKeys,
|
StacksWithKeys,
|
||||||
StacksInTabs: StacksInTabs,
|
StacksInTabs,
|
||||||
StacksOverTabs: StacksOverTabs,
|
StacksOverTabs,
|
||||||
LinkStack: {
|
LinkStack: {
|
||||||
screen: SimpleStack,
|
screen: SimpleStack,
|
||||||
path: 'people/Jordan',
|
path: 'people/Jordan',
|
||||||
@@ -139,8 +145,8 @@ const ExampleRoutes = {
|
|||||||
screen: SimpleTabs,
|
screen: SimpleTabs,
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
},
|
},
|
||||||
TabAnimations: TabAnimations,
|
TabAnimations,
|
||||||
// TabsWithNavigationFocus: TabsWithNavigationFocus,
|
TabsWithNavigationFocus,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
|||||||
@@ -119,6 +119,9 @@ const StacksInTabs = TabNavigator(
|
|||||||
tabBarPosition: 'bottom',
|
tabBarPosition: 'bottom',
|
||||||
animationEnabled: false,
|
animationEnabled: false,
|
||||||
swipeEnabled: false,
|
swipeEnabled: false,
|
||||||
|
tabBarOptions: {
|
||||||
|
showLabel: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
121
examples/NavigationPlayground/js/SwitchWithStacks.js
Normal file
121
examples/NavigationPlayground/js/SwitchWithStacks.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
AsyncStorage,
|
||||||
|
Button,
|
||||||
|
StatusBar,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import { StackNavigator, SwitchNavigator } from 'react-navigation';
|
||||||
|
|
||||||
|
class SignInScreen extends React.Component<any, any> {
|
||||||
|
static navigationOptions = {
|
||||||
|
title: 'Please sign in',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Button title="Sign in!" onPress={this._signInAsync} />
|
||||||
|
<Button
|
||||||
|
title="Go back to other examples"
|
||||||
|
onPress={() => this.props.navigation.goBack(null)}
|
||||||
|
/>
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_signInAsync = async () => {
|
||||||
|
await AsyncStorage.setItem('userToken', 'abc');
|
||||||
|
this.props.navigation.navigate('App');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomeScreen extends React.Component<any, any> {
|
||||||
|
static navigationOptions = {
|
||||||
|
title: 'Welcome to the app!',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Button title="Show me more of the app" onPress={this._showMoreApp} />
|
||||||
|
<Button title="Actually, sign me out :)" onPress={this._signOutAsync} />
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showMoreApp = () => {
|
||||||
|
this.props.navigation.navigate('Other');
|
||||||
|
};
|
||||||
|
|
||||||
|
_signOutAsync = async () => {
|
||||||
|
await AsyncStorage.clear();
|
||||||
|
this.props.navigation.navigate('Auth');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtherScreen extends React.Component<any, any> {
|
||||||
|
static navigationOptions = {
|
||||||
|
title: 'Lots of features here',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Button title="I'm done, sign me out" onPress={this._signOutAsync} />
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_signOutAsync = async () => {
|
||||||
|
await AsyncStorage.clear();
|
||||||
|
this.props.navigation.navigate('Auth');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadingScreen extends React.Component<any, any> {
|
||||||
|
componentDidMount() {
|
||||||
|
this._bootstrapAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
_bootstrapAsync = async () => {
|
||||||
|
const userToken = await AsyncStorage.getItem('userToken');
|
||||||
|
let initialRouteName = userToken ? 'App' : 'Auth';
|
||||||
|
this.props.navigation.navigate(initialRouteName);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ActivityIndicator />
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const AppStack = StackNavigator({ Home: HomeScreen, Other: OtherScreen });
|
||||||
|
const AuthStack = StackNavigator({ SignIn: SignInScreen });
|
||||||
|
|
||||||
|
export default SwitchNavigator({
|
||||||
|
Loading: LoadingScreen,
|
||||||
|
App: AppStack,
|
||||||
|
Auth: AuthStack,
|
||||||
|
});
|
||||||
@@ -3,40 +3,75 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SafeAreaView, Text } from 'react-native';
|
import { Button, SafeAreaView, Text } from 'react-native';
|
||||||
import { TabNavigator, withNavigationFocus } from 'react-navigation';
|
import { TabNavigator, withNavigationFocus } from 'react-navigation';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
|
|
||||||
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
class Child extends React.Component<any, any> {
|
||||||
const TabScreen = ({ isFocused }) => (
|
render() {
|
||||||
<SafeAreaView
|
return (
|
||||||
forceInset={{ horizontal: 'always', top: 'always' }}
|
<Text style={{ color: this.props.isFocused ? 'green' : 'maroon' }}>
|
||||||
style={{
|
{this.props.isFocused
|
||||||
flex: 1,
|
? 'I know that my parent is focused!'
|
||||||
alignItems: 'center',
|
: 'My parent is not focused! :O'}
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
|
|
||||||
{'Tab ' + name.toLowerCase()}
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{'props.isFocused: ' + (isFocused ? ' true' : 'false')}</Text>
|
);
|
||||||
</SafeAreaView>
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
TabScreen.navigationOptions = {
|
const ChildWithNavigationFocus = withNavigationFocus(Child);
|
||||||
tabBarLabel: name,
|
|
||||||
tabBarIcon: ({ tintColor, focused }) => (
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={focused ? focusedIcon : icon}
|
|
||||||
size={26}
|
|
||||||
style={{ color: focused ? tintColor : '#ccc' }}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
||||||
|
class TabScreen extends React.Component<any, any> {
|
||||||
|
static navigationOptions = {
|
||||||
|
tabBarLabel: name,
|
||||||
|
tabBarIcon: ({ tintColor, focused }) => (
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name={focused ? focusedIcon : icon}
|
||||||
|
size={26}
|
||||||
|
style={{ color: focused ? tintColor : '#ccc' }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
state = { showChild: false };
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isFocused } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView
|
||||||
|
forceInset={{ horizontal: 'always', top: 'always' }}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
|
||||||
|
{'Tab ' + name.toLowerCase()}
|
||||||
|
</Text>
|
||||||
|
<Text style={{ marginBottom: 20 }}>
|
||||||
|
{'props.isFocused: ' + (isFocused ? ' true' : 'false')}
|
||||||
|
</Text>
|
||||||
|
{this.state.showChild ? (
|
||||||
|
<ChildWithNavigationFocus />
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
title="Press me"
|
||||||
|
onPress={() => this.setState({ showChild: true })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onPress={() => this.props.navigation.goBack(null)}
|
||||||
|
title="Back to other examples"
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return withNavigationFocus(TabScreen);
|
return withNavigationFocus(TabScreen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"react": "16.2.0",
|
"react": "16.2.0",
|
||||||
"react-native": "^0.52.0",
|
"react-native": "^0.52.0",
|
||||||
"react-navigation": "link:../..",
|
"react-navigation": "link:../..",
|
||||||
"react-navigation-redux-helpers": "^1.0.0",
|
"react-navigation-redux-helpers": "^1.0.3",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.0.6",
|
||||||
"redux": "^3.7.2"
|
"redux": "^3.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
|
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
|
||||||
|
import { initializeListeners } from 'react-navigation-redux-helpers';
|
||||||
|
|
||||||
import LoginScreen from '../components/LoginScreen';
|
import LoginScreen from '../components/LoginScreen';
|
||||||
import MainScreen from '../components/MainScreen';
|
import MainScreen from '../components/MainScreen';
|
||||||
@@ -20,6 +21,10 @@ class AppWithNavigationState extends React.Component {
|
|||||||
nav: PropTypes.object.isRequired,
|
nav: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
initializeListeners('root', this.props.nav);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dispatch, nav } = this.props;
|
const { dispatch, nav } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
50
flow/react-navigation.js
vendored
50
flow/react-navigation.js
vendored
@@ -48,6 +48,15 @@ declare module 'react-navigation' {
|
|||||||
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js
|
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js
|
||||||
declare type AnimatedValue = Object;
|
declare type AnimatedValue = Object;
|
||||||
|
|
||||||
|
declare type HeaderForceInset = {
|
||||||
|
horizontal?: string,
|
||||||
|
vertical?: string,
|
||||||
|
left?: string,
|
||||||
|
right?: string,
|
||||||
|
top?: string,
|
||||||
|
bottom?: string,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next, all the type declarations
|
* Next, all the type declarations
|
||||||
*/
|
*/
|
||||||
@@ -286,7 +295,6 @@ declare module 'react-navigation' {
|
|||||||
} & NavigationScreenRouteConfig);
|
} & NavigationScreenRouteConfig);
|
||||||
|
|
||||||
declare export type NavigationScreenRouteConfig =
|
declare export type NavigationScreenRouteConfig =
|
||||||
| NavigationComponent
|
|
||||||
| {
|
| {
|
||||||
screen: NavigationComponent,
|
screen: NavigationComponent,
|
||||||
}
|
}
|
||||||
@@ -340,6 +348,7 @@ declare module 'react-navigation' {
|
|||||||
headerPressColorAndroid?: string,
|
headerPressColorAndroid?: string,
|
||||||
headerRight?: React$Node,
|
headerRight?: React$Node,
|
||||||
headerStyle?: ViewStyleProp,
|
headerStyle?: ViewStyleProp,
|
||||||
|
headerForceInset?: HeaderForceInset,
|
||||||
headerBackground?: React$Node | React$ElementType,
|
headerBackground?: React$Node | React$ElementType,
|
||||||
gesturesEnabled?: boolean,
|
gesturesEnabled?: boolean,
|
||||||
gestureResponseDistance?: { vertical?: number, horizontal?: number },
|
gestureResponseDistance?: { vertical?: number, horizontal?: number },
|
||||||
@@ -351,6 +360,7 @@ declare module 'react-navigation' {
|
|||||||
initialRouteParams?: NavigationParams,
|
initialRouteParams?: NavigationParams,
|
||||||
paths?: NavigationPathsConfig,
|
paths?: NavigationPathsConfig,
|
||||||
navigationOptions?: NavigationScreenConfig<*>,
|
navigationOptions?: NavigationScreenConfig<*>,
|
||||||
|
initialRouteKey?: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
declare export type NavigationStackViewConfig = {|
|
declare export type NavigationStackViewConfig = {|
|
||||||
@@ -368,6 +378,20 @@ declare module 'react-navigation' {
|
|||||||
...NavigationStackRouterConfig,
|
...NavigationStackRouterConfig,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch Navigator
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare export type NavigationSwitchRouterConfig = {|
|
||||||
|
initialRouteName?: string,
|
||||||
|
initialRouteParams?: NavigationParams,
|
||||||
|
paths?: NavigationPathsConfig,
|
||||||
|
navigationOptions?: NavigationScreenConfig<*>,
|
||||||
|
order?: Array<string>,
|
||||||
|
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
|
||||||
|
resetOnBlur?: boolean, // defaults to `true`
|
||||||
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab Navigator
|
* Tab Navigator
|
||||||
*/
|
*/
|
||||||
@@ -379,7 +403,6 @@ declare module 'react-navigation' {
|
|||||||
navigationOptions?: NavigationScreenConfig<*>,
|
navigationOptions?: NavigationScreenConfig<*>,
|
||||||
// todo: type these as the real route names rather than 'string'
|
// todo: type these as the real route names rather than 'string'
|
||||||
order?: Array<string>,
|
order?: Array<string>,
|
||||||
|
|
||||||
// Does the back button cause the router to switch to the initial tab
|
// Does the back button cause the router to switch to the initial tab
|
||||||
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
||||||
|};
|
|};
|
||||||
@@ -447,7 +470,7 @@ declare module 'react-navigation' {
|
|||||||
type: EventType,
|
type: EventType,
|
||||||
action: NavigationAction,
|
action: NavigationAction,
|
||||||
state: NavigationState,
|
state: NavigationState,
|
||||||
lastState: NavigationState,
|
lastState: ?NavigationState,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export type NavigationEventCallback = (
|
declare export type NavigationEventCallback = (
|
||||||
@@ -530,7 +553,7 @@ declare module 'react-navigation' {
|
|||||||
|
|
||||||
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
|
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
|
||||||
uriPrefix?: string | RegExp,
|
uriPrefix?: string | RegExp,
|
||||||
onNavigationStateChange?: (
|
onNavigationStateChange?: ?(
|
||||||
NavigationState,
|
NavigationState,
|
||||||
NavigationState,
|
NavigationState,
|
||||||
NavigationAction
|
NavigationAction
|
||||||
@@ -701,7 +724,7 @@ declare module 'react-navigation' {
|
|||||||
SET_PARAMS: 'Navigation/SET_PARAMS',
|
SET_PARAMS: 'Navigation/SET_PARAMS',
|
||||||
URI: 'Navigation/URI',
|
URI: 'Navigation/URI',
|
||||||
back: {
|
back: {
|
||||||
(payload: { key?: ?string }): NavigationBackAction,
|
(payload?: { key?: ?string }): NavigationBackAction,
|
||||||
toString: () => string,
|
toString: () => string,
|
||||||
},
|
},
|
||||||
init: {
|
init: {
|
||||||
@@ -786,6 +809,13 @@ declare module 'react-navigation' {
|
|||||||
routeConfigs: NavigationRouteConfigMap,
|
routeConfigs: NavigationRouteConfigMap,
|
||||||
config?: _TabNavigatorConfig
|
config?: _TabNavigatorConfig
|
||||||
): NavigationContainer<*, *, *>;
|
): NavigationContainer<*, *, *>;
|
||||||
|
declare type _SwitchNavigatorConfig = {|
|
||||||
|
...NavigationSwitchRouterConfig,
|
||||||
|
|};
|
||||||
|
declare export function SwitchNavigator(
|
||||||
|
routeConfigs: NavigationRouteConfigMap,
|
||||||
|
config?: _SwitchNavigatorConfig
|
||||||
|
): NavigationContainer<*, *, *>;
|
||||||
|
|
||||||
declare type _DrawerViewConfig = {|
|
declare type _DrawerViewConfig = {|
|
||||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||||
@@ -895,12 +925,14 @@ declare module 'react-navigation' {
|
|||||||
vertical?: _SafeAreaViewForceInsetValue,
|
vertical?: _SafeAreaViewForceInsetValue,
|
||||||
horizontal?: _SafeAreaViewForceInsetValue,
|
horizontal?: _SafeAreaViewForceInsetValue,
|
||||||
},
|
},
|
||||||
children: React$Node,
|
children?: React$Node,
|
||||||
style?: AnimatedViewStyleProp,
|
style?: AnimatedViewStyleProp,
|
||||||
};
|
};
|
||||||
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
|
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
|
||||||
|
|
||||||
declare export var Header: React$ComponentType<HeaderProps>;
|
declare export var Header: React$ComponentType<HeaderProps> & {
|
||||||
|
HEIGHT: number,
|
||||||
|
};
|
||||||
|
|
||||||
declare type _HeaderTitleProps = {
|
declare type _HeaderTitleProps = {
|
||||||
children: React$Node,
|
children: React$Node,
|
||||||
@@ -965,6 +997,8 @@ declare module 'react-navigation' {
|
|||||||
itemsContainerStyle?: ViewStyleProp,
|
itemsContainerStyle?: ViewStyleProp,
|
||||||
itemStyle?: ViewStyleProp,
|
itemStyle?: ViewStyleProp,
|
||||||
labelStyle?: TextStyleProp,
|
labelStyle?: TextStyleProp,
|
||||||
|
activeLabelStyle?: TextStyleProp,
|
||||||
|
inactiveLabelStyle?: TextStyleProp,
|
||||||
iconContainerStyle?: ViewStyleProp,
|
iconContainerStyle?: ViewStyleProp,
|
||||||
drawerPosition: 'left' | 'right',
|
drawerPosition: 'left' | 'right',
|
||||||
};
|
};
|
||||||
@@ -1045,7 +1079,7 @@ declare module 'react-navigation' {
|
|||||||
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
||||||
|
|
||||||
declare type _NavigationInjectedProps = {
|
declare type _NavigationInjectedProps = {
|
||||||
navigation: NavigationScreenProp<NavigationState>,
|
navigation: NavigationScreenProp<NavigationStateRoute>,
|
||||||
};
|
};
|
||||||
declare export function withNavigation<T: {}>(
|
declare export function withNavigation<T: {}>(
|
||||||
Component: React$ComponentType<T & _NavigationInjectedProps>
|
Component: React$ComponentType<T & _NavigationInjectedProps>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-navigation",
|
"name": "react-navigation",
|
||||||
"version": "1.2.1",
|
"version": "1.5.6",
|
||||||
"description": "Routing and navigation for your React Native apps",
|
"description": "Routing and navigation for your React Native apps",
|
||||||
"main": "src/react-navigation.js",
|
"main": "src/react-navigation.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"hoist-non-react-statics": "^2.2.0",
|
"hoist-non-react-statics": "^2.2.0",
|
||||||
"path-to-regexp": "^1.7.0",
|
"path-to-regexp": "^1.7.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
|
"react-lifecycles-compat": "^1.0.2",
|
||||||
"react-native-drawer-layout-polyfill": "^1.3.2",
|
"react-native-drawer-layout-polyfill": "^1.3.2",
|
||||||
"react-native-safe-area-view": "^0.7.0",
|
"react-native-safe-area-view": "^0.7.0",
|
||||||
"react-native-tab-view": "^0.0.74"
|
"react-native-tab-view": "^0.0.74"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
||||||
* focus and blur events for this child
|
* focus and blur events for this child
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function getChildEventSubscriber(addListener, key) {
|
export default function getChildEventSubscriber(addListener, key) {
|
||||||
const actionSubscribers = new Set();
|
const actionSubscribers = new Set();
|
||||||
const willFocusSubscribers = new Set();
|
const willFocusSubscribers = new Set();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import NavigationActions from '../NavigationActions';
|
|||||||
|
|
||||||
export default (routeConfigMap, stackConfig = {}) => {
|
export default (routeConfigMap, stackConfig = {}) => {
|
||||||
const {
|
const {
|
||||||
|
initialRouteKey,
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
initialRouteParams,
|
initialRouteParams,
|
||||||
paths,
|
paths,
|
||||||
@@ -25,6 +26,7 @@ export default (routeConfigMap, stackConfig = {}) => {
|
|||||||
} = stackConfig;
|
} = stackConfig;
|
||||||
|
|
||||||
const stackRouterConfig = {
|
const stackRouterConfig = {
|
||||||
|
initialRouteKey,
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
initialRouteParams,
|
initialRouteParams,
|
||||||
paths,
|
paths,
|
||||||
@@ -47,7 +49,7 @@ export default (routeConfigMap, stackConfig = {}) => {
|
|||||||
onTransitionEnd={(lastTransition, transition) => {
|
onTransitionEnd={(lastTransition, transition) => {
|
||||||
const { state, dispatch } = props.navigation;
|
const { state, dispatch } = props.navigation;
|
||||||
dispatch(NavigationActions.completeTransition({ key: state.key }));
|
dispatch(NavigationActions.completeTransition({ key: state.key }));
|
||||||
onTransitionEnd && onTransitionEnd();
|
onTransitionEnd && onTransitionEnd(lastTransition, transition);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
15
src/navigators/SwitchNavigator.js
Normal file
15
src/navigators/SwitchNavigator.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SwitchRouter from '../routers/SwitchRouter';
|
||||||
|
import SwitchView from '../views/SwitchView/SwitchView';
|
||||||
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
|
import createNavigator from '../navigators/createNavigator';
|
||||||
|
|
||||||
|
export default (routeConfigMap, switchConfig = {}) => {
|
||||||
|
const router = SwitchRouter(routeConfigMap, switchConfig);
|
||||||
|
|
||||||
|
const navigator = createNavigator(router, routeConfigMap, switchConfig)(
|
||||||
|
props => <SwitchView {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return createNavigationContainer(navigator);
|
||||||
|
};
|
||||||
18
src/navigators/__tests__/SwitchNavigator-test.js
Normal file
18
src/navigators/__tests__/SwitchNavigator-test.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
|
import SwitchNavigator from '../SwitchNavigator';
|
||||||
|
|
||||||
|
const A = () => <View />;
|
||||||
|
const B = () => <View />;
|
||||||
|
const routeConfig = { A, B };
|
||||||
|
|
||||||
|
describe('SwitchNavigator', () => {
|
||||||
|
it('renders successfully', () => {
|
||||||
|
const MySwitchNavigator = SwitchNavigator(routeConfig);
|
||||||
|
const rendered = renderer.create(<MySwitchNavigator />).toJSON();
|
||||||
|
|
||||||
|
expect(rendered).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -224,6 +224,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
|
|||||||
"color": "#2196f3",
|
"color": "#2196f3",
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -80,10 +80,11 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
pointerEvents="box-none"
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "red",
|
||||||
"borderBottomColor": "#A7A7AA",
|
"borderBottomColor": "#A7A7AA",
|
||||||
"borderBottomWidth": 0.5,
|
"borderBottomWidth": 0.5,
|
||||||
"height": 64,
|
"height": 64,
|
||||||
|
"opacity": 0.5,
|
||||||
"paddingBottom": 0,
|
"paddingBottom": 0,
|
||||||
"paddingLeft": 0,
|
"paddingLeft": 0,
|
||||||
"paddingRight": 0,
|
"paddingRight": 0,
|
||||||
@@ -265,10 +266,11 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
pointerEvents="box-none"
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "red",
|
||||||
"borderBottomColor": "#A7A7AA",
|
"borderBottomColor": "#A7A7AA",
|
||||||
"borderBottomWidth": 0.5,
|
"borderBottomWidth": 0.5,
|
||||||
"height": 64,
|
"height": 64,
|
||||||
|
"opacity": 0.5,
|
||||||
"paddingBottom": 0,
|
"paddingBottom": 0,
|
||||||
"paddingLeft": 0,
|
"paddingLeft": 0,
|
||||||
"paddingRight": 0,
|
"paddingRight": 0,
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`SwitchNavigator renders successfully 1`] = `<View />`;
|
||||||
@@ -137,9 +137,12 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
Object {
|
Array [
|
||||||
"flexGrow": 1,
|
false,
|
||||||
}
|
Object {
|
||||||
|
"flexGrow": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -147,13 +150,12 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"bottom": 0,
|
"alignSelf": "center",
|
||||||
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"left": 0,
|
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"width": "100%",
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -162,13 +164,12 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"bottom": 0,
|
"alignSelf": "center",
|
||||||
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"left": 0,
|
|
||||||
"opacity": 0,
|
"opacity": 0,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"width": "100%",
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
11
src/react-navigation.js
vendored
11
src/react-navigation.js
vendored
@@ -22,6 +22,9 @@ module.exports = {
|
|||||||
get StackNavigator() {
|
get StackNavigator() {
|
||||||
return require('./navigators/StackNavigator').default;
|
return require('./navigators/StackNavigator').default;
|
||||||
},
|
},
|
||||||
|
get SwitchNavigator() {
|
||||||
|
return require('./navigators/SwitchNavigator').default;
|
||||||
|
},
|
||||||
get TabNavigator() {
|
get TabNavigator() {
|
||||||
return require('./navigators/TabNavigator').default;
|
return require('./navigators/TabNavigator').default;
|
||||||
},
|
},
|
||||||
@@ -36,6 +39,9 @@ module.exports = {
|
|||||||
get TabRouter() {
|
get TabRouter() {
|
||||||
return require('./routers/TabRouter').default;
|
return require('./routers/TabRouter').default;
|
||||||
},
|
},
|
||||||
|
get SwitchRouter() {
|
||||||
|
return require('./routers/SwitchRouter').default;
|
||||||
|
},
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
get Transitioner() {
|
get Transitioner() {
|
||||||
@@ -84,6 +90,11 @@ module.exports = {
|
|||||||
return require('./views/TabView/TabBarBottom').default;
|
return require('./views/TabView/TabBarBottom').default;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// SwitchView
|
||||||
|
get SwitchView() {
|
||||||
|
return require('./views/SwitchView/SwitchView').default;
|
||||||
|
},
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
get withNavigation() {
|
get withNavigation() {
|
||||||
return require('./views/withNavigation').default;
|
return require('./views/withNavigation').default;
|
||||||
|
|||||||
@@ -92,11 +92,12 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
...(action.params || {}),
|
...(action.params || {}),
|
||||||
...(initialRouteParams || {}),
|
...(initialRouteParams || {}),
|
||||||
};
|
};
|
||||||
|
const { initialRouteKey } = stackConfig;
|
||||||
route = {
|
route = {
|
||||||
...route,
|
...route,
|
||||||
...(params ? { params } : {}),
|
...(params ? { params } : {}),
|
||||||
routeName: initialRouteName,
|
routeName: initialRouteName,
|
||||||
key: action.key || generateKey(),
|
key: action.key || (initialRouteKey || generateKey()),
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
|
|||||||
360
src/routers/SwitchRouter.js
Normal file
360
src/routers/SwitchRouter.js
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
import invariant from '../utils/invariant';
|
||||||
|
import getScreenForRouteName from './getScreenForRouteName';
|
||||||
|
import createConfigGetter from './createConfigGetter';
|
||||||
|
|
||||||
|
import NavigationActions from '../NavigationActions';
|
||||||
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
|
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
|
||||||
|
|
||||||
|
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||||
|
return [
|
||||||
|
NavigationActions.SET_PARAMS,
|
||||||
|
NavigationActions.COMPLETE_TRANSITION,
|
||||||
|
].includes(actionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (routeConfigs, config = {}) => {
|
||||||
|
// Fail fast on invalid route definitions
|
||||||
|
validateRouteConfigMap(routeConfigs);
|
||||||
|
|
||||||
|
const order = config.order || Object.keys(routeConfigs);
|
||||||
|
const paths = config.paths || {};
|
||||||
|
const initialRouteParams = config.initialRouteParams;
|
||||||
|
const initialRouteName = config.initialRouteName || order[0];
|
||||||
|
const backBehavior = config.backBehavior || 'none';
|
||||||
|
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||||
|
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
||||||
|
? config.resetOnBlur
|
||||||
|
: true;
|
||||||
|
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||||
|
const childRouters = {};
|
||||||
|
|
||||||
|
order.forEach(routeName => {
|
||||||
|
const routeConfig = routeConfigs[routeName];
|
||||||
|
paths[routeName] =
|
||||||
|
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
||||||
|
childRouters[routeName] = null;
|
||||||
|
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||||
|
if (screen.router) {
|
||||||
|
childRouters[routeName] = screen.router;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (initialRouteIndex === -1) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||||
|
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetChildRoute(routeName) {
|
||||||
|
const params =
|
||||||
|
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||||
|
const childRouter = childRouters[routeName];
|
||||||
|
if (childRouter) {
|
||||||
|
const childAction = NavigationActions.init();
|
||||||
|
return {
|
||||||
|
...childRouter.getStateForAction(childAction),
|
||||||
|
key: routeName,
|
||||||
|
routeName,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: routeName,
|
||||||
|
routeName,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getInitialState() {
|
||||||
|
const routes = order.map(resetChildRoute);
|
||||||
|
return {
|
||||||
|
routes,
|
||||||
|
index: initialRouteIndex,
|
||||||
|
isTransitioning: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getNextState(prevState, possibleNextState) {
|
||||||
|
let nextState;
|
||||||
|
if (prevState.index !== possibleNextState.index && resetOnBlur) {
|
||||||
|
const prevRouteName = prevState.routes[prevState.index].routeName;
|
||||||
|
const nextRoutes = [...possibleNextState.routes];
|
||||||
|
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...possibleNextState,
|
||||||
|
routes: nextRoutes,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
nextState = possibleNextState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextState;
|
||||||
|
},
|
||||||
|
|
||||||
|
getStateForAction(action, inputState) {
|
||||||
|
let prevState = inputState ? { ...inputState } : inputState;
|
||||||
|
let state = inputState || this.getInitialState();
|
||||||
|
let activeChildIndex = state.index;
|
||||||
|
|
||||||
|
if (action.type === NavigationActions.INIT) {
|
||||||
|
// NOTE(brentvatne): this seems weird... why are we merging these
|
||||||
|
// params into child routes?
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Merge any params from the action into all the child routes
|
||||||
|
const { params } = action;
|
||||||
|
if (params) {
|
||||||
|
state.routes = state.routes.map(route => ({
|
||||||
|
...route,
|
||||||
|
params: {
|
||||||
|
...route.params,
|
||||||
|
...params,
|
||||||
|
...(route.routeName === initialRouteName
|
||||||
|
? initialRouteParams
|
||||||
|
: null),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the current child handle it
|
||||||
|
const activeChildLastState = state.routes[state.index];
|
||||||
|
const activeChildRouter = childRouters[order[state.index]];
|
||||||
|
if (activeChildRouter) {
|
||||||
|
const activeChildState = activeChildRouter.getStateForAction(
|
||||||
|
action,
|
||||||
|
activeChildLastState
|
||||||
|
);
|
||||||
|
if (!activeChildState && inputState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (activeChildState && activeChildState !== activeChildLastState) {
|
||||||
|
const routes = [...state.routes];
|
||||||
|
routes[state.index] = activeChildState;
|
||||||
|
return this.getNextState(prevState, {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tab changing. Do this after letting the current tab try to
|
||||||
|
// handle the action, to allow inner children to change first
|
||||||
|
if (backBehavior !== 'none') {
|
||||||
|
const isBackEligible =
|
||||||
|
action.key == null || action.key === activeChildLastState.key;
|
||||||
|
if (action.type === NavigationActions.BACK) {
|
||||||
|
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||||
|
activeChildIndex = initialRouteIndex;
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let didNavigate = false;
|
||||||
|
if (action.type === NavigationActions.NAVIGATE) {
|
||||||
|
const navigateAction = action;
|
||||||
|
didNavigate = !!order.find((childId, i) => {
|
||||||
|
if (childId === navigateAction.routeName) {
|
||||||
|
activeChildIndex = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (didNavigate) {
|
||||||
|
const childState = state.routes[activeChildIndex];
|
||||||
|
let newChildState;
|
||||||
|
|
||||||
|
const childRouter = childRouters[action.routeName];
|
||||||
|
|
||||||
|
if (action.action) {
|
||||||
|
newChildState = childRouter
|
||||||
|
? childRouter.getStateForAction(action.action, childState)
|
||||||
|
: null;
|
||||||
|
} else if (!childRouter && action.params) {
|
||||||
|
newChildState = {
|
||||||
|
...childState,
|
||||||
|
params: {
|
||||||
|
...(childState.params || {}),
|
||||||
|
...action.params,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChildState && newChildState !== childState) {
|
||||||
|
const routes = [...state.routes];
|
||||||
|
routes[activeChildIndex] = newChildState;
|
||||||
|
return this.getNextState(prevState, {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
index: activeChildIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === NavigationActions.SET_PARAMS) {
|
||||||
|
const key = action.key;
|
||||||
|
const lastRoute = state.routes.find(route => route.key === key);
|
||||||
|
if (lastRoute) {
|
||||||
|
const params = {
|
||||||
|
...lastRoute.params,
|
||||||
|
...action.params,
|
||||||
|
};
|
||||||
|
const routes = [...state.routes];
|
||||||
|
routes[state.routes.indexOf(lastRoute)] = {
|
||||||
|
...lastRoute,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
return this.getNextState(prevState, {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeChildIndex !== state.index) {
|
||||||
|
return this.getNextState(prevState, {
|
||||||
|
...state,
|
||||||
|
index: activeChildIndex,
|
||||||
|
});
|
||||||
|
} else if (didNavigate && !inputState) {
|
||||||
|
return state;
|
||||||
|
} else if (didNavigate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let other children handle it and switch to the first child that returns a new state
|
||||||
|
let index = state.index;
|
||||||
|
let routes = state.routes;
|
||||||
|
order.find((childId, i) => {
|
||||||
|
const childRouter = childRouters[childId];
|
||||||
|
if (i === index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let childState = routes[i];
|
||||||
|
if (childRouter) {
|
||||||
|
childState = childRouter.getStateForAction(action, childState);
|
||||||
|
}
|
||||||
|
if (!childState) {
|
||||||
|
index = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (childState !== routes[i]) {
|
||||||
|
routes = [...routes];
|
||||||
|
routes[i] = childState;
|
||||||
|
index = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nested routers can be updated after switching children with actions such as SET_PARAMS
|
||||||
|
// and COMPLETE_TRANSITION.
|
||||||
|
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
||||||
|
// that can be handled by child routers without automatically changing index.
|
||||||
|
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
||||||
|
index = state.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index !== state.index || routes !== state.routes) {
|
||||||
|
return this.getNextState(prevState, {
|
||||||
|
...state,
|
||||||
|
index,
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
getComponentForState(state) {
|
||||||
|
const routeName = state.routes[state.index].routeName;
|
||||||
|
invariant(
|
||||||
|
routeName,
|
||||||
|
`There is no route defined for index ${state.index}. Check that
|
||||||
|
that you passed in a navigation state with a valid tab/screen index.`
|
||||||
|
);
|
||||||
|
const childRouter = childRouters[routeName];
|
||||||
|
if (childRouter) {
|
||||||
|
return childRouter.getComponentForState(state.routes[state.index]);
|
||||||
|
}
|
||||||
|
return getScreenForRouteName(routeConfigs, routeName);
|
||||||
|
},
|
||||||
|
|
||||||
|
getComponentForRouteName(routeName) {
|
||||||
|
return getScreenForRouteName(routeConfigs, routeName);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPathAndParamsForState(state) {
|
||||||
|
const route = state.routes[state.index];
|
||||||
|
const routeName = order[state.index];
|
||||||
|
const subPath = paths[routeName];
|
||||||
|
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||||
|
let path = subPath;
|
||||||
|
let params = route.params;
|
||||||
|
if (screen && screen.router) {
|
||||||
|
const stateRoute = route;
|
||||||
|
// If it has a router it's a navigator.
|
||||||
|
// If it doesn't have router it's an ordinary React component.
|
||||||
|
const child = screen.router.getPathAndParamsForState(stateRoute);
|
||||||
|
path = subPath ? `${subPath}/${child.path}` : child.path;
|
||||||
|
params = child.params ? { ...params, ...child.params } : params;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an optional action, based on a relative path and query params.
|
||||||
|
*
|
||||||
|
* This will return null if there is no action matched
|
||||||
|
*/
|
||||||
|
getActionForPathAndParams(path, params) {
|
||||||
|
return (
|
||||||
|
order
|
||||||
|
.map(childId => {
|
||||||
|
const parts = path.split('/');
|
||||||
|
const pathToTest = paths[childId];
|
||||||
|
if (parts[0] === pathToTest) {
|
||||||
|
const childRouter = childRouters[childId];
|
||||||
|
const action = NavigationActions.navigate({
|
||||||
|
routeName: childId,
|
||||||
|
});
|
||||||
|
if (childRouter && childRouter.getActionForPathAndParams) {
|
||||||
|
action.action = childRouter.getActionForPathAndParams(
|
||||||
|
parts.slice(1).join('/'),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
} else if (params) {
|
||||||
|
action.params = params;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.find(action => !!action) ||
|
||||||
|
order
|
||||||
|
.map(childId => {
|
||||||
|
const childRouter = childRouters[childId];
|
||||||
|
return (
|
||||||
|
childRouter && childRouter.getActionForPathAndParams(path, params)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.find(action => !!action) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getScreenOptions: createConfigGetter(
|
||||||
|
routeConfigs,
|
||||||
|
config.navigationOptions
|
||||||
|
),
|
||||||
|
|
||||||
|
getScreenConfig: getScreenConfigDeprecated,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -576,6 +576,23 @@ describe('StackRouter', () => {
|
|||||||
expect(state2.routes[1].routes[1].routes[1].routeName).toEqual('Corge');
|
expect(state2.routes[1].routes[1].routes[1].routeName).toEqual('Corge');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Navigate to initial screen is possible', () => {
|
||||||
|
const TestRouter = StackRouter(
|
||||||
|
{
|
||||||
|
foo: { screen: () => <div /> },
|
||||||
|
bar: { screen: () => <div /> },
|
||||||
|
},
|
||||||
|
{ initialRouteKey: 'foo' }
|
||||||
|
);
|
||||||
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
|
const pushedState = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.navigate({ routeName: 'foo', key: 'foo' }),
|
||||||
|
initState
|
||||||
|
);
|
||||||
|
expect(pushedState.index).toEqual(0);
|
||||||
|
expect(pushedState.routes[0].routeName).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
test('Navigate with key is idempotent', () => {
|
test('Navigate with key is idempotent', () => {
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
|
|||||||
109
src/routers/__tests__/SwitchRouter-test.js
Normal file
109
src/routers/__tests__/SwitchRouter-test.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* eslint react/display-name:0 */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import SwitchRouter from '../SwitchRouter';
|
||||||
|
import StackRouter from '../StackRouter';
|
||||||
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
|
||||||
|
describe('SwitchRouter', () => {
|
||||||
|
test('resets the route when unfocusing a tab by default', () => {
|
||||||
|
const router = getExampleRouter();
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
|
const state2 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(state2.routes[0].index).toEqual(1);
|
||||||
|
expect(state2.routes[0].routes.length).toEqual(2);
|
||||||
|
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state3.routes[0].index).toEqual(0);
|
||||||
|
expect(state3.routes[0].routes.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not reset the route on unfocus if resetOnBlur is false', () => {
|
||||||
|
const router = getExampleRouter({ resetOnBlur: false });
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
|
const state2 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(state2.routes[0].index).toEqual(1);
|
||||||
|
expect(state2.routes[0].routes.length).toEqual(2);
|
||||||
|
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state3.routes[0].index).toEqual(1);
|
||||||
|
expect(state3.routes[0].routes.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores back by default', () => {
|
||||||
|
const router = getExampleRouter();
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
|
const state2 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(state2.index).toEqual(1);
|
||||||
|
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.BACK },
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state3.index).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles back if given a backBehavior', () => {
|
||||||
|
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
|
const state2 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(state2.index).toEqual(1);
|
||||||
|
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{ type: NavigationActions.BACK },
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state3.index).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const getExampleRouter = (config = {}) => {
|
||||||
|
const PlainScreen = () => <div />;
|
||||||
|
const StackA = () => <div />;
|
||||||
|
const StackB = () => <div />;
|
||||||
|
|
||||||
|
StackA.router = StackRouter({
|
||||||
|
A1: PlainScreen,
|
||||||
|
A2: PlainScreen,
|
||||||
|
});
|
||||||
|
|
||||||
|
StackB.router = StackRouter({
|
||||||
|
B1: PlainScreen,
|
||||||
|
B2: PlainScreen,
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = SwitchRouter(
|
||||||
|
{
|
||||||
|
A: StackA,
|
||||||
|
B: StackB,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialRouteName: 'A',
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`validateRouteConfigMap Fails if both screen and getScreen are defined 1`] = `"Route 'Home' should declare a screen or a getScreen, not both."`;
|
||||||
|
|
||||||
|
exports[`validateRouteConfigMap Fails on bad object 1`] = `
|
||||||
|
"The component for route 'Home' must be a React component. For example:
|
||||||
|
|
||||||
|
import MyScreen from './MyScreen';
|
||||||
|
...
|
||||||
|
Home: MyScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also use a navigator:
|
||||||
|
|
||||||
|
import MyNavigator from './MyNavigator';
|
||||||
|
...
|
||||||
|
Home: MyNavigator,
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateRouteConfigMap Fails on empty bare screen 1`] = `
|
||||||
|
"The component for route 'Home' must be a React component. For example:
|
||||||
|
|
||||||
|
import MyScreen from './MyScreen';
|
||||||
|
...
|
||||||
|
Home: MyScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also use a navigator:
|
||||||
|
|
||||||
|
import MyNavigator from './MyNavigator';
|
||||||
|
...
|
||||||
|
Home: MyNavigator,
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateRouteConfigMap Fails on empty config 1`] = `"Please specify at least one route when configuring a navigator."`;
|
||||||
@@ -13,9 +13,19 @@ ProfileNavigator.router = StackRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('validateRouteConfigMap', () => {
|
describe('validateRouteConfigMap', () => {
|
||||||
|
test('Fails on empty bare screen', () => {
|
||||||
|
const invalidMap = {
|
||||||
|
Home: undefined,
|
||||||
|
};
|
||||||
|
expect(() =>
|
||||||
|
validateRouteConfigMap(invalidMap)
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
test('Fails on empty config', () => {
|
test('Fails on empty config', () => {
|
||||||
const invalidMap = {};
|
const invalidMap = {};
|
||||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
expect(() =>
|
||||||
|
validateRouteConfigMap(invalidMap)
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
test('Fails on bad object', () => {
|
test('Fails on bad object', () => {
|
||||||
const invalidMap = {
|
const invalidMap = {
|
||||||
@@ -23,7 +33,9 @@ describe('validateRouteConfigMap', () => {
|
|||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
expect(() =>
|
||||||
|
validateRouteConfigMap(invalidMap)
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
test('Fails if both screen and getScreen are defined', () => {
|
test('Fails if both screen and getScreen are defined', () => {
|
||||||
const invalidMap = {
|
const invalidMap = {
|
||||||
@@ -32,15 +44,17 @@ describe('validateRouteConfigMap', () => {
|
|||||||
getScreen: () => ListScreen,
|
getScreen: () => ListScreen,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
expect(() =>
|
||||||
|
validateRouteConfigMap(invalidMap)
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
test('Succeeds on a valid config', () => {
|
test('Succeeds on a valid config', () => {
|
||||||
const invalidMap = {
|
const validMap = {
|
||||||
Home: {
|
Home: {
|
||||||
screen: ProfileNavigator,
|
screen: ProfileNavigator,
|
||||||
},
|
},
|
||||||
Chat: ListScreen,
|
Chat: ListScreen,
|
||||||
};
|
};
|
||||||
validateRouteConfigMap(invalidMap);
|
validateRouteConfigMap(validMap);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ function validateRouteConfigMap(routeConfigs) {
|
|||||||
|
|
||||||
routeNames.forEach(routeName => {
|
routeNames.forEach(routeName => {
|
||||||
const routeConfig = routeConfigs[routeName];
|
const routeConfig = routeConfigs[routeName];
|
||||||
|
const screenComponent = getScreenComponent(routeConfig);
|
||||||
const screenComponent = routeConfig.screen
|
|
||||||
? routeConfig.screen
|
|
||||||
: routeConfig;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
screenComponent &&
|
!screenComponent ||
|
||||||
typeof screenComponent !== 'function' &&
|
(typeof screenComponent !== 'function' &&
|
||||||
typeof screenComponent !== 'string' &&
|
typeof screenComponent !== 'string' &&
|
||||||
!routeConfig.getScreen
|
!routeConfig.getScreen)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The component for route '${routeName}' must be a ` +
|
`The component for route '${routeName}' must be a ` +
|
||||||
@@ -48,4 +45,12 @@ function validateRouteConfigMap(routeConfigs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getScreenComponent(routeConfig) {
|
||||||
|
if (!routeConfig) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return routeConfig.screen ? routeConfig.screen : routeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
export default validateRouteConfigMap;
|
export default validateRouteConfigMap;
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ class CardStack extends React.Component {
|
|||||||
|
|
||||||
_screenDetails = {};
|
_screenDetails = {};
|
||||||
|
|
||||||
|
_childEventSubscribers = {};
|
||||||
|
|
||||||
componentWillReceiveProps(props) {
|
componentWillReceiveProps(props) {
|
||||||
if (props.screenProps !== this.props.screenProps) {
|
if (props.screenProps !== this.props.screenProps) {
|
||||||
this._screenDetails = {};
|
this._screenDetails = {};
|
||||||
@@ -96,17 +98,39 @@ class CardStack extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const activeKeys = this.props.transitionProps.navigation.state.routes.map(
|
||||||
|
route => route.key
|
||||||
|
);
|
||||||
|
Object.keys(this._childEventSubscribers).forEach(key => {
|
||||||
|
if (!activeKeys.includes(key)) {
|
||||||
|
delete this._childEventSubscribers[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRouteFocused = route => {
|
||||||
|
const { transitionProps: { navigation: { state } } } = this.props;
|
||||||
|
const focusedRoute = state.routes[state.index];
|
||||||
|
return route === focusedRoute;
|
||||||
|
};
|
||||||
|
|
||||||
_getScreenDetails = scene => {
|
_getScreenDetails = scene => {
|
||||||
const { screenProps, transitionProps: { navigation }, router } = this.props;
|
const { screenProps, transitionProps: { navigation }, router } = this.props;
|
||||||
let screenDetails = this._screenDetails[scene.key];
|
let screenDetails = this._screenDetails[scene.key];
|
||||||
if (!screenDetails || screenDetails.state !== scene.route) {
|
if (!screenDetails || screenDetails.state !== scene.route) {
|
||||||
|
if (!this._childEventSubscribers[scene.route.key]) {
|
||||||
|
this._childEventSubscribers[scene.route.key] = getChildEventSubscriber(
|
||||||
|
navigation.addListener,
|
||||||
|
scene.route.key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const screenNavigation = addNavigationHelpers({
|
const screenNavigation = addNavigationHelpers({
|
||||||
dispatch: navigation.dispatch,
|
dispatch: navigation.dispatch,
|
||||||
state: scene.route,
|
state: scene.route,
|
||||||
addListener: getChildEventSubscriber(
|
isFocused: () => this._isRouteFocused(scene.route),
|
||||||
navigation.addListener,
|
addListener: this._childEventSubscribers[scene.route.key],
|
||||||
scene.route.key
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
screenDetails = {
|
screenDetails = {
|
||||||
state: scene.route,
|
state: scene.route,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const DrawerNavigatorItems = ({
|
|||||||
itemsContainerStyle,
|
itemsContainerStyle,
|
||||||
itemStyle,
|
itemStyle,
|
||||||
labelStyle,
|
labelStyle,
|
||||||
|
activeLabelStyle,
|
||||||
|
inactiveLabelStyle,
|
||||||
iconContainerStyle,
|
iconContainerStyle,
|
||||||
drawerPosition,
|
drawerPosition,
|
||||||
}) => (
|
}) => (
|
||||||
@@ -34,6 +36,7 @@ const DrawerNavigatorItems = ({
|
|||||||
const scene = { route, index, focused, tintColor: color };
|
const scene = { route, index, focused, tintColor: color };
|
||||||
const icon = renderIcon(scene);
|
const icon = renderIcon(scene);
|
||||||
const label = getLabel(scene);
|
const label = getLabel(scene);
|
||||||
|
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
|
||||||
return (
|
return (
|
||||||
<TouchableItem
|
<TouchableItem
|
||||||
key={route.key}
|
key={route.key}
|
||||||
@@ -63,7 +66,9 @@ const DrawerNavigatorItems = ({
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
{typeof label === 'string' ? (
|
{typeof label === 'string' ? (
|
||||||
<Text style={[styles.label, { color }, labelStyle]}>
|
<Text
|
||||||
|
style={[styles.label, { color }, labelStyle, extraLabelStyle]}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
: this.props.drawerWidth,
|
: this.props.drawerWidth,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_childEventSubscribers = {};
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._updateScreenNavigation(this.props.navigation);
|
this._updateScreenNavigation(this.props.navigation);
|
||||||
|
|
||||||
@@ -27,6 +29,17 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
Dimensions.removeEventListener('change', this._updateWidth);
|
Dimensions.removeEventListener('change', this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const activeKeys = this.props.navigation.state.routes.map(
|
||||||
|
route => route.key
|
||||||
|
);
|
||||||
|
Object.keys(this._childEventSubscribers).forEach(key => {
|
||||||
|
if (!activeKeys.includes(key)) {
|
||||||
|
delete this._childEventSubscribers[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (
|
if (
|
||||||
this.props.navigation.state.index !== nextProps.navigation.state.index
|
this.props.navigation.state.index !== nextProps.navigation.state.index
|
||||||
@@ -68,6 +81,12 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_isRouteFocused = route => () => {
|
||||||
|
const { state } = this.props.navigation;
|
||||||
|
const focusedRoute = state.routes[state.index];
|
||||||
|
return route === focusedRoute;
|
||||||
|
};
|
||||||
|
|
||||||
_updateScreenNavigation = navigation => {
|
_updateScreenNavigation = navigation => {
|
||||||
const { drawerCloseRoute } = this.props;
|
const { drawerCloseRoute } = this.props;
|
||||||
const navigationState = navigation.state.routes.find(
|
const navigationState = navigation.state.routes.find(
|
||||||
@@ -79,13 +98,18 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._childEventSubscribers[navigationState.key]) {
|
||||||
|
this._childEventSubscribers[
|
||||||
|
navigationState.key
|
||||||
|
] = getChildEventSubscriber(navigation.addListener, navigationState.key);
|
||||||
|
}
|
||||||
|
|
||||||
this._screenNavigationProp = addNavigationHelpers({
|
this._screenNavigationProp = addNavigationHelpers({
|
||||||
dispatch: navigation.dispatch,
|
dispatch: navigation.dispatch,
|
||||||
state: navigationState,
|
state: navigationState,
|
||||||
addListener: getChildEventSubscriber(
|
isFocused: () => this._isRouteFocused(navigationState),
|
||||||
navigation.addListener,
|
addListener: this._childEventSubscribers[navigationState.key],
|
||||||
navigationState.key
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -476,11 +476,11 @@ class Header extends React.PureComponent {
|
|||||||
safeHeaderStyle,
|
safeHeaderStyle,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const { headerForceInset } = options;
|
||||||
|
const forceInset = headerForceInset || { top: 'always', bottom: 'never' };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
||||||
forceInset={{ top: 'always', bottom: 'never' }}
|
|
||||||
style={containerStyles}
|
|
||||||
>
|
|
||||||
<View style={StyleSheet.absoluteFill}>{options.headerBackground}</View>
|
<View style={StyleSheet.absoluteFill}>{options.headerBackground}</View>
|
||||||
<View style={{ flex: 1 }}>{appBar}</View>
|
<View style={{ flex: 1 }}>{appBar}</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|||||||
@@ -1,40 +1,33 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import withLifecyclePolyfill from 'react-lifecycles-compat';
|
||||||
|
|
||||||
import SceneView from './SceneView';
|
import SceneView from './SceneView';
|
||||||
|
|
||||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||||
|
|
||||||
export default class ResourceSavingSceneView extends React.PureComponent {
|
class ResourceSavingSceneView extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const key = props.childNavigation.state.key;
|
|
||||||
const focusedIndex = props.navigation.state.index;
|
|
||||||
const focusedKey = props.navigation.state.routes[focusedIndex].key;
|
|
||||||
const isFocused = key === focusedKey;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
awake: props.lazy ? isFocused : true,
|
awake: props.lazy ? props.isFocused : true,
|
||||||
visible: isFocused,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
this._actionListener = this.props.navigation.addListener(
|
if (nextProps.isFocused && !prevState.awake) {
|
||||||
'action',
|
return { awake: true };
|
||||||
this._onAction
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
return null;
|
||||||
this._actionListener.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { awake, visible } = this.state;
|
const { awake } = this.state;
|
||||||
const {
|
const {
|
||||||
|
isFocused,
|
||||||
childNavigation,
|
childNavigation,
|
||||||
navigation,
|
navigation,
|
||||||
removeClippedSubviews,
|
removeClippedSubviews,
|
||||||
@@ -49,12 +42,12 @@ export default class ResourceSavingSceneView extends React.PureComponent {
|
|||||||
removeClippedSubviews={
|
removeClippedSubviews={
|
||||||
Platform.OS === 'android'
|
Platform.OS === 'android'
|
||||||
? removeClippedSubviews
|
? removeClippedSubviews
|
||||||
: !visible && removeClippedSubviews
|
: !isFocused && removeClippedSubviews
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
this._mustAlwaysBeVisible() || visible
|
this._mustAlwaysBeVisible() || isFocused
|
||||||
? styles.innerAttached
|
? styles.innerAttached
|
||||||
: styles.innerDetached
|
: styles.innerDetached
|
||||||
}
|
}
|
||||||
@@ -68,33 +61,6 @@ export default class ResourceSavingSceneView extends React.PureComponent {
|
|||||||
_mustAlwaysBeVisible = () => {
|
_mustAlwaysBeVisible = () => {
|
||||||
return this.props.animationEnabled || this.props.swipeEnabled;
|
return this.props.animationEnabled || this.props.swipeEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
_onAction = payload => {
|
|
||||||
// We do not care about transition complete events, they won't actually change the state
|
|
||||||
if (
|
|
||||||
payload.action.type == 'Navigation/COMPLETE_TRANSITION' ||
|
|
||||||
!payload.state
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { routes, index } = payload.state;
|
|
||||||
const key = this.props.childNavigation.state.key;
|
|
||||||
|
|
||||||
if (routes[index].key === key) {
|
|
||||||
if (!this.state.visible) {
|
|
||||||
let nextState = { visible: true };
|
|
||||||
if (!this.state.awake) {
|
|
||||||
nextState.awake = true;
|
|
||||||
}
|
|
||||||
this.setState(nextState);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.state.visible) {
|
|
||||||
this.setState({ visible: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@@ -110,3 +76,5 @@ const styles = StyleSheet.create({
|
|||||||
top: FAR_FAR_AWAY,
|
top: FAR_FAR_AWAY,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default withLifecyclePolyfill(ResourceSavingSceneView);
|
||||||
|
|||||||
27
src/views/SwitchView/SwitchView.js
Normal file
27
src/views/SwitchView/SwitchView.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SceneView from '../SceneView';
|
||||||
|
import withCachedChildNavigation from '../../withCachedChildNavigation';
|
||||||
|
|
||||||
|
class SwitchContainer extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { screenProps } = this.props;
|
||||||
|
|
||||||
|
const route = this.props.navigation.state.routes[
|
||||||
|
this.props.navigation.state.index
|
||||||
|
];
|
||||||
|
const childNavigation = this.props.childNavigationProps[route.key];
|
||||||
|
const ChildComponent = this.props.router.getComponentForRouteName(
|
||||||
|
route.routeName
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SceneView
|
||||||
|
component={ChildComponent}
|
||||||
|
navigation={childNavigation}
|
||||||
|
screenProps={screenProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withCachedChildNavigation(SwitchContainer);
|
||||||
@@ -100,6 +100,9 @@ class TabBarBottom extends React.PureComponent {
|
|||||||
if (showIcon === false) {
|
if (showIcon === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const horizontal = this._shouldUseHorizontalTabs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabBarIcon
|
<TabBarIcon
|
||||||
position={position}
|
position={position}
|
||||||
@@ -108,7 +111,10 @@ class TabBarBottom extends React.PureComponent {
|
|||||||
inactiveTintColor={inactiveTintColor}
|
inactiveTintColor={inactiveTintColor}
|
||||||
renderIcon={renderIcon}
|
renderIcon={renderIcon}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
style={showLabel && this._shouldUseHorizontalTabs() ? {} : styles.icon}
|
style={[
|
||||||
|
horizontal && styles.horizontalIcon,
|
||||||
|
showLabel !== false && !horizontal && styles.icon,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -286,6 +292,9 @@ class TabBarBottom extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HEIGHT = 49;
|
||||||
|
const COMPACT_HEIGHT = 29;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
tabBar: {
|
tabBar: {
|
||||||
backgroundColor: '#F7F7F7', // Default background color in iOS 10
|
backgroundColor: '#F7F7F7', // Default background color in iOS 10
|
||||||
@@ -294,10 +303,10 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
tabBarCompact: {
|
tabBarCompact: {
|
||||||
height: 29,
|
height: COMPACT_HEIGHT,
|
||||||
},
|
},
|
||||||
tabBarRegular: {
|
tabBarRegular: {
|
||||||
height: 49,
|
height: DEFAULT_HEIGHT,
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -314,6 +323,9 @@ const styles = StyleSheet.create({
|
|||||||
icon: {
|
icon: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
|
horizontalIcon: {
|
||||||
|
height: Platform.isPad ? DEFAULT_HEIGHT : COMPACT_HEIGHT,
|
||||||
|
},
|
||||||
label: {
|
label: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default class TabBarIcon extends React.PureComponent {
|
|||||||
inputRange,
|
inputRange,
|
||||||
outputRange: inputRange.map(i => (i === index ? 0 : 1)),
|
outputRange: inputRange.map(i => (i === index ? 0 : 1)),
|
||||||
});
|
});
|
||||||
|
|
||||||
// We render the icon twice at the same position on top of each other:
|
// We render the icon twice at the same position on top of each other:
|
||||||
// active and inactive one, so we can fade between them.
|
// active and inactive one, so we can fade between them.
|
||||||
return (
|
return (
|
||||||
@@ -53,12 +54,11 @@ const styles = StyleSheet.create({
|
|||||||
// We render the icon twice at the same position on top of each other:
|
// We render the icon twice at the same position on top of each other:
|
||||||
// active and inactive one, so we can fade between them:
|
// active and inactive one, so we can fade between them:
|
||||||
// Cover the whole iconContainer:
|
// Cover the whole iconContainer:
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
alignSelf: 'center',
|
||||||
|
height: '100%',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ class TabView extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_renderScene = ({ route }) => {
|
_renderScene = ({ route }) => {
|
||||||
const { screenProps } = this.props;
|
const { screenProps, navigation } = this.props;
|
||||||
|
const focusedIndex = navigation.state.index;
|
||||||
|
const focusedKey = navigation.state.routes[focusedIndex].key;
|
||||||
|
const key = route.key;
|
||||||
const childNavigation = this.props.childNavigationProps[route.key];
|
const childNavigation = this.props.childNavigationProps[route.key];
|
||||||
const TabComponent = this.props.router.getComponentForRouteName(
|
const TabComponent = this.props.router.getComponentForRouteName(
|
||||||
route.routeName
|
route.routeName
|
||||||
@@ -31,6 +34,7 @@ class TabView extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<ResourceSavingSceneView
|
<ResourceSavingSceneView
|
||||||
lazy={this.props.lazy}
|
lazy={this.props.lazy}
|
||||||
|
isFocused={focusedKey === key}
|
||||||
removeClippedSubViews={this.props.removeClippedSubviews}
|
removeClippedSubViews={this.props.removeClippedSubviews}
|
||||||
animationEnabled={this.props.animationEnabled}
|
animationEnabled={this.props.animationEnabled}
|
||||||
swipeEnabled={this.props.swipeEnabled}
|
swipeEnabled={this.props.swipeEnabled}
|
||||||
|
|||||||
@@ -83,9 +83,12 @@ exports[`TabBarBottom renders successfully 1`] = `
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
Object {
|
Array [
|
||||||
"flexGrow": 1,
|
false,
|
||||||
}
|
Object {
|
||||||
|
"flexGrow": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -93,13 +96,12 @@ exports[`TabBarBottom renders successfully 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"bottom": 0,
|
"alignSelf": "center",
|
||||||
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"left": 0,
|
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"width": "100%",
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -108,13 +110,12 @@ exports[`TabBarBottom renders successfully 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"bottom": 0,
|
"alignSelf": "center",
|
||||||
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"left": 0,
|
|
||||||
"opacity": 0,
|
"opacity": 0,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"width": "100%",
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -248,6 +249,7 @@ exports[`TabBarBottom renders successfully 1`] = `
|
|||||||
"dispatch": undefined,
|
"dispatch": undefined,
|
||||||
"getParam": [Function],
|
"getParam": [Function],
|
||||||
"goBack": [Function],
|
"goBack": [Function],
|
||||||
|
"isFocused": [Function],
|
||||||
"navigate": [Function],
|
"navigate": [Function],
|
||||||
"pop": [Function],
|
"pop": [Function],
|
||||||
"popToTop": [Function],
|
"popToTop": [Function],
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ export default function withNavigationFocus(Component) {
|
|||||||
navigation: propTypes.object.isRequired,
|
navigation: propTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
constructor(props, context) {
|
||||||
isFocused: false,
|
super();
|
||||||
};
|
|
||||||
|
this.state = {
|
||||||
|
isFocused: this.getNavigation(props, context).isFocused(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const navigation = this.getNavigation();
|
const navigation = this.getNavigation();
|
||||||
@@ -32,8 +36,8 @@ export default function withNavigationFocus(Component) {
|
|||||||
this.subscriptions.forEach(sub => sub.remove());
|
this.subscriptions.forEach(sub => sub.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
getNavigation = () => {
|
getNavigation = (props = this.props, context = this.context) => {
|
||||||
const navigation = this.props.navigation || this.context.navigation;
|
const navigation = props.navigation || context.navigation;
|
||||||
invariant(
|
invariant(
|
||||||
!!navigation,
|
!!navigation,
|
||||||
'withNavigationFocus can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'
|
'withNavigationFocus can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export default function withCachedChildNavigation(Comp) {
|
|||||||
return class extends React.PureComponent {
|
return class extends React.PureComponent {
|
||||||
static displayName = `withCachedChildNavigation(${displayName})`;
|
static displayName = `withCachedChildNavigation(${displayName})`;
|
||||||
|
|
||||||
|
_childEventSubscribers = {};
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._updateNavigationProps(this.props.navigation);
|
this._updateNavigationProps(this.props.navigation);
|
||||||
}
|
}
|
||||||
@@ -18,6 +20,23 @@ export default function withCachedChildNavigation(Comp) {
|
|||||||
this._updateNavigationProps(nextProps.navigation);
|
this._updateNavigationProps(nextProps.navigation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const activeKeys = this.props.navigation.state.routes.map(
|
||||||
|
route => route.key
|
||||||
|
);
|
||||||
|
Object.keys(this._childEventSubscribers).forEach(key => {
|
||||||
|
if (!activeKeys.includes(key)) {
|
||||||
|
delete this._childEventSubscribers[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRouteFocused = route => {
|
||||||
|
const { state } = this.props.navigation;
|
||||||
|
const focusedRoute = state.routes[state.index];
|
||||||
|
return route === focusedRoute;
|
||||||
|
};
|
||||||
|
|
||||||
_updateNavigationProps = navigation => {
|
_updateNavigationProps = navigation => {
|
||||||
// Update props for each child route
|
// Update props for each child route
|
||||||
if (!this._childNavigationProps) {
|
if (!this._childNavigationProps) {
|
||||||
@@ -28,13 +47,19 @@ export default function withCachedChildNavigation(Comp) {
|
|||||||
if (childNavigation && childNavigation.state === route) {
|
if (childNavigation && childNavigation.state === route) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._childEventSubscribers[route.key]) {
|
||||||
|
this._childEventSubscribers[route.key] = getChildEventSubscriber(
|
||||||
|
navigation.addListener,
|
||||||
|
route.key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this._childNavigationProps[route.key] = addNavigationHelpers({
|
this._childNavigationProps[route.key] = addNavigationHelpers({
|
||||||
dispatch: navigation.dispatch,
|
dispatch: navigation.dispatch,
|
||||||
state: route,
|
state: route,
|
||||||
addListener: getChildEventSubscriber(
|
isFocused: () => this._isRouteFocused(route),
|
||||||
navigation.addListener,
|
addListener: this._childEventSubscribers[route.key],
|
||||||
route.key
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4424,6 +4424,10 @@ react-devtools-core@3.0.0:
|
|||||||
shell-quote "^1.6.1"
|
shell-quote "^1.6.1"
|
||||||
ws "^2.0.3"
|
ws "^2.0.3"
|
||||||
|
|
||||||
|
react-lifecycles-compat@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.0.2.tgz#551d8b1d156346e5fcf30ffac9b32ce3f78b8850"
|
||||||
|
|
||||||
react-native-dismiss-keyboard@1.0.0:
|
react-native-dismiss-keyboard@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz#32886242b3f2317e121f3aeb9b0a585e2b879b49"
|
resolved "https://registry.yarnpkg.com/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz#32886242b3f2317e121f3aeb9b0a585e2b879b49"
|
||||||
|
|||||||
Reference in New Issue
Block a user