mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-22 11:58:16 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9fbffbfa0 | ||
|
|
1b8422d77d | ||
|
|
e27afea7ab | ||
|
|
ca6cd89118 |
@@ -15,26 +15,9 @@ jobs:
|
||||
- v3-react-navigation-master
|
||||
- run: yarn # install root deps
|
||||
- run: ./scripts/test.sh # run tests
|
||||
- setup_remote_docker
|
||||
- deploy:
|
||||
command: |
|
||||
set -x
|
||||
VER="17.03.0-ce"
|
||||
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
|
||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
||||
mv /tmp/docker/* /usr/bin
|
||||
|
||||
yarn global add exp
|
||||
set +x
|
||||
exp login -u "$EXPO_USERNAME" -p "$EXPO_PASSWORD"
|
||||
set -x
|
||||
cd examples/NavigationPlayground && yarn && exp publish --release-channel "${CIRCLE_SHA1}"
|
||||
- save_cache:
|
||||
key: v3-react-navigation-{{ .Branch }}-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- ~/react-navigation/examples/NavigationPlayground/node_modules
|
||||
- ~/react-navigation/examples/ReduxExample/node_modules
|
||||
notify:
|
||||
webhooks:
|
||||
- url: https://react-navigation-ci.now.sh
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -9,8 +9,6 @@ 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)
|
||||
- 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
|
||||
|
||||
@@ -45,8 +45,8 @@ This library is a community effort: it can only be great if we all help out in o
|
||||
* Creating public example repositories or [Snacks](https://snack.expo.io/) of navigation problems you have solved and sharing the links in [Community Resources](https://github.com/react-navigation/react-navigation/blob/master/COMMUNITY_RESOURCES.md).
|
||||
* Answering questions on [Stack Overflow](https://stackoverflow.com/search?q=react-navigation).
|
||||
* Answering questions in our [Reactiflux](https://www.reactiflux.com/) channel.
|
||||
* Providing feedback on the open [PRs](https://github.com/react-navigation/react-navigation/pulls).
|
||||
* Providing feedback on the open [RFCs](https://github.com/react-navigation/rfcs).
|
||||
* Providing feedback on the open [PRs](https://github.com/react-community/react-navigation/pulls).
|
||||
* Providing feedback on the open [RFCs](https://github.com/react-community/rfcs/pulls).
|
||||
* Improving the [website](https://github.com/react-navigation/react-navigation.github.io).
|
||||
|
||||
If you would like to submit a pull request, please follow the [Contributors guide](https://reactnavigation.org/docs/contributing.html) to find out how. If you don't know where to start, check the ones with the label [`good first issue`](https://github.com/react-community/react-navigation/labels/good%20first%20issue) - even [fixing a typo in the documentation](https://github.com/react-community/react-navigation/pull/2727) is a worthy contribution!
|
||||
@@ -57,7 +57,7 @@ Certainly not! There other libraries - which, depending on your needs, can be be
|
||||
|
||||
#### Can I use this library for web?
|
||||
|
||||
Web support was [not a priority for the 1.0 release](https://github.com/react-community/react-navigation/issues/2585#issuecomment-330338793), but the architecture of this library allows for it (and it has worked in the past). If you would like to lead this charge, please reach out with your ideas for how to move forward on the [RFCs repository](https://github.com/react-navigation/rfcs) and we would be happy to discuss.
|
||||
Currently this is [not a priority](https://github.com/react-community/react-navigation/issues/2585#issuecomment-330338793), but the architecture of this library allows for it (and it has worked in the past). If you would like to lead this charge, please reach out with your ideas for how to move forward on the [RFCs repository](https://github.com/react-navigation/rfcs) and we would be happy to discuss.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
|
||||
@@ -65,13 +65,11 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
module.file_ext=.native.js
|
||||
|
||||
suppress_type=$FlowIgnore
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
@@ -25,12 +25,10 @@ import MultipleDrawer from './MultipleDrawer';
|
||||
import TabsInDrawer from './TabsInDrawer';
|
||||
import ModalStack from './ModalStack';
|
||||
import StacksInTabs from './StacksInTabs';
|
||||
import SwitchWithStacks from './SwitchWithStacks';
|
||||
import StacksOverTabs from './StacksOverTabs';
|
||||
import StacksWithKeys from './StacksWithKeys';
|
||||
import SimpleStack from './SimpleStack';
|
||||
import StackWithHeaderPreset from './StackWithHeaderPreset';
|
||||
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
import TabAnimations from './TabAnimations';
|
||||
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
||||
@@ -40,10 +38,6 @@ const ExampleInfo = {
|
||||
name: 'Stack Example',
|
||||
description: 'A card stack',
|
||||
},
|
||||
SwitchWithStacks: {
|
||||
name: 'Switch Example',
|
||||
description: 'A switch with stacks inside',
|
||||
},
|
||||
SimpleTabs: {
|
||||
name: 'Tabs Example',
|
||||
description: 'Tabs following platform conventions',
|
||||
@@ -56,14 +50,6 @@ const ExampleInfo = {
|
||||
name: 'UIKit-style Header Transitions',
|
||||
description: 'Masked back button and sliding header items. iOS only.',
|
||||
},
|
||||
StackWithHeaderPreset: {
|
||||
name: 'UIKit-style Header Transitions',
|
||||
description: 'Masked back button and sliding header items. iOS only.',
|
||||
},
|
||||
StackWithTranslucentHeader: {
|
||||
name: 'Translucent Header',
|
||||
description: 'Render arbitrary translucent content in header background.',
|
||||
},
|
||||
// MultipleDrawer: {
|
||||
// name: 'Multiple Drawer Example',
|
||||
// description: 'Add any drawer you need',
|
||||
@@ -114,29 +100,27 @@ const ExampleInfo = {
|
||||
name: 'Animated Tabs Example',
|
||||
description: 'Tab transitions have custom animations',
|
||||
},
|
||||
TabsWithNavigationFocus: {
|
||||
name: 'withNavigationFocus',
|
||||
description: 'Receive the focus prop to know when a screen is focused',
|
||||
},
|
||||
// TabsWithNavigationFocus: {
|
||||
// name: 'withNavigationFocus',
|
||||
// description: 'Receive the focus prop to know when a screen is focused',
|
||||
// },
|
||||
};
|
||||
|
||||
const ExampleRoutes = {
|
||||
SimpleStack,
|
||||
SwitchWithStacks,
|
||||
SimpleTabs,
|
||||
Drawer,
|
||||
SimpleStack: SimpleStack,
|
||||
SimpleTabs: SimpleTabs,
|
||||
Drawer: Drawer,
|
||||
// MultipleDrawer: {
|
||||
// screen: MultipleDrawer,
|
||||
// },
|
||||
StackWithHeaderPreset,
|
||||
StackWithTranslucentHeader,
|
||||
TabsInDrawer,
|
||||
CustomTabs,
|
||||
CustomTransitioner,
|
||||
ModalStack,
|
||||
StacksWithKeys,
|
||||
StacksInTabs,
|
||||
StacksOverTabs,
|
||||
StackWithHeaderPreset: StackWithHeaderPreset,
|
||||
TabsInDrawer: TabsInDrawer,
|
||||
CustomTabs: CustomTabs,
|
||||
CustomTransitioner: CustomTransitioner,
|
||||
ModalStack: ModalStack,
|
||||
StacksWithKeys: StacksWithKeys,
|
||||
StacksInTabs: StacksInTabs,
|
||||
StacksOverTabs: StacksOverTabs,
|
||||
LinkStack: {
|
||||
screen: SimpleStack,
|
||||
path: 'people/Jordan',
|
||||
@@ -145,8 +129,8 @@ const ExampleRoutes = {
|
||||
screen: SimpleTabs,
|
||||
path: 'settings',
|
||||
},
|
||||
TabAnimations,
|
||||
TabsWithNavigationFocus,
|
||||
TabAnimations: TabAnimations,
|
||||
// TabsWithNavigationFocus: TabsWithNavigationFocus,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
@@ -178,18 +178,20 @@ MyProfileScreen.navigationOptions = props => {
|
||||
};
|
||||
};
|
||||
|
||||
const SimpleStack = StackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
});
|
||||
const SimpleStack = StackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default SimpleStack;
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
NavigationScreenProp,
|
||||
NavigationEventSubscription,
|
||||
} from 'react-navigation';
|
||||
|
||||
import { isIphoneX } from 'react-native-iphone-x-helper';
|
||||
|
||||
import * as React from 'react';
|
||||
import { BlurView, Constants } from 'expo';
|
||||
import {
|
||||
Button,
|
||||
Dimensions,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Header, StackNavigator } from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
type MyNavScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
banner: React.Node,
|
||||
};
|
||||
|
||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
render() {
|
||||
const { navigation, banner } = this.props;
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||
title="Push a profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
||||
title="Navigate to a photos screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||
title="Replace with profile"
|
||||
/>
|
||||
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
// Inset to compensate for navigation bar being transparent.
|
||||
// And improved abstraction for this will be built in to react-navigation
|
||||
// at some point.
|
||||
|
||||
getHeaderInset() {
|
||||
const NOTCH_HEIGHT = isIphoneX() ? 25 : 0;
|
||||
|
||||
// $FlowIgnore: we will remove the HEIGHT static soon enough
|
||||
const BASE_HEADER_HEIGHT = Header.HEIGHT;
|
||||
|
||||
const HEADER_HEIGHT =
|
||||
Platform.OS === 'ios'
|
||||
? BASE_HEADER_HEIGHT + NOTCH_HEIGHT
|
||||
: BASE_HEADER_HEIGHT + Constants.statusBarHeight;
|
||||
|
||||
return Platform.select({
|
||||
ios: {
|
||||
contentInset: { top: HEADER_HEIGHT },
|
||||
contentOffset: { y: -HEADER_HEIGHT },
|
||||
},
|
||||
android: {
|
||||
contentContainerStyle: {
|
||||
paddingTop: HEADER_HEIGHT,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type MyHomeScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
};
|
||||
|
||||
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
static navigationOptions = {
|
||||
title: 'Welcome',
|
||||
};
|
||||
_s0: NavigationEventSubscription;
|
||||
_s1: NavigationEventSubscription;
|
||||
_s2: NavigationEventSubscription;
|
||||
_s3: NavigationEventSubscription;
|
||||
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus HomeScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus HomeScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur HomeScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur HomeScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
|
||||
}
|
||||
}
|
||||
|
||||
type MyPhotosScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
};
|
||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
static navigationOptions = {
|
||||
title: 'Photos',
|
||||
};
|
||||
_s0: NavigationEventSubscription;
|
||||
_s1: NavigationEventSubscription;
|
||||
_s2: NavigationEventSubscription;
|
||||
_s3: NavigationEventSubscription;
|
||||
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus PhotosScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus PhotosScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur PhotosScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur PhotosScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.name}'s Photos`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MyProfileScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
|
||||
navigation.state.params.name
|
||||
}'s Profile`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
MyProfileScreen.navigationOptions = props => {
|
||||
const { navigation } = props;
|
||||
const { state, setParams } = navigation;
|
||||
const { params } = state;
|
||||
return {
|
||||
headerBackImage: params.headerBackImage,
|
||||
headerTitle: `${params.name}'s Profile!`,
|
||||
// Render a button on the right side of the header.
|
||||
// When pressed switches the screen to edit mode.
|
||||
headerRight: (
|
||||
<Button
|
||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||
onPress={() =>
|
||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||
}
|
||||
/>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const StackWithTranslucentHeader = StackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerTransitionPreset: 'uikit',
|
||||
navigationOptions: {
|
||||
headerTransparent: true,
|
||||
headerBackground: Platform.select({
|
||||
ios: <BlurView style={{ flex: 1 }} intensity={98} />,
|
||||
android: (
|
||||
<View style={{ flex: 1, backgroundColor: 'rgba(255,255,255,0.7)' }} />
|
||||
),
|
||||
}),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default StackWithTranslucentHeader;
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* @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,75 +3,40 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, SafeAreaView, Text } from 'react-native';
|
||||
import { SafeAreaView, Text } from 'react-native';
|
||||
import { TabNavigator, withNavigationFocus } from 'react-navigation';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
|
||||
class Child extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<Text style={{ color: this.props.isFocused ? 'green' : 'maroon' }}>
|
||||
{this.props.isFocused
|
||||
? 'I know that my parent is focused!'
|
||||
: 'My parent is not focused! :O'}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ChildWithNavigationFocus = withNavigationFocus(Child);
|
||||
|
||||
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' }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
const TabScreen = ({ isFocused }) => (
|
||||
<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>{'props.isFocused: ' + (isFocused ? ' true' : 'false')}</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
state = { showChild: false };
|
||||
TabScreen.navigationOptions = {
|
||||
tabBarLabel: name,
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? focusedIcon : icon}
|
||||
size={26}
|
||||
style={{ color: focused ? tintColor : '#ccc' }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
"expo": "^25.0.0",
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native-iphone-x-helper": "^1.0.2",
|
||||
"react-navigation": "link:../.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^21.0.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.0",
|
||||
"babel-jest": "^21.0.0",
|
||||
"flow-bin": "^0.61.0",
|
||||
"jest": "^21.0.1",
|
||||
"jest-expo": "^25.1.0",
|
||||
|
||||
@@ -5173,17 +5173,13 @@ react-native-gesture-handler@1.0.0-alpha.39:
|
||||
invariant "^2.2.2"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-native-iphone-x-helper@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.0.2.tgz#7dbca530930f7c1ce8633cc8fd13ba94102992e1"
|
||||
|
||||
react-native-maps@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-maps/-/react-native-maps-0.19.0.tgz#ce94fad1cf360e335cb4338a68c95f791e869074"
|
||||
|
||||
react-native-safe-area-view@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.7.0.tgz#38f5ab9368d6ef9e5d18ab64212938af3ec39421"
|
||||
react-native-safe-area-view@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.6.0.tgz#ce01eb27905a77780219537e0f53fe9c783a8b3d"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^2.3.1"
|
||||
|
||||
|
||||
95
flow/react-navigation.js
vendored
95
flow/react-navigation.js
vendored
@@ -48,15 +48,6 @@ declare module 'react-navigation' {
|
||||
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js
|
||||
declare type AnimatedValue = Object;
|
||||
|
||||
declare type HeaderForceInset = {
|
||||
horizontal?: string,
|
||||
vertical?: string,
|
||||
left?: string,
|
||||
right?: string,
|
||||
top?: string,
|
||||
bottom?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Next, all the type declarations
|
||||
*/
|
||||
@@ -80,11 +71,25 @@ declare module 'react-navigation' {
|
||||
key?: string,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationNavigateAction = {|
|
||||
type: 'Navigate',
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
|
||||
// The action to run inside the sub-router
|
||||
action?: NavigationNavigateAction | DeprecatedNavigationNavigateAction,
|
||||
|};
|
||||
|
||||
declare export type NavigationBackAction = {|
|
||||
type: 'Navigation/BACK',
|
||||
key?: ?string,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationBackAction = {|
|
||||
type: 'Back',
|
||||
key?: ?string,
|
||||
|};
|
||||
|
||||
declare export type NavigationSetParamsAction = {|
|
||||
type: 'Navigation/SET_PARAMS',
|
||||
|
||||
@@ -95,11 +100,26 @@ declare module 'react-navigation' {
|
||||
params: NavigationParams,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationSetParamsAction = {|
|
||||
type: 'SetParams',
|
||||
|
||||
// The key of the route where the params should be set
|
||||
key: string,
|
||||
|
||||
// The new params to merge into the existing route params
|
||||
params: NavigationParams,
|
||||
|};
|
||||
|
||||
declare export type NavigationInitAction = {|
|
||||
type: 'Navigation/INIT',
|
||||
params?: NavigationParams,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationInitAction = {|
|
||||
type: 'Init',
|
||||
params?: NavigationParams,
|
||||
|};
|
||||
|
||||
declare export type NavigationResetAction = {|
|
||||
type: 'Navigation/RESET',
|
||||
index: number,
|
||||
@@ -107,11 +127,25 @@ declare module 'react-navigation' {
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationResetAction = {|
|
||||
type: 'Reset',
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions: Array<
|
||||
NavigationNavigateAction | DeprecatedNavigationNavigateAction
|
||||
>,
|
||||
|};
|
||||
|
||||
declare export type NavigationUriAction = {|
|
||||
type: 'Navigation/URI',
|
||||
uri: string,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationUriAction = {|
|
||||
type: 'Uri',
|
||||
uri: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationReplaceAction = {|
|
||||
+type: 'Navigation/REPLACE',
|
||||
+key: string,
|
||||
@@ -147,6 +181,17 @@ declare module 'react-navigation' {
|
||||
| NavigationSetParamsAction
|
||||
| NavigationResetAction;
|
||||
|
||||
declare type DeprecatedNavigationAction =
|
||||
| DeprecatedNavigationInitAction
|
||||
| DeprecatedNavigationNavigateAction
|
||||
| DeprecatedNavigationBackAction
|
||||
| DeprecatedNavigationSetParamsAction
|
||||
| DeprecatedNavigationResetAction;
|
||||
|
||||
declare export type PossiblyDeprecatedNavigationAction =
|
||||
| NavigationAction
|
||||
| DeprecatedNavigationAction;
|
||||
|
||||
/**
|
||||
* NavigationState is a tree of routes for a single navigator, where each
|
||||
* child route may either be a NavigationScreenRoute or a
|
||||
@@ -336,7 +381,6 @@ declare module 'react-navigation' {
|
||||
|
||||
declare export type NavigationStackScreenOptions = NavigationScreenOptions & {
|
||||
header?: ?(React$Node | (HeaderProps => React$Node)),
|
||||
headerTransparent?: boolean,
|
||||
headerTitle?: string | React$Node | React$ElementType,
|
||||
headerTitleStyle?: AnimatedTextStyleProp,
|
||||
headerTitleAllowFontScaling?: boolean,
|
||||
@@ -349,8 +393,6 @@ declare module 'react-navigation' {
|
||||
headerPressColorAndroid?: string,
|
||||
headerRight?: React$Node,
|
||||
headerStyle?: ViewStyleProp,
|
||||
headerForceInset?: HeaderForceInset,
|
||||
headerBackground?: React$Node | React$ElementType,
|
||||
gesturesEnabled?: boolean,
|
||||
gestureResponseDistance?: { vertical?: number, horizontal?: number },
|
||||
gestureDirection?: 'default' | 'inverted',
|
||||
@@ -378,20 +420,6 @@ declare module 'react-navigation' {
|
||||
...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
|
||||
*/
|
||||
@@ -403,6 +431,7 @@ declare module 'react-navigation' {
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
// todo: type these as the real route names rather than 'string'
|
||||
order?: Array<string>,
|
||||
|
||||
// Does the back button cause the router to switch to the initial tab
|
||||
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
||||
|};
|
||||
@@ -451,7 +480,7 @@ declare module 'react-navigation' {
|
||||
*/
|
||||
|
||||
declare export type NavigationDispatch = (
|
||||
action: NavigationAction
|
||||
action: PossiblyDeprecatedNavigationAction
|
||||
) => boolean;
|
||||
|
||||
declare export type NavigationProp<S> = {
|
||||
@@ -728,7 +757,7 @@ declare module 'react-navigation' {
|
||||
toString: () => string,
|
||||
},
|
||||
init: {
|
||||
(payload?: { params?: NavigationParams }): NavigationInitAction,
|
||||
(payload: { params?: NavigationParams }): NavigationInitAction,
|
||||
toString: () => string,
|
||||
},
|
||||
navigate: {
|
||||
@@ -758,6 +787,9 @@ declare module 'react-navigation' {
|
||||
(payload: { uri: string }): NavigationUriAction,
|
||||
toString: () => string,
|
||||
},
|
||||
mapDeprecatedActionAndWarn: (
|
||||
action: PossiblyDeprecatedNavigationAction
|
||||
) => NavigationAction,
|
||||
};
|
||||
|
||||
declare type _RouterProp<S: NavigationState, O: {}> = {
|
||||
@@ -809,13 +841,6 @@ declare module 'react-navigation' {
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare type _SwitchNavigatorConfig = {|
|
||||
...NavigationSwitchRouterConfig,
|
||||
|};
|
||||
declare export function SwitchNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _SwitchNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
|
||||
declare type _DrawerViewConfig = {|
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "1.4.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/react-navigation.js",
|
||||
"repository": {
|
||||
@@ -33,7 +33,7 @@
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-native-drawer-layout-polyfill": "^1.3.2",
|
||||
"react-native-safe-area-view": "^0.7.0",
|
||||
"react-native-safe-area-view": "^0.6.0",
|
||||
"react-native-tab-view": "^0.0.74"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -57,7 +57,6 @@ const pop = createAction(POP, payload => ({
|
||||
const popToTop = createAction(POP_TO_TOP, payload => ({
|
||||
type: POP_TO_TOP,
|
||||
immediate: payload && payload.immediate,
|
||||
key: payload && payload.key,
|
||||
}));
|
||||
|
||||
const push = createAction(PUSH, payload => {
|
||||
@@ -107,6 +106,59 @@ const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
|
||||
key: payload && payload.key,
|
||||
}));
|
||||
|
||||
const mapDeprecatedNavigateAction = action => {
|
||||
if (action.type === 'Navigate') {
|
||||
const payload = {
|
||||
routeName: action.routeName,
|
||||
params: action.params,
|
||||
};
|
||||
if (action.action) {
|
||||
payload.action = mapDeprecatedNavigateAction(action.action);
|
||||
}
|
||||
return navigate(payload);
|
||||
}
|
||||
return action;
|
||||
};
|
||||
|
||||
const mapDeprecatedAction = action => {
|
||||
if (action.type === 'Back') {
|
||||
return back(action);
|
||||
} else if (action.type === 'Init') {
|
||||
return init(action);
|
||||
} else if (action.type === 'Navigate') {
|
||||
return mapDeprecatedNavigateAction(action);
|
||||
} else if (action.type === 'Reset') {
|
||||
return reset({
|
||||
index: action.index,
|
||||
key: action.key,
|
||||
actions: action.actions.map(mapDeprecatedNavigateAction),
|
||||
});
|
||||
} else if (action.type === 'SetParams') {
|
||||
return setParams(action);
|
||||
}
|
||||
return action;
|
||||
};
|
||||
|
||||
const mapDeprecatedActionAndWarn = action => {
|
||||
const newAction = mapDeprecatedAction(action);
|
||||
if (newAction !== action) {
|
||||
const oldType = action.type;
|
||||
const newType = newAction.type;
|
||||
console.warn(
|
||||
[
|
||||
`The action type '${oldType}' has been renamed to '${newType}'.`,
|
||||
`'${oldType}' will continue to work while in beta but will be removed`,
|
||||
'in the first major release. Moving forward, you should use the',
|
||||
'action constants and action creators exported by this library in',
|
||||
"the 'actions' object.",
|
||||
'See https://github.com/react-community/react-navigation/pull/120 for',
|
||||
'more details.',
|
||||
].join(' ')
|
||||
);
|
||||
}
|
||||
return newAction;
|
||||
};
|
||||
|
||||
export default {
|
||||
// Action constants
|
||||
BACK,
|
||||
@@ -133,4 +185,7 @@ export default {
|
||||
setParams,
|
||||
uri,
|
||||
completeTransition,
|
||||
|
||||
// TODO: Remove once old actions are deprecated
|
||||
mapDeprecatedActionAndWarn,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {
|
||||
BackAndroid as DeprecatedBackAndroid,
|
||||
BackHandler as ModernBackHandler,
|
||||
MaskedViewIOS,
|
||||
} from 'react-native';
|
||||
|
||||
const BackHandler = ModernBackHandler || DeprecatedBackAndroid;
|
||||
|
||||
export { BackHandler, MaskedViewIOS };
|
||||
export { BackHandler };
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import React from 'react';
|
||||
import { BackHandler, View } from 'react-native';
|
||||
import { BackHandler } from 'react-native';
|
||||
|
||||
const MaskedViewIOS = () => <View>{this.props.children}</View>;
|
||||
|
||||
export { BackHandler, MaskedViewIOS };
|
||||
export { BackHandler };
|
||||
|
||||
@@ -166,7 +166,8 @@ export default function createNavigationContainer(Component) {
|
||||
|
||||
// Per-tick temporary storage for state.nav
|
||||
|
||||
dispatch = action => {
|
||||
dispatch = inputAction => {
|
||||
const action = NavigationActions.mapDeprecatedActionAndWarn(inputAction);
|
||||
if (!this._isStateful()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
||||
* focus and blur events for this child
|
||||
*/
|
||||
|
||||
export default function getChildEventSubscriber(addListener, key) {
|
||||
const actionSubscribers = new Set();
|
||||
const willFocusSubscribers = new Set();
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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);
|
||||
};
|
||||
@@ -1,22 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import StackNavigator from '../StackNavigator';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
class HomeScreen extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: `Welcome ${
|
||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
||||
}`,
|
||||
gesturesEnabled: true,
|
||||
headerStyle: [{ backgroundColor: 'red' }, styles.header],
|
||||
});
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -48,7 +48,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EFEFF4",
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
@@ -75,109 +75,163 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
cardStyle={undefined}
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
getScreenDetails={[Function]}
|
||||
headerMode={undefined}
|
||||
headerTransitionPreset={undefined}
|
||||
index={0}
|
||||
layout={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
"height": 0,
|
||||
"initHeight": 0,
|
||||
"initWidth": 0,
|
||||
"isMeasured": false,
|
||||
"width": 0,
|
||||
}
|
||||
}
|
||||
leftButtonInterpolator={[Function]}
|
||||
leftInterpolator={[Function]}
|
||||
leftLabelInterpolator={[Function]}
|
||||
mode="float"
|
||||
navigation={
|
||||
Object {
|
||||
"addListener": [Function],
|
||||
"dispatch": [Function],
|
||||
"getParam": [Function],
|
||||
"goBack": [Function],
|
||||
"navigate": [Function],
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
"setParams": [Function],
|
||||
"state": Object {
|
||||
"index": 0,
|
||||
"isTransitioning": false,
|
||||
"key": "StackRouterRoot",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"key": "id-0-1",
|
||||
"routeName": "Home",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
rightInterpolator={[Function]}
|
||||
router={
|
||||
Object {
|
||||
"getActionForPathAndParams": [Function],
|
||||
"getComponentForRouteName": [Function],
|
||||
"getComponentForState": [Function],
|
||||
"getPathAndParamsForState": [Function],
|
||||
"getScreenConfig": [Function],
|
||||
"getScreenOptions": [Function],
|
||||
"getStateForAction": [Function],
|
||||
}
|
||||
}
|
||||
titleFromLeftInterpolator={[Function]}
|
||||
titleInterpolator={[Function]}
|
||||
transitionConfig={undefined}
|
||||
transitionPreset="fade-in-place"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"backgroundColor": "#F7F7F7",
|
||||
"borderBottomColor": "rgba(0, 0, 0, .3)",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 70,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 70,
|
||||
"top": 0,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
},
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
<View
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 70,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 70,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<View />
|
||||
>
|
||||
<View />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -234,7 +288,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EFEFF4",
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
@@ -261,91 +315,145 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
cardStyle={undefined}
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
getScreenDetails={[Function]}
|
||||
headerMode={undefined}
|
||||
headerTransitionPreset={undefined}
|
||||
index={0}
|
||||
layout={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
"height": 0,
|
||||
"initHeight": 0,
|
||||
"initWidth": 0,
|
||||
"isMeasured": false,
|
||||
"width": 0,
|
||||
}
|
||||
}
|
||||
leftButtonInterpolator={[Function]}
|
||||
leftInterpolator={[Function]}
|
||||
leftLabelInterpolator={[Function]}
|
||||
mode="float"
|
||||
navigation={
|
||||
Object {
|
||||
"addListener": [Function],
|
||||
"dispatch": [Function],
|
||||
"getParam": [Function],
|
||||
"goBack": [Function],
|
||||
"navigate": [Function],
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
"setParams": [Function],
|
||||
"state": Object {
|
||||
"index": 0,
|
||||
"isTransitioning": false,
|
||||
"key": "StackRouterRoot",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"key": "id-0-0",
|
||||
"routeName": "Home",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
rightInterpolator={[Function]}
|
||||
router={
|
||||
Object {
|
||||
"getActionForPathAndParams": [Function],
|
||||
"getComponentForRouteName": [Function],
|
||||
"getComponentForState": [Function],
|
||||
"getPathAndParamsForState": [Function],
|
||||
"getScreenConfig": [Function],
|
||||
"getScreenOptions": [Function],
|
||||
"getStateForAction": [Function],
|
||||
}
|
||||
}
|
||||
titleFromLeftInterpolator={[Function]}
|
||||
titleInterpolator={[Function]}
|
||||
transitionConfig={undefined}
|
||||
transitionPreset="fade-in-place"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"backgroundColor": "#F7F7F7",
|
||||
"borderBottomColor": "rgba(0, 0, 0, .3)",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
},
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
<View
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwitchNavigator renders successfully 1`] = `<View />`;
|
||||
11
src/react-navigation.js
vendored
11
src/react-navigation.js
vendored
@@ -22,9 +22,6 @@ module.exports = {
|
||||
get StackNavigator() {
|
||||
return require('./navigators/StackNavigator').default;
|
||||
},
|
||||
get SwitchNavigator() {
|
||||
return require('./navigators/SwitchNavigator').default;
|
||||
},
|
||||
get TabNavigator() {
|
||||
return require('./navigators/TabNavigator').default;
|
||||
},
|
||||
@@ -39,9 +36,6 @@ module.exports = {
|
||||
get TabRouter() {
|
||||
return require('./routers/TabRouter').default;
|
||||
},
|
||||
get SwitchRouter() {
|
||||
return require('./routers/SwitchRouter').default;
|
||||
},
|
||||
|
||||
// Views
|
||||
get Transitioner() {
|
||||
@@ -90,11 +84,6 @@ module.exports = {
|
||||
return require('./views/TabView/TabBarBottom').default;
|
||||
},
|
||||
|
||||
// SwitchView
|
||||
get SwitchView() {
|
||||
return require('./views/SwitchView/SwitchView').default;
|
||||
},
|
||||
|
||||
// HOCs
|
||||
get withNavigation() {
|
||||
return require('./views/withNavigation').default;
|
||||
|
||||
@@ -305,12 +305,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
||||
if (action.type === NavigationActions.POP_TO_TOP) {
|
||||
// Refuse to handle pop to top if a key is given that doesn't correspond
|
||||
// to this router
|
||||
if (action.key && state.key !== action.key) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// If we're already at the top, then we return the state with a new
|
||||
// identity so that the action is handled by this router.
|
||||
if (state.index === 0) {
|
||||
@@ -392,28 +386,21 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
// undefined on either the state or the action
|
||||
return state;
|
||||
}
|
||||
const newStackActions = action.actions;
|
||||
const resetAction = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
routes: newStackActions.map(newStackAction => {
|
||||
const router = childRouters[newStackAction.routeName];
|
||||
|
||||
routes: resetAction.actions.map(childAction => {
|
||||
const router = childRouters[childAction.routeName];
|
||||
let childState = {};
|
||||
|
||||
if (router) {
|
||||
const childAction =
|
||||
newStackAction.action ||
|
||||
NavigationActions.init({ params: newStackAction.params });
|
||||
|
||||
childState = router.getStateForAction(childAction);
|
||||
}
|
||||
|
||||
return {
|
||||
params: newStackAction.params,
|
||||
params: childAction.params,
|
||||
...childState,
|
||||
routeName: newStackAction.routeName,
|
||||
key: newStackAction.key || generateKey(),
|
||||
routeName: childAction.routeName,
|
||||
key: childAction.key || generateKey(),
|
||||
};
|
||||
}),
|
||||
index: action.index,
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -240,7 +240,7 @@ export default (routeConfigs, config = {}) => {
|
||||
},
|
||||
|
||||
getComponentForState(state) {
|
||||
const routeName = state.routes[state.index].routeName;
|
||||
const routeName = order[state.index];
|
||||
invariant(
|
||||
routeName,
|
||||
`There is no route defined for index ${state.index}. Check that
|
||||
|
||||
@@ -457,35 +457,6 @@ describe('StackRouter', () => {
|
||||
expect(state3 && state3.routes[1].index).toEqual(0);
|
||||
});
|
||||
|
||||
test('popToTop targets StackRouter by key if specified', () => {
|
||||
const ChildNavigator = () => <div />;
|
||||
ChildNavigator.router = StackRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
Qux: { screen: () => <div /> },
|
||||
});
|
||||
const router = StackRouter({
|
||||
Foo: { screen: () => <div /> },
|
||||
Bar: { screen: ChildNavigator },
|
||||
});
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
},
|
||||
state
|
||||
);
|
||||
const barKey = state2.routes[1].routes[0].key;
|
||||
const state3 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.POP_TO_TOP,
|
||||
key: state2.key,
|
||||
},
|
||||
state2
|
||||
);
|
||||
expect(state3 && state3.index).toEqual(0);
|
||||
});
|
||||
|
||||
test('popToTop works as expected', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
@@ -1535,6 +1506,40 @@ describe('StackRouter', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Maps old actions (uses "Handles the reset action" test)', () => {
|
||||
global.console.warn = jest.fn();
|
||||
const router = StackRouter({
|
||||
Foo: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
Bar: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
});
|
||||
const initAction = NavigationActions.mapDeprecatedActionAndWarn({
|
||||
type: 'Init',
|
||||
});
|
||||
const state = router.getStateForAction(initAction);
|
||||
const resetAction = NavigationActions.mapDeprecatedActionAndWarn({
|
||||
type: 'Reset',
|
||||
actions: [
|
||||
{ type: 'Navigate', routeName: 'Foo', params: { bar: '42' } },
|
||||
{ type: 'Navigate', routeName: 'Bar' },
|
||||
],
|
||||
index: 1,
|
||||
});
|
||||
const state2 = router.getStateForAction(resetAction, state);
|
||||
expect(state2 && state2.index).toEqual(1);
|
||||
expect(state2 && state2.routes[0].params).toEqual({ bar: '42' });
|
||||
expect(state2 && state2.routes[0].routeName).toEqual('Foo');
|
||||
expect(state2 && state2.routes[1].routeName).toEqual('Bar');
|
||||
expect(console.warn).toBeCalledWith(
|
||||
expect.stringContaining(
|
||||
"The action type 'Init' has been renamed to 'Navigation/INIT'"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('URI encoded string get passed to deep link', () => {
|
||||
const uri = 'people/2018%2F02%2F07';
|
||||
const action = TestStackRouter.getActionForPathAndParams(uri);
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/* 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;
|
||||
};
|
||||
@@ -593,6 +593,29 @@ describe('TabRouter', () => {
|
||||
expect(path).toEqual('f/Baz');
|
||||
});
|
||||
|
||||
test('Maps old actions (uses "getStateForAction returns null when navigating to same tab" test)', () => {
|
||||
global.console.warn = jest.fn();
|
||||
const router = TabRouter(
|
||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
||||
{ initialRouteName: 'Bar' }
|
||||
);
|
||||
const initAction = NavigationActions.mapDeprecatedActionAndWarn({
|
||||
type: 'Init',
|
||||
});
|
||||
const state = router.getStateForAction(initAction);
|
||||
const navigateAction = NavigationActions.mapDeprecatedActionAndWarn({
|
||||
type: 'Navigate',
|
||||
routeName: 'Bar',
|
||||
});
|
||||
const state2 = router.getStateForAction(navigateAction, state);
|
||||
expect(state2).toEqual(null);
|
||||
expect(console.warn).toBeCalledWith(
|
||||
expect.stringContaining(
|
||||
"The action type 'Init' has been renamed to 'Navigation/INIT'"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('Can navigate to other tab (no router) with params', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// 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,19 +13,9 @@ ProfileNavigator.router = StackRouter({
|
||||
});
|
||||
|
||||
describe('validateRouteConfigMap', () => {
|
||||
test('Fails on empty bare screen', () => {
|
||||
const invalidMap = {
|
||||
Home: undefined,
|
||||
};
|
||||
expect(() =>
|
||||
validateRouteConfigMap(invalidMap)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
test('Fails on empty config', () => {
|
||||
const invalidMap = {};
|
||||
expect(() =>
|
||||
validateRouteConfigMap(invalidMap)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
||||
});
|
||||
test('Fails on bad object', () => {
|
||||
const invalidMap = {
|
||||
@@ -33,9 +23,7 @@ describe('validateRouteConfigMap', () => {
|
||||
foo: 'bar',
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
validateRouteConfigMap(invalidMap)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
||||
});
|
||||
test('Fails if both screen and getScreen are defined', () => {
|
||||
const invalidMap = {
|
||||
@@ -44,17 +32,15 @@ describe('validateRouteConfigMap', () => {
|
||||
getScreen: () => ListScreen,
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
validateRouteConfigMap(invalidMap)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
|
||||
});
|
||||
test('Succeeds on a valid config', () => {
|
||||
const validMap = {
|
||||
const invalidMap = {
|
||||
Home: {
|
||||
screen: ProfileNavigator,
|
||||
},
|
||||
Chat: ListScreen,
|
||||
};
|
||||
validateRouteConfigMap(validMap);
|
||||
validateRouteConfigMap(invalidMap);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,13 +13,16 @@ function validateRouteConfigMap(routeConfigs) {
|
||||
|
||||
routeNames.forEach(routeName => {
|
||||
const routeConfig = routeConfigs[routeName];
|
||||
const screenComponent = getScreenComponent(routeConfig);
|
||||
|
||||
const screenComponent = routeConfig.screen
|
||||
? routeConfig.screen
|
||||
: routeConfig;
|
||||
|
||||
if (
|
||||
!screenComponent ||
|
||||
(typeof screenComponent !== 'function' &&
|
||||
typeof screenComponent !== 'string' &&
|
||||
!routeConfig.getScreen)
|
||||
screenComponent &&
|
||||
typeof screenComponent !== 'function' &&
|
||||
typeof screenComponent !== 'string' &&
|
||||
!routeConfig.getScreen
|
||||
) {
|
||||
throw new Error(
|
||||
`The component for route '${routeName}' must be a ` +
|
||||
@@ -45,12 +48,4 @@ function validateRouteConfigMap(routeConfigs) {
|
||||
});
|
||||
}
|
||||
|
||||
function getScreenComponent(routeConfig) {
|
||||
if (!routeConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return routeConfig.screen ? routeConfig.screen : routeConfig;
|
||||
}
|
||||
|
||||
export default validateRouteConfigMap;
|
||||
|
||||
@@ -22,7 +22,7 @@ class Card extends React.Component {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
backgroundColor: '#EFEFF4',
|
||||
backgroundColor: '#E9E9EF',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
|
||||
@@ -19,7 +19,6 @@ import getChildEventSubscriber from '../../getChildEventSubscriber';
|
||||
import SceneView from '../SceneView';
|
||||
|
||||
import TransitionConfigs from './TransitionConfigs';
|
||||
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
||||
|
||||
const emptyFunction = () => {};
|
||||
|
||||
@@ -82,13 +81,11 @@ class CardStack extends React.Component {
|
||||
|
||||
_screenDetails = {};
|
||||
|
||||
_childEventSubscribers = {};
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (props.screenProps !== this.props.screenProps) {
|
||||
this._screenDetails = {};
|
||||
}
|
||||
props.transitionProps.scenes.forEach(newScene => {
|
||||
props.scenes.forEach(newScene => {
|
||||
if (
|
||||
this._screenDetails[newScene.key] &&
|
||||
this._screenDetails[newScene.key].state !== newScene.route
|
||||
@@ -98,39 +95,17 @@ 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 { state } = this.props.navigation;
|
||||
const focusedRoute = state.routes[state.index];
|
||||
return route === focusedRoute;
|
||||
};
|
||||
|
||||
_getScreenDetails = scene => {
|
||||
const { screenProps, transitionProps: { navigation }, router } = this.props;
|
||||
const { screenProps, navigation, router } = this.props;
|
||||
let screenDetails = this._screenDetails[scene.key];
|
||||
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({
|
||||
dispatch: navigation.dispatch,
|
||||
state: scene.route,
|
||||
isFocused: this._isRouteFocused.bind(this, scene.route),
|
||||
addListener: this._childEventSubscribers[scene.route.key],
|
||||
addListener: getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
scene.route.key
|
||||
),
|
||||
});
|
||||
screenDetails = {
|
||||
state: scene.route,
|
||||
@@ -156,16 +131,10 @@ class CardStack extends React.Component {
|
||||
headerRightInterpolator,
|
||||
} = this._getTransitionConfig();
|
||||
|
||||
const {
|
||||
mode,
|
||||
transitionProps,
|
||||
prevTransitionProps,
|
||||
...passProps
|
||||
} = this.props;
|
||||
const { mode, ...passProps } = this.props;
|
||||
|
||||
return renderHeader({
|
||||
...passProps,
|
||||
...transitionProps,
|
||||
scene,
|
||||
mode: headerMode,
|
||||
transitionPreset: this._getHeaderTransitionPreset(),
|
||||
@@ -185,42 +154,34 @@ class CardStack extends React.Component {
|
||||
// when we'd do that with the current structure we have. `stopAnimation` callback
|
||||
// is also broken with native animated values that have no listeners so if we
|
||||
// want to remove this we have to fix this too.
|
||||
animatedSubscribeValue(props.transitionProps.layout.width);
|
||||
animatedSubscribeValue(props.transitionProps.layout.height);
|
||||
animatedSubscribeValue(props.transitionProps.position);
|
||||
animatedSubscribeValue(props.layout.width);
|
||||
animatedSubscribeValue(props.layout.height);
|
||||
animatedSubscribeValue(props.position);
|
||||
}
|
||||
|
||||
_reset(resetToIndex, duration) {
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
ReactNativeFeatures.supportsImprovedSpringAnimation()
|
||||
) {
|
||||
Animated.spring(this.props.transitionProps.position, {
|
||||
toValue: resetToIndex,
|
||||
stiffness: 5000,
|
||||
damping: 600,
|
||||
mass: 3,
|
||||
useNativeDriver: this.props.transitionProps.position.__isNative,
|
||||
}).start();
|
||||
} else {
|
||||
Animated.timing(this.props.transitionProps.position, {
|
||||
toValue: resetToIndex,
|
||||
duration,
|
||||
easing: EaseInOut,
|
||||
useNativeDriver: this.props.transitionProps.position.__isNative,
|
||||
}).start();
|
||||
}
|
||||
Animated.timing(this.props.position, {
|
||||
toValue: resetToIndex,
|
||||
duration,
|
||||
easing: EaseInOut,
|
||||
useNativeDriver: this.props.position.__isNative,
|
||||
}).start();
|
||||
}
|
||||
|
||||
_goBack(backFromIndex, duration) {
|
||||
const { navigation, position, scenes } = this.props.transitionProps;
|
||||
const { navigation, position, scenes } = this.props;
|
||||
const toValue = Math.max(backFromIndex - 1, 0);
|
||||
|
||||
// set temporary index for gesture handler to respect until the action is
|
||||
// dispatched at the end of the transition.
|
||||
this._immediateIndex = toValue;
|
||||
|
||||
const onCompleteAnimation = () => {
|
||||
Animated.timing(position, {
|
||||
toValue,
|
||||
duration,
|
||||
easing: EaseInOut,
|
||||
useNativeDriver: position.__isNative,
|
||||
}).start(() => {
|
||||
this._immediateIndex = null;
|
||||
const backFromScene = scenes.find(s => s.index === toValue + 1);
|
||||
if (!this._isResponding && backFromScene) {
|
||||
@@ -231,42 +192,16 @@ class CardStack extends React.Component {
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
ReactNativeFeatures.supportsImprovedSpringAnimation()
|
||||
) {
|
||||
Animated.spring(position, {
|
||||
toValue,
|
||||
stiffness: 5000,
|
||||
damping: 600,
|
||||
mass: 3,
|
||||
useNativeDriver: position.__isNative,
|
||||
}).start(onCompleteAnimation);
|
||||
} else {
|
||||
Animated.timing(position, {
|
||||
toValue,
|
||||
duration,
|
||||
easing: EaseInOut,
|
||||
useNativeDriver: position.__isNative,
|
||||
}).start(onCompleteAnimation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let floatingHeader = null;
|
||||
const headerMode = this._getHeaderMode();
|
||||
if (headerMode === 'float') {
|
||||
floatingHeader = this._renderHeader(
|
||||
this.props.transitionProps.scene,
|
||||
headerMode
|
||||
);
|
||||
floatingHeader = this._renderHeader(this.props.scene, headerMode);
|
||||
}
|
||||
const {
|
||||
transitionProps: { navigation, position, layout, scene, scenes },
|
||||
mode,
|
||||
} = this.props;
|
||||
const { navigation, position, layout, scene, scenes, mode } = this.props;
|
||||
const { index } = navigation.state;
|
||||
const isVertical = mode === 'modal';
|
||||
const { options } = this._getScreenDetails(scene);
|
||||
@@ -476,8 +411,8 @@ class CardStack extends React.Component {
|
||||
|
||||
return TransitionConfigs.getTransitionConfig(
|
||||
this.props.transitionConfig,
|
||||
this.props.transitionProps,
|
||||
this.props.prevTransitionProps,
|
||||
{},
|
||||
{},
|
||||
isModal
|
||||
);
|
||||
};
|
||||
@@ -485,19 +420,15 @@ class CardStack extends React.Component {
|
||||
_renderCard = scene => {
|
||||
const { screenInterpolator } = this._getTransitionConfig();
|
||||
const style =
|
||||
screenInterpolator &&
|
||||
screenInterpolator({ ...this.props.transitionProps, scene });
|
||||
screenInterpolator && screenInterpolator({ ...this.props, scene });
|
||||
|
||||
const SceneComponent = this.props.router.getComponentForRouteName(
|
||||
scene.route.routeName
|
||||
);
|
||||
|
||||
const { transitionProps, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
{...props}
|
||||
{...transitionProps}
|
||||
{...this.props}
|
||||
key={`card_${scene.key}`}
|
||||
style={[style, this.props.cardStyle]}
|
||||
scene={scene}
|
||||
|
||||
@@ -53,7 +53,7 @@ class CardStackTransitioner extends React.Component {
|
||||
return transitionSpec;
|
||||
};
|
||||
|
||||
_render = (props, prevProps) => {
|
||||
_render = props => {
|
||||
const {
|
||||
screenProps,
|
||||
headerMode,
|
||||
@@ -72,8 +72,7 @@ class CardStackTransitioner extends React.Component {
|
||||
router={router}
|
||||
cardStyle={cardStyle}
|
||||
transitionConfig={transitionConfig}
|
||||
transitionProps={props}
|
||||
prevTransitionProps={prevProps}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,8 +17,6 @@ export default class DrawerView extends React.PureComponent {
|
||||
: this.props.drawerWidth,
|
||||
};
|
||||
|
||||
_childEventSubscribers = {};
|
||||
|
||||
componentWillMount() {
|
||||
this._updateScreenNavigation(this.props.navigation);
|
||||
|
||||
@@ -29,17 +27,6 @@ export default class DrawerView extends React.PureComponent {
|
||||
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) {
|
||||
if (
|
||||
this.props.navigation.state.index !== nextProps.navigation.state.index
|
||||
@@ -81,12 +68,6 @@ 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 => {
|
||||
const { drawerCloseRoute } = this.props;
|
||||
const navigationState = navigation.state.routes.find(
|
||||
@@ -98,18 +79,13 @@ export default class DrawerView extends React.PureComponent {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._childEventSubscribers[navigationState.key]) {
|
||||
this._childEventSubscribers[
|
||||
navigationState.key
|
||||
] = getChildEventSubscriber(navigation.addListener, navigationState.key);
|
||||
}
|
||||
|
||||
this._screenNavigationProp = addNavigationHelpers({
|
||||
dispatch: navigation.dispatch,
|
||||
state: navigationState,
|
||||
isFocused: this._isRouteFocused.bind(this, navigationState),
|
||||
addListener: this._childEventSubscribers[navigationState.key],
|
||||
addListener: getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
navigationState.key
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
Image,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
MaskedViewIOS,
|
||||
View,
|
||||
ViewPropTypes,
|
||||
} from 'react-native';
|
||||
import { MaskedViewIOS } from '../../PlatformHelpers';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import HeaderTitle from './HeaderTitle';
|
||||
@@ -373,18 +373,18 @@ class Header extends React.PureComponent {
|
||||
hasRightComponent: !!right,
|
||||
});
|
||||
|
||||
const { isLandscape, transitionPreset } = this.props;
|
||||
const { options } = this.props.getScreenDetails(props.scene);
|
||||
|
||||
const wrapperProps = {
|
||||
style: styles.header,
|
||||
style: [StyleSheet.absoluteFill, styles.header],
|
||||
key: `scene_${props.scene.key}`,
|
||||
};
|
||||
|
||||
const { isLandscape, transitionPreset } = this.props;
|
||||
const { options } = this.props.getScreenDetails(props.scene);
|
||||
|
||||
if (
|
||||
options.headerLeft ||
|
||||
options.headerBackImage ||
|
||||
Platform.OS !== 'ios' ||
|
||||
Platform.OS === 'android' ||
|
||||
transitionPreset !== 'uikit'
|
||||
) {
|
||||
return (
|
||||
@@ -418,9 +418,8 @@ class Header extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
let appBar;
|
||||
const { mode, scene, isLandscape } = this.props;
|
||||
|
||||
if (mode === 'float') {
|
||||
if (this.props.mode === 'float') {
|
||||
const scenesByIndex = {};
|
||||
this.props.scenes.forEach(scene => {
|
||||
scenesByIndex[scene.index] = scene;
|
||||
@@ -439,59 +438,37 @@ class Header extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
const { options } = this.props.getScreenDetails(scene);
|
||||
const { headerStyle = {} } = options;
|
||||
const headerStyleObj = StyleSheet.flatten(headerStyle);
|
||||
const appBarHeight = getAppBarHeight(isLandscape);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {
|
||||
alignItems,
|
||||
justifyContent,
|
||||
flex,
|
||||
flexDirection,
|
||||
flexGrow,
|
||||
flexShrink,
|
||||
flexBasis,
|
||||
flexWrap,
|
||||
...safeHeaderStyle
|
||||
} = headerStyleObj;
|
||||
scenes,
|
||||
scene,
|
||||
position,
|
||||
screenProps,
|
||||
progress,
|
||||
isLandscape,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
if (__DEV__) {
|
||||
warnIfHeaderStyleDefined(alignItems, 'alignItems');
|
||||
warnIfHeaderStyleDefined(justifyContent, 'justifyContent');
|
||||
warnIfHeaderStyleDefined(flex, 'flex');
|
||||
warnIfHeaderStyleDefined(flexDirection, 'flexDirection');
|
||||
warnIfHeaderStyleDefined(flexGrow, 'flexGrow');
|
||||
warnIfHeaderStyleDefined(flexShrink, 'flexShrink');
|
||||
warnIfHeaderStyleDefined(flexBasis, 'flexBasis');
|
||||
warnIfHeaderStyleDefined(flexWrap, 'flexWrap');
|
||||
}
|
||||
|
||||
// TODO: warn if any unsafe styles are provided
|
||||
const { options } = this.props.getScreenDetails(scene);
|
||||
const { headerStyle } = options;
|
||||
const appBarHeight = getAppBarHeight(isLandscape);
|
||||
const containerStyles = [
|
||||
options.headerTransparent
|
||||
? styles.transparentContainer
|
||||
: styles.container,
|
||||
{ height: appBarHeight },
|
||||
safeHeaderStyle,
|
||||
styles.container,
|
||||
{
|
||||
height: appBarHeight,
|
||||
},
|
||||
headerStyle,
|
||||
];
|
||||
|
||||
const { headerForceInset } = options;
|
||||
const forceInset = headerForceInset || { top: 'always', bottom: 'never' };
|
||||
|
||||
return (
|
||||
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
||||
<View style={StyleSheet.absoluteFill}>{options.headerBackground}</View>
|
||||
<View style={{ flex: 1 }}>{appBar}</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function warnIfHeaderStyleDefined(value, styleProp) {
|
||||
if (value !== undefined) {
|
||||
console.warn(
|
||||
`${styleProp} was given a value of ${value}, this has no effect on headerStyle.`
|
||||
<Animated.View {...rest}>
|
||||
<SafeAreaView
|
||||
style={containerStyles}
|
||||
forceInset={{ top: 'always', bottom: 'never' }}
|
||||
>
|
||||
<View style={styles.appBar}>{appBar}</View>
|
||||
</SafeAreaView>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -500,7 +477,7 @@ let platformContainerStyles;
|
||||
if (Platform.OS === 'ios') {
|
||||
platformContainerStyles = {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#A7A7AA',
|
||||
borderBottomColor: 'rgba(0, 0, 0, .3)',
|
||||
};
|
||||
} else {
|
||||
platformContainerStyles = {
|
||||
@@ -519,18 +496,15 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
||||
...platformContainerStyles,
|
||||
},
|
||||
transparentContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
...platformContainerStyles,
|
||||
appBar: {
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
item: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
iconMaskContainer: {
|
||||
@@ -554,29 +528,23 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: TITLE_OFFSET,
|
||||
right: TITLE_OFFSET,
|
||||
top: 0,
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
|
||||
alignItems: Platform.OS === 'ios' ? 'center' : 'flex-start',
|
||||
},
|
||||
left: {
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
right: {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
position: 'absolute',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Dimensions, I18nManager } from 'react-native';
|
||||
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
|
||||
|
||||
const crossFadeInterpolation = (first, index, last) => ({
|
||||
inputRange: [first, index - 0.9, index - 0.2, index, last],
|
||||
outputRange: [0, 0, 0.3, 1, 0],
|
||||
inputRange: [first, index - 0.75, index - 0.5, index, index + 0.5, last],
|
||||
outputRange: [0, 0, 0.2, 1, 0.5, 0],
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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);
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
import withOrientation from '../withOrientation';
|
||||
|
||||
const majorVersion = parseInt(Platform.Version, 10);
|
||||
@@ -177,24 +176,6 @@ class TabBarBottom extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_handleTabPress = index => {
|
||||
const { jumpToIndex, navigation } = this.props;
|
||||
const currentIndex = navigation.state.index;
|
||||
|
||||
if (currentIndex === index) {
|
||||
let childRoute = navigation.state.routes[index];
|
||||
if (childRoute.hasOwnProperty('index') && childRoute.index > 0) {
|
||||
navigation.dispatch(
|
||||
NavigationActions.popToTop({ key: childRoute.key })
|
||||
);
|
||||
} else {
|
||||
// TODO: do something to scroll to top
|
||||
}
|
||||
} else {
|
||||
jumpToIndex(index);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
position,
|
||||
@@ -254,13 +235,8 @@ class TabBarBottom extends React.PureComponent {
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
onPress={() =>
|
||||
onPress
|
||||
? onPress({
|
||||
previousScene,
|
||||
scene,
|
||||
jumpToIndex,
|
||||
defaultHandler: this._handleTabPress,
|
||||
})
|
||||
: this._handleTabPress(index)
|
||||
? onPress({ previousScene, scene, jumpToIndex })
|
||||
: jumpToIndex(index)
|
||||
}
|
||||
>
|
||||
<Animated.View style={[styles.tab, { backgroundColor }]}>
|
||||
|
||||
@@ -91,15 +91,7 @@ export default class TabBarTop extends React.PureComponent {
|
||||
const onPress = getOnPress(previousScene, scene);
|
||||
|
||||
if (onPress) {
|
||||
// To maintain the same API as `TabbarBottom`, we pass in a `defaultHandler`
|
||||
// even though I don't believe in this case it should be any different
|
||||
// than `jumpToIndex`.
|
||||
onPress({
|
||||
previousScene,
|
||||
scene,
|
||||
jumpToIndex,
|
||||
defaultHandler: jumpToIndex,
|
||||
});
|
||||
onPress({ previousScene, scene, jumpToIndex });
|
||||
} else {
|
||||
jumpToIndex(scene.index);
|
||||
}
|
||||
|
||||
@@ -179,19 +179,9 @@ class Transitioner extends React.Component {
|
||||
const prevTransitionProps = this._prevTransitionProps;
|
||||
this._prevTransitionProps = null;
|
||||
|
||||
const scenes = this.state.scenes.filter(isSceneNotStale);
|
||||
|
||||
const nextState = {
|
||||
...this.state,
|
||||
/**
|
||||
* Array.prototype.filter creates a new instance of an array
|
||||
* even if there were no elements removed. There are cases when
|
||||
* `this.state.scenes` will have no stale scenes (typically when
|
||||
* pushing a new route). As a result, components that rely on this prop
|
||||
* might enter an unnecessary render cycle.
|
||||
*/
|
||||
scenes:
|
||||
this.state.scenes.length === scenes.length ? this.state.scenes : scenes,
|
||||
scenes: this.state.scenes.filter(isSceneNotStale),
|
||||
};
|
||||
|
||||
this._transitionProps = buildTransitionProps(this.props, nextState);
|
||||
|
||||
@@ -248,7 +248,6 @@ exports[`TabBarBottom renders successfully 1`] = `
|
||||
"dispatch": undefined,
|
||||
"getParam": [Function],
|
||||
"goBack": [Function],
|
||||
"isFocused": [Function],
|
||||
"navigate": [Function],
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
|
||||
@@ -12,13 +12,9 @@ export default function withNavigationFocus(Component) {
|
||||
navigation: propTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
isFocused: this.getNavigation(props, context).isFocused(),
|
||||
};
|
||||
}
|
||||
state = {
|
||||
isFocused: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const navigation = this.getNavigation();
|
||||
@@ -36,8 +32,8 @@ export default function withNavigationFocus(Component) {
|
||||
this.subscriptions.forEach(sub => sub.remove());
|
||||
}
|
||||
|
||||
getNavigation = (props = this.props, context = this.context) => {
|
||||
const navigation = props.navigation || context.navigation;
|
||||
getNavigation = () => {
|
||||
const navigation = this.props.navigation || this.context.navigation;
|
||||
invariant(
|
||||
!!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.'
|
||||
|
||||
@@ -10,8 +10,6 @@ export default function withCachedChildNavigation(Comp) {
|
||||
return class extends React.PureComponent {
|
||||
static displayName = `withCachedChildNavigation(${displayName})`;
|
||||
|
||||
_childEventSubscribers = {};
|
||||
|
||||
componentWillMount() {
|
||||
this._updateNavigationProps(this.props.navigation);
|
||||
}
|
||||
@@ -20,23 +18,6 @@ export default function withCachedChildNavigation(Comp) {
|
||||
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 => {
|
||||
// Update props for each child route
|
||||
if (!this._childNavigationProps) {
|
||||
@@ -47,19 +28,13 @@ export default function withCachedChildNavigation(Comp) {
|
||||
if (childNavigation && childNavigation.state === route) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._childEventSubscribers[route.key]) {
|
||||
this._childEventSubscribers[route.key] = getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
route.key
|
||||
);
|
||||
}
|
||||
|
||||
this._childNavigationProps[route.key] = addNavigationHelpers({
|
||||
dispatch: navigation.dispatch,
|
||||
state: route,
|
||||
isFocused: this._isRouteFocused.bind(this, route),
|
||||
addListener: this._childEventSubscribers[route.key],
|
||||
addListener: getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
route.key
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user