Compare commits

..

4 Commits

Author SHA1 Message Date
Brent Vatne
f9fbffbfa0 Release 1.1.0 2018-02-19 18:15:37 -08:00
Brent Vatne
1b8422d77d Fix TabRouter to support shorthand route config 2018-02-19 18:13:22 -08:00
Brent Vatne
e27afea7ab Release 1.1.0-rc.5 2018-02-19 14:46:39 -08:00
Brent Vatne
ca6cd89118 Fix regression in modular back button 2018-02-19 14:46:25 -08:00
46 changed files with 600 additions and 1635 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,
});

View File

@@ -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);
};

View File

@@ -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",

View File

@@ -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"

View File

@@ -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',

View File

@@ -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": {

View File

@@ -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,
};

View File

@@ -1,9 +1,8 @@
import {
BackAndroid as DeprecatedBackAndroid,
BackHandler as ModernBackHandler,
MaskedViewIOS,
} from 'react-native';
const BackHandler = ModernBackHandler || DeprecatedBackAndroid;
export { BackHandler, MaskedViewIOS };
export { BackHandler };

View File

@@ -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 };

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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);
};

View File

@@ -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() {

View File

@@ -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();
});
});

View File

@@ -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>

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwitchNavigator renders successfully 1`] = `<View />`;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,
};
};

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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 />;

View File

@@ -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."`;

View File

@@ -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);
});
});

View File

@@ -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;

View File

@@ -22,7 +22,7 @@ class Card extends React.Component {
const styles = StyleSheet.create({
main: {
backgroundColor: '#EFEFF4',
backgroundColor: '#E9E9EF',
bottom: 0,
left: 0,
position: 'absolute',

View File

@@ -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}

View File

@@ -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}
/>
);
};

View File

@@ -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
),
});
};

View File

@@ -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',
},
});

View File

@@ -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],
});
/**

View File

@@ -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);

View File

@@ -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 }]}>

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -248,7 +248,6 @@ exports[`TabBarBottom renders successfully 1`] = `
"dispatch": undefined,
"getParam": [Function],
"goBack": [Function],
"isFocused": [Function],
"navigate": [Function],
"pop": [Function],
"popToTop": [Function],

View File

@@ -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.'

View File

@@ -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
),
});
});
};