mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-14 22:37:48 +08:00
Compare commits
5 Commits
2.2.0
...
@ericvicen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ea01673e5 | ||
|
|
71adb7cc4f | ||
|
|
77343cb096 | ||
|
|
accee76951 | ||
|
|
bbb8c4d8d3 |
26
.eslintrc
26
.eslintrc
@@ -7,18 +7,18 @@
|
|||||||
"prettier/react"
|
"prettier/react"
|
||||||
],
|
],
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"plugins": ["react", "prettier"],
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"jasmine": true
|
"jasmine": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": [
|
"prettier/prettier": ["error", {
|
||||||
"error",
|
"trailingComma": "es5",
|
||||||
{
|
"singleQuote": true
|
||||||
"trailingComma": "es5",
|
}],
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"no-underscore-dangle": "off",
|
"no-underscore-dangle": "off",
|
||||||
"no-use-before-define": "off",
|
"no-use-before-define": "off",
|
||||||
@@ -27,20 +27,24 @@
|
|||||||
"no-plusplus": "off",
|
"no-plusplus": "off",
|
||||||
"no-class-assign": "off",
|
"no-class-assign": "off",
|
||||||
"no-duplicate-imports": "off",
|
"no-duplicate-imports": "off",
|
||||||
|
|
||||||
"import/extensions": "off",
|
"import/extensions": "off",
|
||||||
"import/no-extraneous-dependencies": "off",
|
"import/no-extraneous-dependencies": "off",
|
||||||
"import/no-unresolved": "off",
|
"import/no-unresolved": "off",
|
||||||
|
|
||||||
"react/jsx-filename-extension": ["off", { "extensions": [".js", ".jsx"] }],
|
"react/jsx-filename-extension": [
|
||||||
|
"off", { "extensions": [".js", ".jsx"] }
|
||||||
|
],
|
||||||
|
|
||||||
"react/sort-comp": "off",
|
"react/sort-comp": "off",
|
||||||
"react/prefer-stateless-function": "off",
|
"react/prefer-stateless-function": "off",
|
||||||
"react/no-deprecated": "off",
|
|
||||||
|
|
||||||
"react/forbid-prop-types": "warn",
|
"react/forbid-prop-types": "warn",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/require-default-props": "off",
|
"react/require-default-props": "off",
|
||||||
"react/no-unused-prop-types": "off"
|
"react/no-unused-prop-types": "off",
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 6,
|
"ecmaVersion": 6,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# React Navigation
|
# React Navigation
|
||||||
|
|
||||||
[](https://badge.fury.io/js/react-navigation) [](https://codecov.io/gh/react-navigation/react-navigation) [](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [](https://reactnavigation.org/docs/contributing.html)
|
[](https://badge.fury.io/js/react-navigation) [](https://codecov.io/gh/react-community/react-navigation) [](https://reactnavigation.org/docs/guides/contributors)
|
||||||
|
|
||||||
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
|
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
|
||||||
|
|
||||||
@@ -55,4 +55,4 @@ This library has adopted a Code of Conduct that we expect project participants t
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
React Navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
|
React-navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is needed to hijack asset imports so that test files don't attempt
|
|
||||||
* to import them as JavaScript modules.
|
|
||||||
* See https://github.com/facebook/jest/issues/2663#issuecomment-317109798
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
process(src, filename, config, options) {
|
|
||||||
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -55,6 +55,8 @@ module.system=haste
|
|||||||
|
|
||||||
emoji=true
|
emoji=true
|
||||||
|
|
||||||
|
experimental.strict_type_args=true
|
||||||
|
|
||||||
munge_underscores=true
|
munge_underscores=true
|
||||||
|
|
||||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||||
@@ -75,5 +77,7 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*
|
|||||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||||
|
|
||||||
|
unsafe.enable_getters_and_setters=true
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
^0.67.0
|
^0.61.0
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ import renderer from 'react-test-renderer';
|
|||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const rendered = renderer.create(<App />).toJSON();
|
const rendered = renderer.create(<App />).toJSON();
|
||||||
// Will be null because the playground uses state persistence which happens asyncronously
|
expect(rendered).toBeTruthy();
|
||||||
expect(rendered).toEqual(null);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ A playground for experimenting with react-navigation in a pure-JS React Native a
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
|
||||||
|
|
||||||
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).
|
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/icons/splash.png"
|
"image": "./assets/icons/splash.png"
|
||||||
},
|
},
|
||||||
"sdkVersion": "27.0.0",
|
"sdkVersion": "25.0.0",
|
||||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||||
"packagerOpts": {
|
"packagerOpts": {
|
||||||
"assetExts": [
|
"assetExts": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import {
|
|||||||
StatusBar,
|
StatusBar,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView, createStackNavigator } from 'react-navigation';
|
import { SafeAreaView, StackNavigator } from 'react-navigation';
|
||||||
|
|
||||||
import CustomTabs from './CustomTabs';
|
import CustomTabs from './CustomTabs';
|
||||||
import CustomTransitioner from './CustomTransitioner';
|
import CustomTransitioner from './CustomTransitioner';
|
||||||
@@ -27,34 +27,18 @@ import ModalStack from './ModalStack';
|
|||||||
import StacksInTabs from './StacksInTabs';
|
import StacksInTabs from './StacksInTabs';
|
||||||
import StacksOverTabs from './StacksOverTabs';
|
import StacksOverTabs from './StacksOverTabs';
|
||||||
import StacksWithKeys from './StacksWithKeys';
|
import StacksWithKeys from './StacksWithKeys';
|
||||||
import InactiveStack from './InactiveStack';
|
|
||||||
import StackWithCustomHeaderBackImage from './StackWithCustomHeaderBackImage';
|
|
||||||
import SimpleStack from './SimpleStack';
|
import SimpleStack from './SimpleStack';
|
||||||
import StackWithHeaderPreset from './StackWithHeaderPreset';
|
import StackWithHeaderPreset from './StackWithHeaderPreset';
|
||||||
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
||||||
import SimpleTabs from './SimpleTabs';
|
import SimpleTabs from './SimpleTabs';
|
||||||
import SwitchWithStacks from './SwitchWithStacks';
|
import TabAnimations from './TabAnimations';
|
||||||
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
||||||
import KeyboardHandlingExample from './KeyboardHandlingExample';
|
|
||||||
|
|
||||||
const ExampleInfo = {
|
const ExampleInfo = {
|
||||||
SimpleStack: {
|
SimpleStack: {
|
||||||
name: 'Stack Example',
|
name: 'Stack Example',
|
||||||
description: 'A card stack',
|
description: 'A card stack',
|
||||||
},
|
},
|
||||||
SwitchWithStacks: {
|
|
||||||
name: 'Switch between routes',
|
|
||||||
description: 'Jump between routes',
|
|
||||||
},
|
|
||||||
InactiveStack: {
|
|
||||||
name: 'Navigate idempotently to stacks in inactive routes',
|
|
||||||
description:
|
|
||||||
'An inactive route in a stack should be given the opportunity to handle actions',
|
|
||||||
},
|
|
||||||
StackWithCustomHeaderBackImage: {
|
|
||||||
name: 'Custom header back image',
|
|
||||||
description: 'Stack with custom header back image',
|
|
||||||
},
|
|
||||||
SimpleTabs: {
|
SimpleTabs: {
|
||||||
name: 'Tabs Example',
|
name: 'Tabs Example',
|
||||||
description: 'Tabs following platform conventions',
|
description: 'Tabs following platform conventions',
|
||||||
@@ -67,6 +51,10 @@ const ExampleInfo = {
|
|||||||
name: 'UIKit-style Header Transitions',
|
name: 'UIKit-style Header Transitions',
|
||||||
description: 'Masked back button and sliding header items. iOS only.',
|
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: {
|
StackWithTranslucentHeader: {
|
||||||
name: 'Translucent Header',
|
name: 'Translucent Header',
|
||||||
description: 'Render arbitrary translucent content in header background.',
|
description: 'Render arbitrary translucent content in header background.',
|
||||||
@@ -117,26 +105,23 @@ const ExampleInfo = {
|
|||||||
name: 'Link to Settings Tab',
|
name: 'Link to Settings Tab',
|
||||||
description: 'Deep linking into a route in tab',
|
description: 'Deep linking into a route in tab',
|
||||||
},
|
},
|
||||||
|
TabAnimations: {
|
||||||
|
name: 'Animated Tabs Example',
|
||||||
|
description: 'Tab transitions have custom animations',
|
||||||
|
},
|
||||||
TabsWithNavigationFocus: {
|
TabsWithNavigationFocus: {
|
||||||
name: 'withNavigationFocus',
|
name: 'withNavigationFocus',
|
||||||
description: 'Receive the focus prop to know when a screen is focused',
|
description: 'Receive the focus prop to know when a screen is focused',
|
||||||
},
|
},
|
||||||
KeyboardHandlingExample: {
|
|
||||||
name: 'Keyboard Handling Example',
|
|
||||||
description:
|
|
||||||
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExampleRoutes = {
|
const ExampleRoutes = {
|
||||||
SimpleStack,
|
SimpleStack: SimpleStack,
|
||||||
SwitchWithStacks,
|
|
||||||
SimpleTabs: SimpleTabs,
|
SimpleTabs: SimpleTabs,
|
||||||
Drawer: Drawer,
|
Drawer: Drawer,
|
||||||
// MultipleDrawer: {
|
// MultipleDrawer: {
|
||||||
// screen: MultipleDrawer,
|
// screen: MultipleDrawer,
|
||||||
// },
|
// },
|
||||||
StackWithCustomHeaderBackImage: StackWithCustomHeaderBackImage,
|
|
||||||
StackWithHeaderPreset: StackWithHeaderPreset,
|
StackWithHeaderPreset: StackWithHeaderPreset,
|
||||||
StackWithTranslucentHeader: StackWithTranslucentHeader,
|
StackWithTranslucentHeader: StackWithTranslucentHeader,
|
||||||
TabsInDrawer: TabsInDrawer,
|
TabsInDrawer: TabsInDrawer,
|
||||||
@@ -154,10 +139,8 @@ const ExampleRoutes = {
|
|||||||
screen: SimpleTabs,
|
screen: SimpleTabs,
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
},
|
},
|
||||||
|
TabAnimations,
|
||||||
TabsWithNavigationFocus,
|
TabsWithNavigationFocus,
|
||||||
KeyboardHandlingExample,
|
|
||||||
// This is commented out because it's rarely useful
|
|
||||||
// InactiveStack,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -168,7 +151,7 @@ class MainScreen extends React.Component<any, State> {
|
|||||||
scrollY: new Animated.Value(0),
|
scrollY: new Animated.Value(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentWillMount() {
|
||||||
Asset.fromModule(
|
Asset.fromModule(
|
||||||
require('react-navigation/src/views/assets/back-icon-mask.png')
|
require('react-navigation/src/views/assets/back-icon-mask.png')
|
||||||
).downloadAsync();
|
).downloadAsync();
|
||||||
@@ -303,7 +286,7 @@ class MainScreen extends React.Component<any, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppNavigator = createStackNavigator(
|
const AppNavigator = StackNavigator(
|
||||||
{
|
{
|
||||||
...ExampleRoutes,
|
...ExampleRoutes,
|
||||||
Index: {
|
Index: {
|
||||||
@@ -322,7 +305,8 @@ const AppNavigator = createStackNavigator(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default AppNavigator;
|
// export default () => <AppNavigator />;
|
||||||
|
export default SimpleStack;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Platform,
|
Platform,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@@ -19,7 +20,6 @@ import {
|
|||||||
TabRouter,
|
TabRouter,
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
|
Button,
|
||||||
Easing,
|
Easing,
|
||||||
Image,
|
Image,
|
||||||
Platform,
|
Platform,
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
createNavigator,
|
createNavigator,
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<SafeAreaView forceInset={{ top: 'always' }}>
|
<SafeAreaView forceInset={{ top: 'always' }}>
|
||||||
|
|||||||
@@ -3,15 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, ScrollView, StatusBar } from 'react-native';
|
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
StackNavigator,
|
||||||
createDrawerNavigator,
|
DrawerNavigator,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@@ -32,26 +31,6 @@ const InboxScreen = ({ navigation }) => (
|
|||||||
<MyNavScreen banner={'Inbox Screen'} navigation={navigation} />
|
<MyNavScreen banner={'Inbox Screen'} navigation={navigation} />
|
||||||
);
|
);
|
||||||
InboxScreen.navigationOptions = {
|
InboxScreen.navigationOptions = {
|
||||||
headerTitle: 'Inbox',
|
|
||||||
};
|
|
||||||
|
|
||||||
const EmailScreen = ({ navigation }) => (
|
|
||||||
<MyNavScreen banner={'Email Screen'} navigation={navigation} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const DraftsScreen = ({ navigation }) => (
|
|
||||||
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
|
|
||||||
);
|
|
||||||
DraftsScreen.navigationOptions = {
|
|
||||||
headerTitle: 'Drafts',
|
|
||||||
};
|
|
||||||
|
|
||||||
const InboxStack = createStackNavigator({
|
|
||||||
Inbox: { screen: InboxScreen },
|
|
||||||
Email: { screen: EmailScreen },
|
|
||||||
});
|
|
||||||
|
|
||||||
InboxStack.navigationOptions = {
|
|
||||||
drawerLabel: 'Inbox',
|
drawerLabel: 'Inbox',
|
||||||
drawerIcon: ({ tintColor }) => (
|
drawerIcon: ({ tintColor }) => (
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
@@ -62,19 +41,31 @@ InboxStack.navigationOptions = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DraftsStack = createStackNavigator({
|
const EmailScreen = ({ navigation }) => (
|
||||||
Drafts: { screen: DraftsScreen },
|
<MyNavScreen banner={'Email Screen'} navigation={navigation} />
|
||||||
Email: { screen: EmailScreen },
|
);
|
||||||
});
|
|
||||||
|
|
||||||
DraftsStack.navigationOptions = {
|
const DraftsScreen = ({ navigation }) => (
|
||||||
|
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
|
||||||
|
);
|
||||||
|
DraftsScreen.navigationOptions = {
|
||||||
drawerLabel: 'Drafts',
|
drawerLabel: 'Drafts',
|
||||||
drawerIcon: ({ tintColor }) => (
|
drawerIcon: ({ tintColor }) => (
|
||||||
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
|
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DrawerExample = createDrawerNavigator(
|
const InboxStack = StackNavigator({
|
||||||
|
Inbox: { screen: InboxScreen },
|
||||||
|
Email: { screen: EmailScreen },
|
||||||
|
});
|
||||||
|
|
||||||
|
const DraftsStack = StackNavigator({
|
||||||
|
Drafts: { screen: DraftsScreen },
|
||||||
|
Email: { screen: EmailScreen },
|
||||||
|
});
|
||||||
|
|
||||||
|
const DrawerExample = DrawerNavigator(
|
||||||
{
|
{
|
||||||
Inbox: {
|
Inbox: {
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Button, Text, StatusBar, View, StyleSheet } from 'react-native';
|
|
||||||
import {
|
|
||||||
SafeAreaView,
|
|
||||||
createStackNavigator,
|
|
||||||
createSwitchNavigator,
|
|
||||||
NavigationActions,
|
|
||||||
} from 'react-navigation';
|
|
||||||
|
|
||||||
const runSubRoutes = navigation => {
|
|
||||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
|
|
||||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'Second2' }));
|
|
||||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const runSubRoutesWithIntermediate = navigation => {
|
|
||||||
navigation.dispatch(toFirst1);
|
|
||||||
navigation.dispatch(toSecond2);
|
|
||||||
navigation.dispatch(toFirst);
|
|
||||||
navigation.dispatch(toFirst2);
|
|
||||||
};
|
|
||||||
|
|
||||||
const runSubAction = navigation => {
|
|
||||||
navigation.dispatch(toFirst2);
|
|
||||||
navigation.dispatch(toSecond2);
|
|
||||||
navigation.dispatch(toFirstChild1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DummyScreen = ({ routeName, navigation, style }) => {
|
|
||||||
return (
|
|
||||||
<SafeAreaView
|
|
||||||
style={[
|
|
||||||
StyleSheet.absoluteFill,
|
|
||||||
{
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: 'white',
|
|
||||||
},
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={{ fontWeight: '800' }}>
|
|
||||||
{routeName}({navigation.state.key})
|
|
||||||
</Text>
|
|
||||||
<View>
|
|
||||||
<Button title="back" onPress={() => navigation.goBack()} />
|
|
||||||
<Button title="dismiss" onPress={() => navigation.dismiss()} />
|
|
||||||
<Button
|
|
||||||
title="between sub-routes"
|
|
||||||
onPress={() => runSubRoutes(navigation)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
title="between sub-routes (with intermediate)"
|
|
||||||
onPress={() => runSubRoutesWithIntermediate(navigation)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
title="with sub-action"
|
|
||||||
onPress={() => runSubAction(navigation)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createDummyScreen = routeName => {
|
|
||||||
const BoundDummyScreen = props => DummyScreen({ ...props, routeName });
|
|
||||||
return BoundDummyScreen;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toFirst = NavigationActions.navigate({ routeName: 'First' });
|
|
||||||
const toFirst1 = NavigationActions.navigate({ routeName: 'First1' });
|
|
||||||
const toFirst2 = NavigationActions.navigate({ routeName: 'First2' });
|
|
||||||
const toSecond2 = NavigationActions.navigate({ routeName: 'Second2' });
|
|
||||||
const toFirstChild1 = NavigationActions.navigate({
|
|
||||||
routeName: 'First',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'First1' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default createStackNavigator(
|
|
||||||
{
|
|
||||||
Other: createDummyScreen('Leaf'),
|
|
||||||
First: createStackNavigator({
|
|
||||||
First1: createDummyScreen('First1'),
|
|
||||||
First2: createDummyScreen('First2'),
|
|
||||||
}),
|
|
||||||
Second: createStackNavigator({
|
|
||||||
Second1: createDummyScreen('Second1'),
|
|
||||||
Second2: createDummyScreen('Second2'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerMode: 'none',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { StatusBar, View, TextInput, InteractionManager } from 'react-native';
|
|
||||||
import { createStackNavigator, withNavigationFocus } from 'react-navigation';
|
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
class ScreenOne extends React.Component {
|
|
||||||
static navigationOptions = {
|
|
||||||
title: 'Home',
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
return (
|
|
||||||
<View style={{ paddingTop: 30 }}>
|
|
||||||
<Button
|
|
||||||
onPress={() => navigation.push('ScreenTwo')}
|
|
||||||
title="Push screen with focused text input"
|
|
||||||
/>
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScreenTwo extends React.Component {
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: navigation.getParam('inputValue', 'Screen w/ Input'),
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
this._textInput.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
return (
|
|
||||||
<View style={{ paddingTop: 30 }}>
|
|
||||||
<View style={{ alignSelf: 'center', paddingVertical: 20 }}>
|
|
||||||
<TextInput
|
|
||||||
ref={c => (this._textInput = c)}
|
|
||||||
onChangeText={inputValue => navigation.setParams({ inputValue })}
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'white',
|
|
||||||
height: 24,
|
|
||||||
width: 150,
|
|
||||||
borderColor: '#555',
|
|
||||||
borderWidth: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createStackNavigator({
|
|
||||||
ScreenOne,
|
|
||||||
ScreenTwo: withNavigationFocus(ScreenTwo),
|
|
||||||
});
|
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, StatusBar, Text } from 'react-native';
|
import { Button, ScrollView, StatusBar, Text } from 'react-native';
|
||||||
import { SafeAreaView, createStackNavigator } from 'react-navigation';
|
import { SafeAreaView, StackNavigator } from 'react-navigation';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@@ -32,8 +31,7 @@ const MyNavScreen = ({ navigation, banner }) => (
|
|||||||
headerVisible:
|
headerVisible:
|
||||||
!navigation.state.params ||
|
!navigation.state.params ||
|
||||||
!navigation.state.params.headerVisible,
|
!navigation.state.params.headerVisible,
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
@@ -59,7 +57,7 @@ MyProfileScreen.navigationOptions = ({ navigation }) => ({
|
|||||||
title: `${navigation.state.params.name}'s Profile!`,
|
title: `${navigation.state.params.name}'s Profile!`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ProfileNavigator = createStackNavigator(
|
const ProfileNavigator = StackNavigator(
|
||||||
{
|
{
|
||||||
Home: {
|
Home: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
@@ -89,7 +87,7 @@ MyHeaderTestScreen.navigationOptions = ({ navigation }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModalStack = createStackNavigator(
|
const ModalStack = StackNavigator(
|
||||||
{
|
{
|
||||||
ProfileNavigator: {
|
ProfileNavigator: {
|
||||||
screen: ProfileNavigator,
|
screen: ProfileNavigator,
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, ScrollView, StyleSheet } from 'react-native';
|
import { Button, Platform, ScrollView, StyleSheet } from 'react-native';
|
||||||
import { createDrawerNavigator } from 'react-navigation';
|
import { DrawerNavigator } from 'react-navigation';
|
||||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView style={styles.container}>
|
<ScrollView style={styles.container}>
|
||||||
@@ -41,7 +40,7 @@ DraftsScreen.navigationOptions = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DrawerExample = createDrawerNavigator(
|
const DrawerExample = DrawerNavigator(
|
||||||
{
|
{
|
||||||
Inbox: {
|
Inbox: {
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -60,7 +59,7 @@ const DrawerExample = createDrawerNavigator(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const MainDrawerExample = createDrawerNavigator({
|
const MainDrawerExample = DrawerNavigator({
|
||||||
Drafts: {
|
Drafts: {
|
||||||
screen: DrawerExample,
|
screen: DrawerExample,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,42 +4,22 @@
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
NavigationScreenProp,
|
NavigationScreenProp,
|
||||||
NavigationState,
|
|
||||||
NavigationStateRoute,
|
|
||||||
NavigationEventSubscription,
|
NavigationEventSubscription,
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ScrollView, StatusBar } from 'react-native';
|
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||||
import {
|
import { StackNavigator, SafeAreaView, withNavigation } from 'react-navigation';
|
||||||
createStackNavigator,
|
|
||||||
SafeAreaView,
|
|
||||||
withNavigation,
|
|
||||||
NavigationActions,
|
|
||||||
StackActions,
|
|
||||||
} from 'react-navigation';
|
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
import { HeaderButtons } from './commonComponents/HeaderButtons';
|
|
||||||
|
|
||||||
type MyNavScreenProps = {
|
type MyNavScreenProps = {
|
||||||
navigation: NavigationScreenProp<NavigationState>,
|
navigation: NavigationScreenProp<*>,
|
||||||
banner: React.Node,
|
banner: React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
type BackButtonProps = {
|
class MyBackButton extends React.Component<any, any> {
|
||||||
navigation: NavigationScreenProp<NavigationStateRoute>,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyBackButton extends React.Component<BackButtonProps, any> {
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <Button onPress={this._navigateBack} title="Custom Back" />;
|
||||||
<HeaderButtons>
|
|
||||||
<HeaderButtons.Item title="Back" onPress={this._navigateBack} />
|
|
||||||
</HeaderButtons>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigateBack = () => {
|
_navigateBack = () => {
|
||||||
@@ -52,46 +32,24 @@ const MyBackButtonWithNavigation = withNavigation(MyBackButton);
|
|||||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||||
render() {
|
render() {
|
||||||
const { navigation, banner } = this.props;
|
const { navigation, banner } = this.props;
|
||||||
const { push, replace, popToTop, pop, dismiss } = navigation;
|
|
||||||
invariant(
|
|
||||||
push && replace && popToTop && pop && dismiss,
|
|
||||||
'missing action creators for StackNavigator'
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<SampleText>{banner}</SampleText>
|
<SampleText>{banner}</SampleText>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => push('Profile', { name: 'Jane' })}
|
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||||
title="Push a profile screen"
|
title="Push a profile screen"
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
onPress={() =>
|
|
||||||
navigation.dispatch(
|
|
||||||
StackActions.reset({
|
|
||||||
index: 0,
|
|
||||||
actions: [
|
|
||||||
NavigationActions.navigate({
|
|
||||||
routeName: 'Photos',
|
|
||||||
params: { name: 'Jane' },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
title="Reset photos"
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
||||||
title="Navigate to a photos screen"
|
title="Navigate to a photos screen"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => replace('Profile', { name: 'Lucy' })}
|
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||||
title="Replace with profile"
|
title="Replace with profile"
|
||||||
/>
|
/>
|
||||||
<Button onPress={() => popToTop()} title="Pop to top" />
|
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||||
<Button onPress={() => pop()} title="Pop" />
|
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||||
<Button onPress={() => navigation.goBack()} title="Go back" />
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
<Button onPress={() => dismiss()} title="Dismiss" />
|
|
||||||
<StatusBar barStyle="default" />
|
<StatusBar barStyle="default" />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -99,7 +57,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MyHomeScreenProps = {
|
type MyHomeScreenProps = {
|
||||||
navigation: NavigationScreenProp<NavigationState>,
|
navigation: NavigationScreenProp<*>,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||||
@@ -143,7 +101,7 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MyPhotosScreenProps = {
|
type MyPhotosScreenProps = {
|
||||||
navigation: NavigationScreenProp<NavigationState>,
|
navigation: NavigationScreenProp<*>,
|
||||||
};
|
};
|
||||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
@@ -184,7 +142,7 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
|||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
return (
|
return (
|
||||||
<MyNavScreen
|
<MyNavScreen
|
||||||
banner={`${navigation.getParam('name')}'s Photos`}
|
banner={`${navigation.state.params.name}'s Photos`}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -193,9 +151,9 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
|||||||
|
|
||||||
const MyProfileScreen = ({ navigation }) => (
|
const MyProfileScreen = ({ navigation }) => (
|
||||||
<MyNavScreen
|
<MyNavScreen
|
||||||
banner={`${
|
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
|
||||||
navigation.getParam('mode') === 'edit' ? 'Now Editing ' : ''
|
navigation.state.params.name
|
||||||
}${navigation.getParam('name')}'s Profile`}
|
}'s Profile`}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -210,19 +168,17 @@ MyProfileScreen.navigationOptions = props => {
|
|||||||
// Render a button on the right side of the header.
|
// Render a button on the right side of the header.
|
||||||
// When pressed switches the screen to edit mode.
|
// When pressed switches the screen to edit mode.
|
||||||
headerRight: (
|
headerRight: (
|
||||||
<HeaderButtons>
|
<Button
|
||||||
<HeaderButtons.Item
|
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
onPress={() =>
|
||||||
onPress={() =>
|
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
</HeaderButtons>
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const SimpleStack = createStackNavigator({
|
const SimpleStack = StackNavigator({
|
||||||
Home: {
|
Home: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ import type {
|
|||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, ScrollView, StatusBar, View } from 'react-native';
|
import { Button, Platform, ScrollView, StatusBar, View } from 'react-native';
|
||||||
import { SafeAreaView, createBottomTabNavigator } from 'react-navigation';
|
import { SafeAreaView, TabNavigator } from 'react-navigation';
|
||||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||||
@@ -144,7 +143,7 @@ MySettingsScreen.navigationOptions = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const SimpleTabs = createBottomTabNavigator(
|
const SimpleTabs = TabNavigator(
|
||||||
{
|
{
|
||||||
Home: {
|
Home: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
@@ -164,6 +163,8 @@ const SimpleTabs = createBottomTabNavigator(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
lazy: true,
|
||||||
|
removeClippedSubviews: true,
|
||||||
tabBarOptions: {
|
tabBarOptions: {
|
||||||
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
|
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
/**
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NavigationScreenProp } from 'react-navigation';
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Image, Button, StatusBar, StyleSheet } from 'react-native';
|
|
||||||
import { createStackNavigator, SafeAreaView } from 'react-navigation';
|
|
||||||
import SampleText from './SampleText';
|
|
||||||
|
|
||||||
type MyNavScreenProps = {
|
|
||||||
navigation: NavigationScreenProp<*>,
|
|
||||||
banner: React.Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyCustomHeaderBackImage extends React.Component<any, any> {
|
|
||||||
render() {
|
|
||||||
const source = require('./assets/back.png');
|
|
||||||
return (
|
|
||||||
<Image
|
|
||||||
source={source}
|
|
||||||
style={[styles.myCustomHeaderBackImage, this.props.style]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
|
||||||
render() {
|
|
||||||
const { navigation, banner } = this.props;
|
|
||||||
return (
|
|
||||||
<SafeAreaView>
|
|
||||||
<SampleText>{banner}</SampleText>
|
|
||||||
<Button
|
|
||||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
|
||||||
title="Navigate to a photos screen"
|
|
||||||
/>
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyHomeScreenProps = {
|
|
||||||
navigation: NavigationScreenProp<*>,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
|
||||||
static navigationOptions = {
|
|
||||||
title: 'Welcome',
|
|
||||||
headerBackTitle: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyPhotosScreenProps = {
|
|
||||||
navigation: NavigationScreenProp<*>,
|
|
||||||
};
|
|
||||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: `${navigation.state.params.name}'s photos`,
|
|
||||||
headerBackTitle: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
return (
|
|
||||||
<SafeAreaView>
|
|
||||||
<SampleText>{`${navigation.state.params.name}'s Photos`}</SampleText>
|
|
||||||
<Button
|
|
||||||
onPress={() => navigation.navigate('Profile', { name: 'Jane' })}
|
|
||||||
title="Navigate to a profile screen"
|
|
||||||
/>
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyProfileScreenProps = {
|
|
||||||
navigation: NavigationScreenProp<*>,
|
|
||||||
};
|
|
||||||
class MyProfileScreen extends React.Component<MyProfileScreenProps> {
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: 'Profile',
|
|
||||||
headerBackImage: (
|
|
||||||
<MyCustomHeaderBackImage style={styles.myCustomHeaderBackImageAlt} />
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
return (
|
|
||||||
<SafeAreaView>
|
|
||||||
<SampleText>{`${navigation.state.params.name}'s Profile`}</SampleText>
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StackWithCustomHeaderBackImage = createStackNavigator(
|
|
||||||
{
|
|
||||||
Home: {
|
|
||||||
screen: MyHomeScreen,
|
|
||||||
},
|
|
||||||
Photos: {
|
|
||||||
path: 'photos/:name',
|
|
||||||
screen: MyPhotosScreen,
|
|
||||||
},
|
|
||||||
Profile: {
|
|
||||||
path: 'profile/:name',
|
|
||||||
screen: MyProfileScreen,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
navigationOptions: {
|
|
||||||
headerBackImage: MyCustomHeaderBackImage,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default StackWithCustomHeaderBackImage;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
myCustomHeaderBackImage: {
|
|
||||||
height: 14.5,
|
|
||||||
width: 24,
|
|
||||||
marginLeft: 9,
|
|
||||||
marginRight: 12,
|
|
||||||
marginVertical: 12,
|
|
||||||
resizeMode: 'contain',
|
|
||||||
},
|
|
||||||
myCustomHeaderBackImageAlt: {
|
|
||||||
tintColor: '#f00',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -4,11 +4,8 @@
|
|||||||
import type { NavigationScreenProp } from 'react-navigation';
|
import type { NavigationScreenProp } from 'react-navigation';
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ScrollView, StatusBar } from 'react-native';
|
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||||
import { createStackNavigator, SafeAreaView } from 'react-navigation';
|
import { StackNavigator, SafeAreaView } from 'react-navigation';
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
type NavScreenProps = {
|
type NavScreenProps = {
|
||||||
navigation: NavigationScreenProp<*>,
|
navigation: NavigationScreenProp<*>,
|
||||||
@@ -21,17 +18,15 @@ class HomeScreen extends React.Component<NavScreenProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const { push } = navigation;
|
|
||||||
invariant(push, 'missing `push` action creator for StackNavigator');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||||
<Button onPress={() => push('Other')} title="Push another screen" />
|
|
||||||
<Button
|
<Button
|
||||||
onPress={() => push('ScreenWithNoHeader')}
|
onPress={() => navigation.push('Other')}
|
||||||
title="Push screen with no header"
|
title="Push another screen"
|
||||||
/>
|
/>
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
|
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||||
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
<StatusBar barStyle="default" />
|
<StatusBar barStyle="default" />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -45,20 +40,14 @@ class OtherScreen extends React.Component<NavScreenProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const { push, pop } = navigation;
|
|
||||||
invariant(push && pop, 'missing action creators for StackNavigator');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => push('ScreenWithLongTitle')}
|
onPress={() => navigation.push('Other')}
|
||||||
title="Push another screen"
|
title="Push another screen"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||||
onPress={() => push('ScreenWithNoHeader')}
|
|
||||||
title="Push screen with no header"
|
|
||||||
/>
|
|
||||||
<Button onPress={() => pop()} title="Pop" />
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
<StatusBar barStyle="default" />
|
<StatusBar barStyle="default" />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
@@ -66,54 +55,10 @@ class OtherScreen extends React.Component<NavScreenProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenWithLongTitle extends React.Component<NavScreenProps> {
|
const StackWithHeaderPreset = StackNavigator(
|
||||||
static navigationOptions = {
|
|
||||||
title: "Another title that's kind of long",
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
const { pop } = navigation;
|
|
||||||
invariant(pop, 'missing `pop` action creator for StackNavigator');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
|
||||||
<Button onPress={() => pop()} title="Pop" />
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScreenWithNoHeader extends React.Component<NavScreenProps> {
|
|
||||||
static navigationOptions = {
|
|
||||||
header: null,
|
|
||||||
title: 'No Header',
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
const { push, pop } = navigation;
|
|
||||||
invariant(push && pop, 'missing action creators for StackNavigator');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
|
||||||
<Button onPress={() => push('Other')} title="Push another screen" />
|
|
||||||
<Button onPress={() => pop()} title="Pop" />
|
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StackWithHeaderPreset = createStackNavigator(
|
|
||||||
{
|
{
|
||||||
Home: HomeScreen,
|
Home: HomeScreen,
|
||||||
Other: OtherScreen,
|
Other: OtherScreen,
|
||||||
ScreenWithNoHeader: ScreenWithNoHeader,
|
|
||||||
ScreenWithLongTitle: ScreenWithLongTitle,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerTransitionPreset: 'uikit',
|
headerTransitionPreset: 'uikit',
|
||||||
|
|||||||
@@ -12,18 +12,15 @@ import { isIphoneX } from 'react-native-iphone-x-helper';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { BlurView, Constants } from 'expo';
|
import { BlurView, Constants } from 'expo';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Platform,
|
Platform,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Header, createStackNavigator } from 'react-navigation';
|
import { Header, StackNavigator } from 'react-navigation';
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
import { HeaderButtons } from './commonComponents/HeaderButtons';
|
|
||||||
|
|
||||||
type MyNavScreenProps = {
|
type MyNavScreenProps = {
|
||||||
navigation: NavigationScreenProp<*>,
|
navigation: NavigationScreenProp<*>,
|
||||||
@@ -33,16 +30,11 @@ type MyNavScreenProps = {
|
|||||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||||
render() {
|
render() {
|
||||||
const { navigation, banner } = this.props;
|
const { navigation, banner } = this.props;
|
||||||
const { push, replace, popToTop, pop } = navigation;
|
|
||||||
invariant(
|
|
||||||
push && replace && popToTop && pop,
|
|
||||||
'missing action creators for StackNavigator'
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
|
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
|
||||||
<SampleText>{banner}</SampleText>
|
<SampleText>{banner}</SampleText>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => push('Profile', { name: 'Jane' })}
|
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||||
title="Push a profile screen"
|
title="Push a profile screen"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -50,11 +42,11 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
|||||||
title="Navigate to a photos screen"
|
title="Navigate to a photos screen"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => replace('Profile', { name: 'Lucy' })}
|
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||||
title="Replace with profile"
|
title="Replace with profile"
|
||||||
/>
|
/>
|
||||||
<Button onPress={() => popToTop()} title="Pop to top" />
|
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||||
<Button onPress={() => pop()} title="Pop" />
|
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
<StatusBar barStyle="default" />
|
<StatusBar barStyle="default" />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -201,19 +193,17 @@ MyProfileScreen.navigationOptions = props => {
|
|||||||
// Render a button on the right side of the header.
|
// Render a button on the right side of the header.
|
||||||
// When pressed switches the screen to edit mode.
|
// When pressed switches the screen to edit mode.
|
||||||
headerRight: (
|
headerRight: (
|
||||||
<HeaderButtons>
|
<Button
|
||||||
<HeaderButtons.Item
|
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
onPress={() =>
|
||||||
onPress={() =>
|
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
</HeaderButtons>
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const StackWithTranslucentHeader = createStackNavigator(
|
const StackWithTranslucentHeader = StackNavigator(
|
||||||
{
|
{
|
||||||
Home: {
|
Home: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
|
|||||||
@@ -3,16 +3,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, StatusBar } from 'react-native';
|
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||||
import {
|
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
|
||||||
SafeAreaView,
|
|
||||||
createStackNavigator,
|
|
||||||
createBottomTabNavigator,
|
|
||||||
} from 'react-navigation';
|
|
||||||
|
|
||||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@@ -56,7 +51,7 @@ const MySettingsScreen = ({ navigation }) => (
|
|||||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const MainTab = createStackNavigator({
|
const MainTab = StackNavigator({
|
||||||
Home: {
|
Home: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -73,7 +68,7 @@ const MainTab = createStackNavigator({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const SettingsTab = createStackNavigator({
|
const SettingsTab = StackNavigator({
|
||||||
Settings: {
|
Settings: {
|
||||||
screen: MySettingsScreen,
|
screen: MySettingsScreen,
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -89,7 +84,7 @@ const SettingsTab = createStackNavigator({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const StacksInTabs = createBottomTabNavigator(
|
const StacksInTabs = TabNavigator(
|
||||||
{
|
{
|
||||||
MainTab: {
|
MainTab: {
|
||||||
screen: MainTab,
|
screen: MainTab,
|
||||||
@@ -121,9 +116,9 @@ const StacksInTabs = createBottomTabNavigator(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tabBarOptions: {
|
tabBarPosition: 'bottom',
|
||||||
showLabel: false,
|
animationEnabled: false,
|
||||||
},
|
swipeEnabled: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, StatusBar } from 'react-native';
|
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||||
import {
|
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
|
||||||
SafeAreaView,
|
|
||||||
createStackNavigator,
|
|
||||||
createBottomTabNavigator,
|
|
||||||
} from 'react-navigation';
|
|
||||||
|
|
||||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
const MyNavScreen = ({ navigation, banner }) => (
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@@ -55,7 +50,7 @@ const MySettingsScreen = ({ navigation }) => (
|
|||||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const TabNav = createBottomTabNavigator(
|
const TabNav = TabNavigator(
|
||||||
{
|
{
|
||||||
MainTab: {
|
MainTab: {
|
||||||
screen: MyHomeScreen,
|
screen: MyHomeScreen,
|
||||||
@@ -94,20 +89,7 @@ const TabNav = createBottomTabNavigator(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
TabNav.navigationOptions = ({ navigation }) => {
|
const StacksOverTabs = StackNavigator({
|
||||||
let { routeName } = navigation.state.routes[navigation.state.index];
|
|
||||||
let title;
|
|
||||||
if (routeName === 'SettingsTab') {
|
|
||||||
title = 'Settings';
|
|
||||||
} else if (routeName === 'MainTab') {
|
|
||||||
title = 'Home';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const StacksOverTabs = createStackNavigator({
|
|
||||||
Root: {
|
Root: {
|
||||||
screen: TabNav,
|
screen: TabNav,
|
||||||
},
|
},
|
||||||
@@ -120,9 +102,9 @@ const StacksOverTabs = createStackNavigator({
|
|||||||
Profile: {
|
Profile: {
|
||||||
screen: MyProfileScreen,
|
screen: MyProfileScreen,
|
||||||
path: '/people/:name',
|
path: '/people/:name',
|
||||||
navigationOptions: ({ navigation }) => ({
|
navigationOptions: ({ navigation }) => {
|
||||||
title: `${navigation.state.params.name}'s Profile!`,
|
title: `${navigation.state.params.name}'s Profile!`;
|
||||||
}),
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StatusBar, Text, View } from 'react-native';
|
import { Button, StatusBar, Text, View } from 'react-native';
|
||||||
import { createStackNavigator } from 'react-navigation';
|
import { StackNavigator } from 'react-navigation';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
class HomeScreen extends React.Component<any, any> {
|
class HomeScreen extends React.Component<any, any> {
|
||||||
render() {
|
render() {
|
||||||
@@ -82,7 +81,7 @@ class SettingsScreen extends React.Component<any, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Stack = createStackNavigator(
|
const Stack = StackNavigator(
|
||||||
{
|
{
|
||||||
Home: {
|
Home: {
|
||||||
screen: HomeScreen,
|
screen: HomeScreen,
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
AsyncStorage,
|
|
||||||
StatusBar,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
} from 'react-native';
|
|
||||||
import { createStackNavigator, createSwitchNavigator } from 'react-navigation';
|
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
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('Home');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
|
|
||||||
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
|
|
||||||
|
|
||||||
export default createSwitchNavigator({
|
|
||||||
Loading: LoadingScreen,
|
|
||||||
App: AppStack,
|
|
||||||
Auth: AuthStack,
|
|
||||||
});
|
|
||||||
127
examples/NavigationPlayground/js/TabAnimations.js
Normal file
127
examples/NavigationPlayground/js/TabAnimations.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Animated, Button, ScrollView, StatusBar } from 'react-native';
|
||||||
|
import { StackNavigator, TabNavigator } from 'react-navigation';
|
||||||
|
|
||||||
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||||
|
import SampleText from './SampleText';
|
||||||
|
|
||||||
|
const MyNavScreen = ({ navigation, banner }) => (
|
||||||
|
<ScrollView>
|
||||||
|
<SampleText>{banner}</SampleText>
|
||||||
|
<Button
|
||||||
|
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||||
|
title="Open profile screen"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onPress={() => navigation.navigate('NotifSettings')}
|
||||||
|
title="Open notifications screen"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onPress={() => navigation.navigate('SettingsTab')}
|
||||||
|
title="Go to settings tab"
|
||||||
|
/>
|
||||||
|
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MyHomeScreen = ({ navigation }) => (
|
||||||
|
<MyNavScreen banner="Home Screen" navigation={navigation} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const MyProfileScreen = ({ navigation }) => (
|
||||||
|
<MyNavScreen
|
||||||
|
banner={`${navigation.state.params.name}s Profile`}
|
||||||
|
navigation={navigation}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MyNotificationsSettingsScreen = ({ navigation }) => (
|
||||||
|
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const MySettingsScreen = ({ navigation }) => (
|
||||||
|
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const MainTab = StackNavigator({
|
||||||
|
Home: {
|
||||||
|
screen: MyHomeScreen,
|
||||||
|
path: '/',
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Welcome',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Profile: {
|
||||||
|
screen: MyProfileScreen,
|
||||||
|
path: '/people/:name',
|
||||||
|
navigationOptions: ({ navigation }) => ({
|
||||||
|
title: `${navigation.state.params.name}'s Profile!`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const SettingsTab = StackNavigator({
|
||||||
|
Settings: {
|
||||||
|
screen: MySettingsScreen,
|
||||||
|
path: '/',
|
||||||
|
navigationOptions: () => ({
|
||||||
|
title: 'Settings',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
NotifSettings: {
|
||||||
|
screen: MyNotificationsSettingsScreen,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Notifications',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const TabAnimations = TabNavigator(
|
||||||
|
{
|
||||||
|
MainTab: {
|
||||||
|
screen: MainTab,
|
||||||
|
path: '/',
|
||||||
|
navigationOptions: {
|
||||||
|
tabBarLabel: 'Home',
|
||||||
|
tabBarIcon: ({ tintColor, focused }) => (
|
||||||
|
<Ionicons
|
||||||
|
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||||
|
size={26}
|
||||||
|
style={{ color: tintColor }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SettingsTab: {
|
||||||
|
screen: SettingsTab,
|
||||||
|
path: '/settings',
|
||||||
|
navigationOptions: {
|
||||||
|
tabBarLabel: 'Settings',
|
||||||
|
tabBarIcon: ({ tintColor, focused }) => (
|
||||||
|
<Ionicons
|
||||||
|
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||||
|
size={26}
|
||||||
|
style={{ color: tintColor }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabBarPosition: 'bottom',
|
||||||
|
animationEnabled: true,
|
||||||
|
configureTransition: (currentTransitionProps,nextTransitionProps) => ({
|
||||||
|
timing: Animated.spring,
|
||||||
|
tension: 1,
|
||||||
|
friction: 35,
|
||||||
|
}),
|
||||||
|
swipeEnabled: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TabAnimations;
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, ScrollView } from 'react-native';
|
import { Button, Platform, ScrollView } from 'react-native';
|
||||||
import { createDrawerNavigator } from 'react-navigation';
|
import { TabNavigator, DrawerNavigator } from 'react-navigation';
|
||||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||||
import SimpleTabs from './SimpleTabs';
|
import SimpleTabs from './SimpleTabs';
|
||||||
import StacksOverTabs from './StacksOverTabs';
|
import StacksOverTabs from './StacksOverTabs';
|
||||||
|
|
||||||
const TabsInDrawer = createDrawerNavigator({
|
const TabsInDrawer = DrawerNavigator({
|
||||||
SimpleTabs: {
|
SimpleTabs: {
|
||||||
screen: SimpleTabs,
|
screen: SimpleTabs,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
|||||||
@@ -3,11 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SafeAreaView, StatusBar, Text, View } from 'react-native';
|
import { Button, SafeAreaView, Text } from 'react-native';
|
||||||
import { withNavigationFocus } from 'react-navigation';
|
import { TabNavigator, withNavigationFocus } from 'react-navigation';
|
||||||
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
|
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { Button } from './commonComponents/ButtonWithMargin';
|
|
||||||
|
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
|
|
||||||
@@ -67,10 +65,9 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onPress={() => this.props.navigation.pop()}
|
onPress={() => this.props.navigation.goBack(null)}
|
||||||
title="Back to other examples"
|
title="Back to other examples"
|
||||||
/>
|
/>
|
||||||
<StatusBar barStyle="default" />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -78,7 +75,7 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
|||||||
return withNavigationFocus(TabScreen);
|
return withNavigationFocus(TabScreen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
|
const TabsWithNavigationFocus = TabNavigator(
|
||||||
{
|
{
|
||||||
One: {
|
One: {
|
||||||
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
|
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
|
||||||
@@ -95,8 +92,9 @@ const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shifting: false,
|
tabBarPosition: 'bottom',
|
||||||
activeTintColor: '#F44336',
|
animationEnabled: true,
|
||||||
|
swipeEnabled: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,18 +0,0 @@
|
|||||||
import { Button as RNButton, StyleSheet, View, Platform } from 'react-native';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export const Button = props => (
|
|
||||||
<View style={styles.margin}>
|
|
||||||
<RNButton {...props} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
margin: {
|
|
||||||
...Platform.select({
|
|
||||||
android: {
|
|
||||||
margin: 10,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import DefaultHeaderButtons from 'react-navigation-header-buttons';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
export class HeaderButtons extends React.PureComponent {
|
|
||||||
static Item = DefaultHeaderButtons.Item;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<DefaultHeaderButtons
|
|
||||||
color={Platform.OS === 'ios' ? '#037aff' : 'black'}
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,26 +8,23 @@
|
|||||||
"eject": "react-native-scripts eject",
|
"eject": "react-native-scripts eject",
|
||||||
"android": "react-native-scripts android",
|
"android": "react-native-scripts android",
|
||||||
"ios": "react-native-scripts ios",
|
"ios": "react-native-scripts ios",
|
||||||
"test": "flow"
|
"test": "node node_modules/jest/bin/jest.js && flow"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"expo": "^27.0.0",
|
"expo": "^25.0.0",
|
||||||
"invariant": "^2.2.4",
|
"react": "16.2.0",
|
||||||
"react": "16.3.1",
|
"react-native": "^0.52.0",
|
||||||
"react-native": "^0.55.0",
|
|
||||||
"react-native-iphone-x-helper": "^1.0.2",
|
"react-native-iphone-x-helper": "^1.0.2",
|
||||||
"react-navigation": "link:../..",
|
"react-navigation": "link:../.."
|
||||||
"react-navigation-header-buttons": "^0.0.4",
|
|
||||||
"react-navigation-material-bottom-tabs": "0.1.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-jest": "^22.4.1",
|
"babel-jest": "^21.0.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.0",
|
"babel-plugin-transform-remove-console": "^6.9.0",
|
||||||
"flow-bin": "^0.67.0",
|
"flow-bin": "^0.61.0",
|
||||||
"jest": "^22.1.3",
|
"jest": "^21.0.1",
|
||||||
"jest-expo": "^26.0.0",
|
"jest-expo": "^25.1.0",
|
||||||
"react-native-scripts": "^1.5.0",
|
"react-native-scripts": "^1.5.0",
|
||||||
"react-test-renderer": "16.3.0-alpha.1"
|
"react-test-renderer": "16.0.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "jest-expo",
|
"preset": "jest-expo",
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ module.exports = {
|
|||||||
return blacklist([
|
return blacklist([
|
||||||
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
|
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
|
||||||
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
||||||
/react\-navigation\/node_modules\/react\/(.*)/,
|
/react\-navigation\/node_modules\/react\/(.*)/
|
||||||
/react\-navigation\/node_modules\/react-native-paper\/(.*)/,
|
|
||||||
/react\-navigation\/node_modules\/@expo\/vector-icons\/(.*)/,
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
"redux": "^3.7.2"
|
"redux": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-jest": "^22.4.1",
|
"babel-jest": "^21.0.0",
|
||||||
"jest": "^22.1.3",
|
"jest": "^21.0.1",
|
||||||
"jest-expo": "^25.1.0",
|
"jest-expo": "^25.1.0",
|
||||||
"react-native-scripts": "^1.3.1",
|
"react-native-scripts": "^1.3.1",
|
||||||
"react-test-renderer": "16.0.0"
|
"react-test-renderer": "16.0.0"
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createStackNavigator } from 'react-navigation';
|
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
|
||||||
import { initializeListeners } from 'react-navigation-redux-helpers';
|
|
||||||
|
|
||||||
import LoginScreen from '../components/LoginScreen';
|
import LoginScreen from '../components/LoginScreen';
|
||||||
import MainScreen from '../components/MainScreen';
|
import MainScreen from '../components/MainScreen';
|
||||||
import ProfileScreen from '../components/ProfileScreen';
|
import ProfileScreen from '../components/ProfileScreen';
|
||||||
import { navigationPropConstructor } from '../utils/redux';
|
import { addListener } from '../utils/redux';
|
||||||
|
|
||||||
export const AppNavigator = createStackNavigator({
|
export const AppNavigator = StackNavigator({
|
||||||
Login: { screen: LoginScreen },
|
Login: { screen: LoginScreen },
|
||||||
Main: { screen: MainScreen },
|
Main: { screen: MainScreen },
|
||||||
Profile: { screen: ProfileScreen },
|
Profile: { screen: ProfileScreen },
|
||||||
@@ -21,14 +20,17 @@ class AppWithNavigationState extends React.Component {
|
|||||||
nav: PropTypes.object.isRequired,
|
nav: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
initializeListeners('root', this.props.nav);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dispatch, nav } = this.props;
|
const { dispatch, nav } = this.props;
|
||||||
const navigation = navigationPropConstructor(dispatch, nav);
|
return (
|
||||||
return <AppNavigator navigation={navigation} />;
|
<AppNavigator
|
||||||
|
navigation={addNavigationHelpers({
|
||||||
|
dispatch,
|
||||||
|
state: nav,
|
||||||
|
addListener,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
createReactNavigationReduxMiddleware,
|
createReactNavigationReduxMiddleware,
|
||||||
createNavigationPropConstructor,
|
createReduxBoundAddListener,
|
||||||
} from 'react-navigation-redux-helpers';
|
} from 'react-navigation-redux-helpers';
|
||||||
|
|
||||||
const middleware = createReactNavigationReduxMiddleware(
|
const middleware = createReactNavigationReduxMiddleware(
|
||||||
'root',
|
"root",
|
||||||
state => state.nav
|
state => state.nav,
|
||||||
);
|
);
|
||||||
const navigationPropConstructor = createNavigationPropConstructor('root');
|
const addListener = createReduxBoundAddListener("root");
|
||||||
|
|
||||||
export { middleware, navigationPropConstructor };
|
export {
|
||||||
|
middleware,
|
||||||
|
addListener,
|
||||||
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
355
flow/react-navigation.js
vendored
355
flow/react-navigation.js
vendored
@@ -69,14 +69,6 @@ declare module 'react-navigation' {
|
|||||||
[key: string]: mixed,
|
[key: string]: mixed,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export type NavigationBackAction = {|
|
|
||||||
type: 'Navigation/BACK',
|
|
||||||
key?: ?string,
|
|
||||||
|};
|
|
||||||
declare export type NavigationInitAction = {|
|
|
||||||
type: 'Navigation/INIT',
|
|
||||||
params?: NavigationParams,
|
|
||||||
|};
|
|
||||||
declare export type NavigationNavigateAction = {|
|
declare export type NavigationNavigateAction = {|
|
||||||
type: 'Navigation/NAVIGATE',
|
type: 'Navigation/NAVIGATE',
|
||||||
routeName: string,
|
routeName: string,
|
||||||
@@ -87,6 +79,12 @@ declare module 'react-navigation' {
|
|||||||
|
|
||||||
key?: string,
|
key?: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
declare export type NavigationBackAction = {|
|
||||||
|
type: 'Navigation/BACK',
|
||||||
|
key?: ?string,
|
||||||
|
|};
|
||||||
|
|
||||||
declare export type NavigationSetParamsAction = {|
|
declare export type NavigationSetParamsAction = {|
|
||||||
type: 'Navigation/SET_PARAMS',
|
type: 'Navigation/SET_PARAMS',
|
||||||
|
|
||||||
@@ -97,6 +95,30 @@ declare module 'react-navigation' {
|
|||||||
params: NavigationParams,
|
params: NavigationParams,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
declare export type NavigationInitAction = {|
|
||||||
|
type: 'Navigation/INIT',
|
||||||
|
params?: NavigationParams,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare export type NavigationResetAction = {|
|
||||||
|
type: 'Navigation/RESET',
|
||||||
|
index: number,
|
||||||
|
key?: ?string,
|
||||||
|
actions: Array<NavigationNavigateAction>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare export type NavigationUriAction = {|
|
||||||
|
type: 'Navigation/URI',
|
||||||
|
uri: string,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare export type NavigationReplaceAction = {|
|
||||||
|
+type: 'Navigation/REPLACE',
|
||||||
|
+key: string,
|
||||||
|
+routeName: string,
|
||||||
|
+params?: NavigationParams,
|
||||||
|
+action?: NavigationNavigateAction,
|
||||||
|
|};
|
||||||
declare export type NavigationPopAction = {|
|
declare export type NavigationPopAction = {|
|
||||||
+type: 'Navigation/POP',
|
+type: 'Navigation/POP',
|
||||||
+n?: number,
|
+n?: number,
|
||||||
@@ -113,51 +135,17 @@ declare module 'react-navigation' {
|
|||||||
+action?: NavigationNavigateAction,
|
+action?: NavigationNavigateAction,
|
||||||
+key?: string,
|
+key?: string,
|
||||||
|};
|
|};
|
||||||
declare export type NavigationResetAction = {|
|
|
||||||
type: 'Navigation/RESET',
|
|
||||||
index: number,
|
|
||||||
key?: ?string,
|
|
||||||
actions: Array<NavigationNavigateAction>,
|
|
||||||
|};
|
|
||||||
declare export type NavigationReplaceAction = {|
|
|
||||||
+type: 'Navigation/REPLACE',
|
|
||||||
+key: string,
|
|
||||||
+routeName: string,
|
|
||||||
+params?: NavigationParams,
|
|
||||||
+action?: NavigationNavigateAction,
|
|
||||||
|};
|
|
||||||
declare export type NavigationCompleteTransitionAction = {|
|
|
||||||
+type: 'Navigation/COMPLETE_TRANSITION',
|
|
||||||
+key?: string,
|
|
||||||
|};
|
|
||||||
|
|
||||||
declare export type NavigationOpenDrawerAction = {|
|
|
||||||
+type: 'Navigation/OPEN_DRAWER',
|
|
||||||
+key?: string,
|
|
||||||
|};
|
|
||||||
declare export type NavigationCloseDrawerAction = {|
|
|
||||||
+type: 'Navigation/CLOSE_DRAWER',
|
|
||||||
+key?: string,
|
|
||||||
|};
|
|
||||||
declare export type NavigationToggleDrawerAction = {|
|
|
||||||
+type: 'Navigation/TOGGLE_DRAWER',
|
|
||||||
+key?: string,
|
|
||||||
|};
|
|
||||||
|
|
||||||
declare export type NavigationAction =
|
declare export type NavigationAction =
|
||||||
| NavigationBackAction
|
|
||||||
| NavigationInitAction
|
| NavigationInitAction
|
||||||
| NavigationNavigateAction
|
| NavigationNavigateAction
|
||||||
| NavigationSetParamsAction
|
| NavigationReplaceAction
|
||||||
| NavigationPopAction
|
| NavigationPopAction
|
||||||
| NavigationPopToTopAction
|
| NavigationPopToTopAction
|
||||||
| NavigationPushAction
|
| NavigationPushAction
|
||||||
| NavigationResetAction
|
| NavigationBackAction
|
||||||
| NavigationReplaceAction
|
| NavigationSetParamsAction
|
||||||
| NavigationCompleteTransitionAction
|
| NavigationResetAction;
|
||||||
| NavigationOpenDrawerAction
|
|
||||||
| NavigationCloseDrawerAction
|
|
||||||
| NavigationToggleDrawerAction;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NavigationState is a tree of routes for a single navigator, where each
|
* NavigationState is a tree of routes for a single navigator, where each
|
||||||
@@ -281,33 +269,24 @@ declare module 'react-navigation' {
|
|||||||
|
|
||||||
declare export type NavigationComponent =
|
declare export type NavigationComponent =
|
||||||
| NavigationScreenComponent<NavigationRoute, *, *>
|
| NavigationScreenComponent<NavigationRoute, *, *>
|
||||||
| NavigationContainer<*, *, *>;
|
| NavigationContainer<*, *, *>
|
||||||
|
| any;
|
||||||
declare interface withOptionalNavigationOptions<Options> {
|
|
||||||
navigationOptions?: NavigationScreenConfig<Options>,
|
|
||||||
}
|
|
||||||
|
|
||||||
declare export type NavigationScreenComponent<
|
declare export type NavigationScreenComponent<
|
||||||
Route: NavigationRoute,
|
Route: NavigationRoute,
|
||||||
Options: {},
|
Options: {},
|
||||||
Props: {}
|
Props: {}
|
||||||
> = React$ComponentType<{
|
> = React$ComponentType<NavigationNavigatorProps<Options, Route> & Props> &
|
||||||
...Props,
|
({} | { navigationOptions: NavigationScreenConfig<Options> });
|
||||||
...NavigationNavigatorProps<Options, Route>,
|
|
||||||
}> & withOptionalNavigationOptions<Options>;
|
|
||||||
|
|
||||||
declare interface withRouter<State, Options> {
|
|
||||||
router: NavigationRouter<State, Options>,
|
|
||||||
}
|
|
||||||
|
|
||||||
declare export type NavigationNavigator<
|
declare export type NavigationNavigator<
|
||||||
State: NavigationState,
|
State: NavigationState,
|
||||||
Options: {},
|
Options: {},
|
||||||
Props: {}
|
Props: {}
|
||||||
> = React$ComponentType<{
|
> = React$ComponentType<NavigationNavigatorProps<Options, State> & Props> & {
|
||||||
...Props,
|
router: NavigationRouter<State, Options>,
|
||||||
...NavigationNavigatorProps<Options, State>,
|
navigationOptions?: ?NavigationScreenConfig<Options>,
|
||||||
}> & withRouter<State, Options> & withOptionalNavigationOptions<Options>;
|
};
|
||||||
|
|
||||||
declare export type NavigationRouteConfig =
|
declare export type NavigationRouteConfig =
|
||||||
| NavigationComponent
|
| NavigationComponent
|
||||||
@@ -317,6 +296,7 @@ declare module 'react-navigation' {
|
|||||||
} & NavigationScreenRouteConfig);
|
} & NavigationScreenRouteConfig);
|
||||||
|
|
||||||
declare export type NavigationScreenRouteConfig =
|
declare export type NavigationScreenRouteConfig =
|
||||||
|
| NavigationComponent
|
||||||
| {
|
| {
|
||||||
screen: NavigationComponent,
|
screen: NavigationComponent,
|
||||||
}
|
}
|
||||||
@@ -364,7 +344,7 @@ declare module 'react-navigation' {
|
|||||||
headerTintColor?: string,
|
headerTintColor?: string,
|
||||||
headerLeft?: React$Node | React$ElementType,
|
headerLeft?: React$Node | React$ElementType,
|
||||||
headerBackTitle?: string,
|
headerBackTitle?: string,
|
||||||
headerBackImage?: React$Node | React$ElementType,
|
headerBackImage?: ImageSource,
|
||||||
headerTruncatedBackTitle?: string,
|
headerTruncatedBackTitle?: string,
|
||||||
headerBackTitleStyle?: TextStyleProp,
|
headerBackTitleStyle?: TextStyleProp,
|
||||||
headerPressColorAndroid?: string,
|
headerPressColorAndroid?: string,
|
||||||
@@ -400,20 +380,6 @@ declare module 'react-navigation' {
|
|||||||
...NavigationStackRouterConfig,
|
...NavigationStackRouterConfig,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch Navigator
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare export type NavigationSwitchRouterConfig = {|
|
|
||||||
initialRouteName?: string,
|
|
||||||
initialRouteParams?: NavigationParams,
|
|
||||||
paths?: NavigationPathsConfig,
|
|
||||||
navigationOptions?: NavigationScreenConfig<*>,
|
|
||||||
order?: Array<string>,
|
|
||||||
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
|
|
||||||
resetOnBlur?: boolean, // defaults to `true`
|
|
||||||
|};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab Navigator
|
* Tab Navigator
|
||||||
*/
|
*/
|
||||||
@@ -425,6 +391,7 @@ declare module 'react-navigation' {
|
|||||||
navigationOptions?: NavigationScreenConfig<*>,
|
navigationOptions?: NavigationScreenConfig<*>,
|
||||||
// todo: type these as the real route names rather than 'string'
|
// todo: type these as the real route names rather than 'string'
|
||||||
order?: Array<string>,
|
order?: Array<string>,
|
||||||
|
|
||||||
// Does the back button cause the router to switch to the initial tab
|
// Does the back button cause the router to switch to the initial tab
|
||||||
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
||||||
|};
|
|};
|
||||||
@@ -447,9 +414,10 @@ declare module 'react-navigation' {
|
|||||||
| ((options: { tintColor: ?string, focused: boolean }) => ?React$Node),
|
| ((options: { tintColor: ?string, focused: boolean }) => ?React$Node),
|
||||||
tabBarVisible?: boolean,
|
tabBarVisible?: boolean,
|
||||||
tabBarTestIDProps?: { testID?: string, accessibilityLabel?: string },
|
tabBarTestIDProps?: { testID?: string, accessibilityLabel?: string },
|
||||||
tabBarOnPress?: ({
|
tabBarOnPress?: (
|
||||||
navigation: NavigationScreenProp<NavigationRoute>,
|
scene: TabScene,
|
||||||
}) => void,
|
jumpToIndex: (index: number) => void
|
||||||
|
) => void,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -503,37 +471,31 @@ declare module 'react-navigation' {
|
|||||||
};
|
};
|
||||||
|
|
||||||
declare export type NavigationScreenProp<+S> = {
|
declare export type NavigationScreenProp<+S> = {
|
||||||
...$ObjMap<
|
|
||||||
_DefaultActionCreators,
|
|
||||||
<Args>((...args: Args) => *) => (...args: Args) => boolean
|
|
||||||
>,
|
|
||||||
+state: S,
|
+state: S,
|
||||||
dispatch: NavigationDispatch,
|
dispatch: NavigationDispatch,
|
||||||
|
goBack: (routeKey?: ?string) => boolean,
|
||||||
|
navigate: (
|
||||||
|
routeName: string,
|
||||||
|
params?: NavigationParams,
|
||||||
|
action?: NavigationNavigateAction
|
||||||
|
) => boolean,
|
||||||
|
setParams: (newParams: NavigationParams) => boolean,
|
||||||
addListener: (
|
addListener: (
|
||||||
eventName: string,
|
eventName: string,
|
||||||
callback: NavigationEventCallback
|
callback: NavigationEventCallback
|
||||||
) => NavigationEventSubscription,
|
) => NavigationEventSubscription,
|
||||||
getParam: (paramName: string, fallback?: any) => any,
|
push: (
|
||||||
isFocused: () => boolean,
|
|
||||||
// StackRouter action creators
|
|
||||||
pop?: (n?: number, params?: { immediate?: boolean }) => boolean,
|
|
||||||
popToTop?: (params?: { immediate?: boolean }) => boolean,
|
|
||||||
push?: (
|
|
||||||
routeName: string,
|
routeName: string,
|
||||||
params?: NavigationParams,
|
params?: NavigationParams,
|
||||||
action?: NavigationNavigateAction
|
action?: NavigationNavigateAction
|
||||||
) => boolean,
|
) => boolean,
|
||||||
replace?: (
|
replace: (
|
||||||
routeName: string,
|
routeName: string,
|
||||||
params?: NavigationParams,
|
params?: NavigationParams,
|
||||||
action?: NavigationNavigateAction
|
action?: NavigationNavigateAction
|
||||||
) => boolean,
|
) => boolean,
|
||||||
reset?: (actions: NavigationAction[], index: number) => boolean,
|
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
|
||||||
dismiss?: () => boolean,
|
popToTop: (params?: { immediate?: boolean }) => boolean,
|
||||||
// DrawerRouter action creators
|
|
||||||
openDrawer?: () => boolean,
|
|
||||||
closeDrawer?: () => boolean,
|
|
||||||
toggleDrawer?: () => boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
|
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
|
||||||
@@ -550,21 +512,19 @@ declare module 'react-navigation' {
|
|||||||
State: NavigationState,
|
State: NavigationState,
|
||||||
Options: {},
|
Options: {},
|
||||||
Props: {}
|
Props: {}
|
||||||
> = React$ComponentType<{
|
> = React$ComponentType<NavigationContainerProps<State, Options> & Props> & {
|
||||||
...Props,
|
router: NavigationRouter<State, Options>,
|
||||||
...NavigationContainerProps<State, Options>,
|
navigationOptions?: ?NavigationScreenConfig<Options>,
|
||||||
}> & withRouter<State, Options> & withOptionalNavigationOptions<Options>;
|
};
|
||||||
|
|
||||||
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
|
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
|
||||||
uriPrefix?: string | RegExp,
|
uriPrefix?: string | RegExp,
|
||||||
onNavigationStateChange?: ?(
|
onNavigationStateChange?: (
|
||||||
NavigationState,
|
NavigationState,
|
||||||
NavigationState,
|
NavigationState,
|
||||||
NavigationAction
|
NavigationAction
|
||||||
) => void,
|
) => void,
|
||||||
navigation?: NavigationScreenProp<S>,
|
navigation?: NavigationScreenProp<S>,
|
||||||
persistenceKey?: ?string,
|
|
||||||
renderLoadingExperimental?: React$ComponentType<{}>,
|
|
||||||
screenProps?: *,
|
screenProps?: *,
|
||||||
navigationOptions?: O,
|
navigationOptions?: O,
|
||||||
}>;
|
}>;
|
||||||
@@ -722,95 +682,46 @@ declare module 'react-navigation' {
|
|||||||
BACK: 'Navigation/BACK',
|
BACK: 'Navigation/BACK',
|
||||||
INIT: 'Navigation/INIT',
|
INIT: 'Navigation/INIT',
|
||||||
NAVIGATE: 'Navigation/NAVIGATE',
|
NAVIGATE: 'Navigation/NAVIGATE',
|
||||||
SET_PARAMS: 'Navigation/SET_PARAMS',
|
|
||||||
|
|
||||||
back: (payload?: { key?: ?string }) => NavigationBackAction,
|
|
||||||
init: (payload?: { params?: NavigationParams }) => NavigationInitAction,
|
|
||||||
navigate: (payload: {
|
|
||||||
routeName: string,
|
|
||||||
params?: ?NavigationParams,
|
|
||||||
action?: ?NavigationNavigateAction,
|
|
||||||
key?: string,
|
|
||||||
}) => NavigationNavigateAction,
|
|
||||||
setParams: (payload: {
|
|
||||||
key: string,
|
|
||||||
params: NavigationParams,
|
|
||||||
}) => NavigationSetParamsAction,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare export var StackActions: {
|
|
||||||
POP: 'Navigation/POP',
|
|
||||||
POP_TO_TOP: 'Navigation/POP_TO_TOP',
|
|
||||||
PUSH: 'Navigation/PUSH',
|
|
||||||
RESET: 'Navigation/RESET',
|
RESET: 'Navigation/RESET',
|
||||||
REPLACE: 'Navigation/REPLACE',
|
SET_PARAMS: 'Navigation/SET_PARAMS',
|
||||||
COMPLETE_TRANSITION: 'Navigation/COMPLETE_TRANSITION',
|
URI: 'Navigation/URI',
|
||||||
|
back: {
|
||||||
pop: (payload: {
|
(payload?: { key?: ?string }): NavigationBackAction,
|
||||||
n?: number,
|
toString: () => string,
|
||||||
immediate?: boolean,
|
},
|
||||||
}) => NavigationPopAction,
|
init: {
|
||||||
popToTop: (payload: {
|
(payload?: { params?: NavigationParams }): NavigationInitAction,
|
||||||
immediate?: boolean,
|
toString: () => string,
|
||||||
}) => NavigationPopToTopAction,
|
},
|
||||||
push: (payload: {
|
navigate: {
|
||||||
routeName: string,
|
(payload: {
|
||||||
params?: NavigationParams,
|
routeName: string,
|
||||||
action?: NavigationNavigateAction,
|
params?: ?NavigationParams,
|
||||||
key?: string,
|
action?: ?NavigationNavigateAction,
|
||||||
}) => NavigationPushAction,
|
}): NavigationNavigateAction,
|
||||||
reset: (payload: {
|
toString: () => string,
|
||||||
index: number,
|
},
|
||||||
key?: ?string,
|
reset: {
|
||||||
actions: Array<NavigationNavigateAction>,
|
(payload: {
|
||||||
}) => NavigationResetAction,
|
index: number,
|
||||||
replace: (payload: {
|
key?: ?string,
|
||||||
key: string,
|
actions: Array<NavigationNavigateAction>,
|
||||||
routeName: string,
|
}): NavigationResetAction,
|
||||||
params?: NavigationParams,
|
toString: () => string,
|
||||||
action?: NavigationNavigateAction,
|
},
|
||||||
}) => NavigationReplaceAction,
|
setParams: {
|
||||||
completeTransition: (payload: {
|
(payload: {
|
||||||
key?: string,
|
key: string,
|
||||||
}) => NavigationCompleteTransitionAction,
|
params: NavigationParams,
|
||||||
|
}): NavigationSetParamsAction,
|
||||||
|
toString: () => string,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
(payload: { uri: string }): NavigationUriAction,
|
||||||
|
toString: () => string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export var DrawerActions: {
|
|
||||||
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
|
|
||||||
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
|
|
||||||
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
|
|
||||||
|
|
||||||
openDrawer: (payload: {
|
|
||||||
key?: string,
|
|
||||||
}) => NavigationOpenDrawerAction,
|
|
||||||
closeDrawer: (payload: {
|
|
||||||
key?: string,
|
|
||||||
}) => NavigationCloseDrawerAction,
|
|
||||||
toggleDrawer: (payload: {
|
|
||||||
key?: string,
|
|
||||||
}) => NavigationToggleDrawerAction,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare type _DefaultActionCreators = {|
|
|
||||||
goBack: (routeKey?: ?string) => NavigationBackAction,
|
|
||||||
navigate: (
|
|
||||||
routeName:
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
routeName: string,
|
|
||||||
params?: NavigationParams,
|
|
||||||
action?: NavigationNavigateAction,
|
|
||||||
key?: string,
|
|
||||||
},
|
|
||||||
params?: NavigationParams,
|
|
||||||
action?: NavigationNavigateAction
|
|
||||||
) => NavigationNavigateAction,
|
|
||||||
setParams: (newParams: NavigationParams) => NavigationSetParamsAction,
|
|
||||||
|};
|
|
||||||
declare export function getNavigationActionCreators(
|
|
||||||
route: NavigationRoute | NavigationState
|
|
||||||
): _DefaultActionCreators;
|
|
||||||
|
|
||||||
declare type _RouterProp<S: NavigationState, O: {}> = {
|
declare type _RouterProp<S: NavigationState, O: {}> = {
|
||||||
router: NavigationRouter<S, O>,
|
router: NavigationRouter<S, O>,
|
||||||
};
|
};
|
||||||
@@ -831,16 +742,12 @@ declare module 'react-navigation' {
|
|||||||
view: NavigationView<O, S>,
|
view: NavigationView<O, S>,
|
||||||
router: NavigationRouter<S, O>,
|
router: NavigationRouter<S, O>,
|
||||||
navigatorConfig?: NavigatorConfig
|
navigatorConfig?: NavigatorConfig
|
||||||
): NavigationNavigator<S, O, *>;
|
): any;
|
||||||
|
|
||||||
declare export function StackNavigator(
|
declare export function StackNavigator(
|
||||||
routeConfigMap: NavigationRouteConfigMap,
|
routeConfigMap: NavigationRouteConfigMap,
|
||||||
stackConfig?: StackNavigatorConfig
|
stackConfig?: StackNavigatorConfig
|
||||||
): NavigationContainer<*, *, *>;
|
): NavigationContainer<*, *, *>;
|
||||||
declare export function createStackNavigator(
|
|
||||||
routeConfigMap: NavigationRouteConfigMap,
|
|
||||||
stackConfig?: StackNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
|
|
||||||
declare type _TabViewConfig = {|
|
declare type _TabViewConfig = {|
|
||||||
tabBarComponent?: React$ElementType,
|
tabBarComponent?: React$ElementType,
|
||||||
@@ -865,30 +772,6 @@ declare module 'react-navigation' {
|
|||||||
routeConfigs: NavigationRouteConfigMap,
|
routeConfigs: NavigationRouteConfigMap,
|
||||||
config?: _TabNavigatorConfig
|
config?: _TabNavigatorConfig
|
||||||
): NavigationContainer<*, *, *>;
|
): NavigationContainer<*, *, *>;
|
||||||
declare export function createTabNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _TabNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
/* TODO: fix the config for each of these tab navigator types */
|
|
||||||
declare export function createBottomTabNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _TabNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
declare export function createMaterialTopTabNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _TabNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
declare type _SwitchNavigatorConfig = {|
|
|
||||||
...NavigationSwitchRouterConfig,
|
|
||||||
|};
|
|
||||||
declare export function SwitchNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _SwitchNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
declare export function createSwitchNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _SwitchNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
|
|
||||||
declare type _DrawerViewConfig = {|
|
declare type _DrawerViewConfig = {|
|
||||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||||
@@ -910,10 +793,6 @@ declare module 'react-navigation' {
|
|||||||
routeConfigs: NavigationRouteConfigMap,
|
routeConfigs: NavigationRouteConfigMap,
|
||||||
config?: _DrawerNavigatorConfig
|
config?: _DrawerNavigatorConfig
|
||||||
): NavigationContainer<*, *, *>;
|
): NavigationContainer<*, *, *>;
|
||||||
declare export function createDrawerNavigator(
|
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
|
||||||
config?: _DrawerNavigatorConfig
|
|
||||||
): NavigationContainer<*, *, *>;
|
|
||||||
|
|
||||||
declare export function StackRouter(
|
declare export function StackRouter(
|
||||||
routeConfigs: NavigationRouteConfigMap,
|
routeConfigs: NavigationRouteConfigMap,
|
||||||
@@ -999,14 +878,12 @@ declare module 'react-navigation' {
|
|||||||
vertical?: _SafeAreaViewForceInsetValue,
|
vertical?: _SafeAreaViewForceInsetValue,
|
||||||
horizontal?: _SafeAreaViewForceInsetValue,
|
horizontal?: _SafeAreaViewForceInsetValue,
|
||||||
},
|
},
|
||||||
children?: React$Node,
|
children: React$Node,
|
||||||
style?: AnimatedViewStyleProp,
|
style?: AnimatedViewStyleProp,
|
||||||
};
|
};
|
||||||
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
|
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
|
||||||
|
|
||||||
declare export var Header: React$ComponentType<HeaderProps> & {
|
declare export var Header: React$ComponentType<HeaderProps>;
|
||||||
HEIGHT: number,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare type _HeaderTitleProps = {
|
declare type _HeaderTitleProps = {
|
||||||
children: React$Node,
|
children: React$Node,
|
||||||
@@ -1149,17 +1026,13 @@ declare module 'react-navigation' {
|
|||||||
};
|
};
|
||||||
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
||||||
|
|
||||||
declare export function withNavigation<Props: {}>(
|
declare type _NavigationInjectedProps = {
|
||||||
Component: React$ComponentType<Props>
|
navigation: NavigationScreenProp<NavigationStateRoute>,
|
||||||
): React$ComponentType<
|
};
|
||||||
$Diff<
|
declare export function withNavigation<T: {}>(
|
||||||
Props,
|
Component: React$ComponentType<T & _NavigationInjectedProps>
|
||||||
{
|
): React$ComponentType<T>;
|
||||||
navigation: NavigationScreenProp<NavigationStateRoute> | void,
|
declare export function withNavigationFocus<T: {}>(
|
||||||
}
|
Component: React$ComponentType<T & _NavigationInjectedProps>
|
||||||
>
|
): React$ComponentType<T>;
|
||||||
>;
|
|
||||||
declare export function withNavigationFocus<Props: {}>(
|
|
||||||
Component: React$ComponentType<Props>
|
|
||||||
): React$ComponentType<$Diff<Props, { isFocused: boolean | void }>>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
55
package.json
55
package.json
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "react-navigation",
|
"name": "react-navigation",
|
||||||
"version": "2.2.0",
|
"version": "1.2.1",
|
||||||
"description": "Routing and navigation for your React Native apps",
|
"description": "Routing and navigation for your React Native apps",
|
||||||
"main": "src/react-navigation.js",
|
"main": "src/react-navigation.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "git@github.com:react-navigation/react-navigation.git",
|
"url": "git@github.com:react-navigation/react-navigation.git",
|
||||||
"type": "git"
|
"type": "git"
|
||||||
},
|
},
|
||||||
"author": "Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>, Brent Vatne <brent@expo.io>",
|
"author":
|
||||||
|
"Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run ios",
|
"start": "npm run ios",
|
||||||
@@ -21,44 +22,38 @@
|
|||||||
"format": "eslint --fix .",
|
"format": "eslint --fix .",
|
||||||
"precommit": "lint-staged"
|
"precommit": "lint-staged"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["src"],
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clamp": "^1.0.1",
|
"clamp": "^1.0.1",
|
||||||
"create-react-context": "^0.2.1",
|
|
||||||
"hoist-non-react-statics": "^2.2.0",
|
"hoist-non-react-statics": "^2.2.0",
|
||||||
"path-to-regexp": "^1.7.0",
|
"path-to-regexp": "^1.7.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react-lifecycles-compat": "^3",
|
|
||||||
"react-native-drawer-layout-polyfill": "^1.3.2",
|
"react-native-drawer-layout-polyfill": "^1.3.2",
|
||||||
"react-native-safe-area-view": "^0.8.0",
|
"react-native-safe-area-view": "^0.7.0",
|
||||||
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
"react-native-tab-view": "^0.0.74"
|
||||||
"react-navigation-tabs": "0.5.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.24.1",
|
"babel-cli": "^6.24.1",
|
||||||
"babel-core": "^6.25.0",
|
"babel-core": "^6.25.0",
|
||||||
"babel-eslint": "^7.2.3",
|
"babel-eslint": "^7.2.3",
|
||||||
"babel-jest": "^22.4.1",
|
"babel-jest": "^20.0.3",
|
||||||
"babel-preset-react-native": "^2.1.0",
|
"babel-preset-react-native": "^2.1.0",
|
||||||
"codecov": "^2.2.0",
|
"codecov": "^2.2.0",
|
||||||
"eslint": "^4.2.0",
|
"eslint": "^4.2.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.3.0",
|
||||||
"eslint-plugin-import": "^2.7.0",
|
"eslint-plugin-import": "^2.7.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||||
"eslint-plugin-prettier": "^2.6.0",
|
"eslint-plugin-prettier": "^2.1.2",
|
||||||
"eslint-plugin-react": "^7.1.0",
|
"eslint-plugin-react": "^7.1.0",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"jest": "^22.1.3",
|
"jest": "^22.1.3",
|
||||||
"jest-expo": "^25.1.0",
|
|
||||||
"lint-staged": "^4.2.1",
|
"lint-staged": "^4.2.1",
|
||||||
"prettier": "^1.12.1",
|
"prettier": "^1.5.3",
|
||||||
"prettier-eslint": "^8.8.1",
|
"prettier-eslint": "^6.4.2",
|
||||||
"react": "16.2.0",
|
"react": "16.2.0",
|
||||||
"react-native": "^0.52.0",
|
"react-native": "^0.52.0",
|
||||||
"react-native-vector-icons": "^4.2.0",
|
"react-native-vector-icons": "^4.2.0",
|
||||||
@@ -67,30 +62,14 @@
|
|||||||
"jest": {
|
"jest": {
|
||||||
"notify": true,
|
"notify": true,
|
||||||
"preset": "react-native",
|
"preset": "react-native",
|
||||||
"testRegex": "/__tests__/[^/]+-test\\.js$",
|
"testRegex": "./src/.*\\-test\\.js$",
|
||||||
"setupFiles": [
|
"setupFiles": ["<rootDir>/jest-setup.js"],
|
||||||
"<rootDir>/jest-setup.js"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "./coverage/",
|
"coverageDirectory": "./coverage/",
|
||||||
"collectCoverage": true,
|
"collectCoverage": true,
|
||||||
"coverageReporters": [
|
"coverageReporters": ["lcov"],
|
||||||
"lcov"
|
"collectCoverageFrom": ["src/**/*.js"],
|
||||||
],
|
"coveragePathIgnorePatterns": ["jest-setup.js"],
|
||||||
"collectCoverageFrom": [
|
"modulePathIgnorePatterns": ["examples"]
|
||||||
"src/**/*.js"
|
|
||||||
],
|
|
||||||
"coveragePathIgnorePatterns": [
|
|
||||||
"jest-setup.js"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"\\.png$": "<rootDir>/assetsTransformer.js"
|
|
||||||
},
|
|
||||||
"modulePathIgnorePatterns": [
|
|
||||||
"<rootDir>/examples/"
|
|
||||||
],
|
|
||||||
"transformIgnorePatterns": [
|
|
||||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator)"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": [
|
"*.js": [
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ set -eo pipefail
|
|||||||
|
|
||||||
case $CIRCLE_NODE_INDEX in
|
case $CIRCLE_NODE_INDEX in
|
||||||
0) yarn test && yarn codecov ;;
|
0) yarn test && yarn codecov ;;
|
||||||
#1) cd examples/NavigationPlayground && yarn && yarn test ;;
|
1) yarn link && cd examples/NavigationPlayground && yarn && yarn link react-navigation && yarn test ;;
|
||||||
#2) cd examples/ReduxExample && yarn && yarn test ;;
|
#2) cd examples/ReduxExample && yarn && yarn test ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
const BACK = 'Navigation/BACK';
|
const BACK = 'Navigation/BACK';
|
||||||
const INIT = 'Navigation/INIT';
|
const INIT = 'Navigation/INIT';
|
||||||
const NAVIGATE = 'Navigation/NAVIGATE';
|
const NAVIGATE = 'Navigation/NAVIGATE';
|
||||||
|
const POP = 'Navigation/POP';
|
||||||
|
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
||||||
|
const PUSH = 'Navigation/PUSH';
|
||||||
|
const RESET = 'Navigation/RESET';
|
||||||
|
const REPLACE = 'Navigation/REPLACE';
|
||||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
||||||
|
const URI = 'Navigation/URI';
|
||||||
|
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||||
|
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
|
||||||
|
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
|
||||||
|
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
|
||||||
|
|
||||||
const back = (payload = {}) => ({
|
const createAction = (type, fn) => {
|
||||||
|
fn.toString = () => type;
|
||||||
|
return fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
const back = createAction(BACK, (payload = {}) => ({
|
||||||
type: BACK,
|
type: BACK,
|
||||||
key: payload.key,
|
key: payload.key,
|
||||||
immediate: payload.immediate,
|
immediate: payload.immediate,
|
||||||
});
|
}));
|
||||||
|
|
||||||
const init = (payload = {}) => {
|
const init = createAction(INIT, (payload = {}) => {
|
||||||
const action = {
|
const action = {
|
||||||
type: INIT,
|
type: INIT,
|
||||||
};
|
};
|
||||||
@@ -17,9 +32,9 @@ const init = (payload = {}) => {
|
|||||||
action.params = payload.params;
|
action.params = payload.params;
|
||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
};
|
});
|
||||||
|
|
||||||
const navigate = payload => {
|
const navigate = createAction(NAVIGATE, payload => {
|
||||||
const action = {
|
const action = {
|
||||||
type: NAVIGATE,
|
type: NAVIGATE,
|
||||||
routeName: payload.routeName,
|
routeName: payload.routeName,
|
||||||
@@ -34,24 +49,107 @@ const navigate = payload => {
|
|||||||
action.key = payload.key;
|
action.key = payload.key;
|
||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
};
|
});
|
||||||
|
|
||||||
const setParams = payload => ({
|
const pop = createAction(POP, payload => ({
|
||||||
|
type: POP,
|
||||||
|
n: payload && payload.n,
|
||||||
|
immediate: payload && payload.immediate,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const popToTop = createAction(POP_TO_TOP, payload => ({
|
||||||
|
type: POP_TO_TOP,
|
||||||
|
immediate: payload && payload.immediate,
|
||||||
|
key: payload && payload.key,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const push = createAction(PUSH, payload => {
|
||||||
|
const action = {
|
||||||
|
type: PUSH,
|
||||||
|
routeName: payload.routeName,
|
||||||
|
};
|
||||||
|
if (payload.params) {
|
||||||
|
action.params = payload.params;
|
||||||
|
}
|
||||||
|
if (payload.action) {
|
||||||
|
action.action = payload.action;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
|
const reset = createAction(RESET, payload => ({
|
||||||
|
type: RESET,
|
||||||
|
index: payload.index,
|
||||||
|
key: payload.key,
|
||||||
|
actions: payload.actions,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const replace = createAction(REPLACE, payload => ({
|
||||||
|
type: REPLACE,
|
||||||
|
key: payload.key,
|
||||||
|
newKey: payload.newKey,
|
||||||
|
params: payload.params,
|
||||||
|
action: payload.action,
|
||||||
|
routeName: payload.routeName,
|
||||||
|
immediate: payload.immediate,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setParams = createAction(SET_PARAMS, payload => ({
|
||||||
type: SET_PARAMS,
|
type: SET_PARAMS,
|
||||||
key: payload.key,
|
key: payload.key,
|
||||||
params: payload.params,
|
params: payload.params,
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
const uri = createAction(URI, payload => ({
|
||||||
|
type: URI,
|
||||||
|
uri: payload.uri,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
|
||||||
|
type: COMPLETE_TRANSITION,
|
||||||
|
key: payload && payload.key,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const openDrawer = createAction(OPEN_DRAWER, payload => ({
|
||||||
|
type: OPEN_DRAWER,
|
||||||
|
}));
|
||||||
|
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
|
||||||
|
type: CLOSE_DRAWER,
|
||||||
|
}));
|
||||||
|
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
|
||||||
|
type: TOGGLE_DRAWER,
|
||||||
|
}));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Action constants
|
// Action constants
|
||||||
BACK,
|
BACK,
|
||||||
INIT,
|
INIT,
|
||||||
NAVIGATE,
|
NAVIGATE,
|
||||||
|
POP,
|
||||||
|
POP_TO_TOP,
|
||||||
|
PUSH,
|
||||||
|
RESET,
|
||||||
|
REPLACE,
|
||||||
SET_PARAMS,
|
SET_PARAMS,
|
||||||
|
URI,
|
||||||
|
COMPLETE_TRANSITION,
|
||||||
|
OPEN_DRAWER,
|
||||||
|
CLOSE_DRAWER,
|
||||||
|
TOGGLE_DRAWER,
|
||||||
|
|
||||||
// Action creators
|
// Action creators
|
||||||
back,
|
back,
|
||||||
init,
|
init,
|
||||||
navigate,
|
navigate,
|
||||||
|
pop,
|
||||||
|
popToTop,
|
||||||
|
push,
|
||||||
|
reset,
|
||||||
|
replace,
|
||||||
setParams,
|
setParams,
|
||||||
|
uri,
|
||||||
|
completeTransition,
|
||||||
|
openDrawer,
|
||||||
|
closeDrawer,
|
||||||
|
toggleDrawer,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const StateUtils = {
|
|||||||
* routes of the navigation state, or -1 if it is not present.
|
* routes of the navigation state, or -1 if it is not present.
|
||||||
*/
|
*/
|
||||||
indexOf(state, key) {
|
indexOf(state, key) {
|
||||||
return state.routes.findIndex(route => route.key === key);
|
return state.routes.map(route => route.key).indexOf(key);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,23 +116,8 @@ const StateUtils = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace a route by a key.
|
* Replace a route by a key.
|
||||||
* Note that this moves the index to the position to where the new route in the
|
* Note that this moves the index to the positon to where the new route in the
|
||||||
* stack is at and updates the routes array accordingly.
|
* stack is at.
|
||||||
*/
|
|
||||||
replaceAndPrune(state, key, route) {
|
|
||||||
const index = StateUtils.indexOf(state, key);
|
|
||||||
const replaced = StateUtils.replaceAtIndex(state, index, route);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...replaced,
|
|
||||||
routes: replaced.routes.slice(0, index + 1),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a route by a key.
|
|
||||||
* Note that this moves the index to the position to where the new route in the
|
|
||||||
* stack is at. Does not prune the routes.
|
|
||||||
*/
|
*/
|
||||||
replaceAt(state, key, route) {
|
replaceAt(state, key, route) {
|
||||||
const index = StateUtils.indexOf(state, key);
|
const index = StateUtils.indexOf(state, key);
|
||||||
@@ -152,7 +137,7 @@ const StateUtils = {
|
|||||||
route.key
|
route.key
|
||||||
);
|
);
|
||||||
|
|
||||||
if (state.routes[index] === route && index === state.index) {
|
if (state.routes[index] === route) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +153,7 @@ const StateUtils = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets all routes.
|
* Resets all routes.
|
||||||
* Note that this moves the index to the position to where the last route in the
|
* Note that this moves the index to the positon to where the last route in the
|
||||||
* stack is at if the param `index` isn't provided.
|
* stack is at if the param `index` isn't provided.
|
||||||
*/
|
*/
|
||||||
reset(state, routes, index) {
|
reset(state, routes, index) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
|
|
||||||
describe('generic navigation actions', () => {
|
describe('actions', () => {
|
||||||
const params = { foo: 'bar' };
|
const params = { foo: 'bar' };
|
||||||
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
|
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
|
||||||
|
|
||||||
it('exports back action and type', () => {
|
it('exports back action and type', () => {
|
||||||
|
expect(NavigationActions.back.toString()).toEqual(NavigationActions.BACK);
|
||||||
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
|
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
|
||||||
expect(NavigationActions.back({ key: 'test' })).toEqual({
|
expect(NavigationActions.back({ key: 'test' })).toEqual({
|
||||||
type: NavigationActions.BACK,
|
type: NavigationActions.BACK,
|
||||||
@@ -13,6 +14,7 @@ describe('generic navigation actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('exports init action and type', () => {
|
it('exports init action and type', () => {
|
||||||
|
expect(NavigationActions.init.toString()).toEqual(NavigationActions.INIT);
|
||||||
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
|
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
|
||||||
expect(NavigationActions.init({ params })).toEqual({
|
expect(NavigationActions.init({ params })).toEqual({
|
||||||
type: NavigationActions.INIT,
|
type: NavigationActions.INIT,
|
||||||
@@ -21,6 +23,9 @@ describe('generic navigation actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('exports navigate action and type', () => {
|
it('exports navigate action and type', () => {
|
||||||
|
expect(NavigationActions.navigate.toString()).toEqual(
|
||||||
|
NavigationActions.NAVIGATE
|
||||||
|
);
|
||||||
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
|
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'test',
|
routeName: 'test',
|
||||||
@@ -42,7 +47,36 @@ describe('generic navigation actions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('exports reset action and type', () => {
|
||||||
|
expect(NavigationActions.reset.toString()).toEqual(NavigationActions.RESET);
|
||||||
|
expect(NavigationActions.reset({ index: 0, actions: [] })).toEqual({
|
||||||
|
type: NavigationActions.RESET,
|
||||||
|
index: 0,
|
||||||
|
actions: [],
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
NavigationActions.reset({
|
||||||
|
index: 0,
|
||||||
|
key: 'test',
|
||||||
|
actions: [navigateAction],
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
type: NavigationActions.RESET,
|
||||||
|
index: 0,
|
||||||
|
key: 'test',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'another',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('exports setParams action and type', () => {
|
it('exports setParams action and type', () => {
|
||||||
|
expect(NavigationActions.setParams.toString()).toEqual(
|
||||||
|
NavigationActions.SET_PARAMS
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
NavigationActions.setParams({
|
NavigationActions.setParams({
|
||||||
key: 'test',
|
key: 'test',
|
||||||
@@ -54,4 +88,12 @@ describe('generic navigation actions', () => {
|
|||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('exports uri action and type', () => {
|
||||||
|
expect(NavigationActions.uri.toString()).toEqual(NavigationActions.URI);
|
||||||
|
expect(NavigationActions.uri({ uri: 'http://google.com' })).toEqual({
|
||||||
|
type: NavigationActions.URI,
|
||||||
|
uri: 'http://google.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,50 +1,46 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import 'react-native';
|
||||||
|
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import createStackNavigator from '../navigators/createStackNavigator';
|
import StackNavigator from '../navigators/createStackNavigator';
|
||||||
import { _TESTING_ONLY_reset_container_count } from '../createNavigationContainer';
|
|
||||||
|
const FooScreen = () => <div />;
|
||||||
|
const BarScreen = () => <div />;
|
||||||
|
const BazScreen = () => <div />;
|
||||||
|
const CarScreen = () => <div />;
|
||||||
|
const DogScreen = () => <div />;
|
||||||
|
const ElkScreen = () => <div />;
|
||||||
|
const NavigationContainer = StackNavigator(
|
||||||
|
{
|
||||||
|
foo: {
|
||||||
|
screen: FooScreen,
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
screen: BarScreen,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
screen: BazScreen,
|
||||||
|
},
|
||||||
|
car: {
|
||||||
|
screen: CarScreen,
|
||||||
|
},
|
||||||
|
dog: {
|
||||||
|
screen: DogScreen,
|
||||||
|
},
|
||||||
|
elk: {
|
||||||
|
screen: ElkScreen,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialRouteName: 'foo',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe('NavigationContainer', () => {
|
describe('NavigationContainer', () => {
|
||||||
jest.useFakeTimers();
|
|
||||||
beforeEach(() => {
|
|
||||||
_TESTING_ONLY_reset_container_count();
|
|
||||||
});
|
|
||||||
|
|
||||||
const FooScreen = () => <div />;
|
|
||||||
const BarScreen = () => <div />;
|
|
||||||
const BazScreen = () => <div />;
|
|
||||||
const CarScreen = () => <div />;
|
|
||||||
const DogScreen = () => <div />;
|
|
||||||
const ElkScreen = () => <div />;
|
|
||||||
const NavigationContainer = createStackNavigator(
|
|
||||||
{
|
|
||||||
foo: {
|
|
||||||
screen: FooScreen,
|
|
||||||
},
|
|
||||||
bar: {
|
|
||||||
screen: BarScreen,
|
|
||||||
},
|
|
||||||
baz: {
|
|
||||||
screen: BazScreen,
|
|
||||||
},
|
|
||||||
car: {
|
|
||||||
screen: CarScreen,
|
|
||||||
},
|
|
||||||
dog: {
|
|
||||||
screen: DogScreen,
|
|
||||||
},
|
|
||||||
elk: {
|
|
||||||
screen: ElkScreen,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'foo',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('state.nav', () => {
|
describe('state.nav', () => {
|
||||||
it("should be preloaded with the router's initial state", () => {
|
it("should be preloaded with the router's initial state", () => {
|
||||||
const navigationContainer = renderer
|
const navigationContainer = renderer
|
||||||
@@ -202,57 +198,4 @@ describe('NavigationContainer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('warnings', () => {
|
|
||||||
function spyConsole() {
|
|
||||||
let spy = {};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
spy.console = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
spy.console.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
return spy;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('detached navigators', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
_TESTING_ONLY_reset_container_count();
|
|
||||||
});
|
|
||||||
|
|
||||||
let spy = spyConsole();
|
|
||||||
|
|
||||||
it('warns when you render more than one navigator explicitly', () => {
|
|
||||||
class BlankScreen extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <View />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootScreen extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<ChildNavigator />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChildNavigator = createStackNavigator({
|
|
||||||
Child: BlankScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
const RootStack = createStackNavigator({
|
|
||||||
Root: RootScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderer.create(<RootStack />).toJSON();
|
|
||||||
expect(spy).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }],
|
routes: [{ key: 'a', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
||||||
key: 'a',
|
key: 'a',
|
||||||
@@ -21,7 +20,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||||
@@ -32,7 +30,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
||||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
||||||
@@ -43,11 +40,9 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }],
|
routes: [{ key: 'a', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
||||||
@@ -59,7 +54,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }],
|
routes: [{ key: 'a', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(() =>
|
expect(() =>
|
||||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
NavigationStateUtils.push(state, { key: 'a', routeName })
|
||||||
@@ -71,12 +65,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }],
|
routes: [{ key: 'a', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||||
});
|
});
|
||||||
@@ -85,7 +77,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }],
|
routes: [{ key: 'a', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||||
});
|
});
|
||||||
@@ -95,12 +86,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||||
@@ -110,7 +99,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
||||||
});
|
});
|
||||||
@@ -119,12 +107,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||||
@@ -134,7 +120,6 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
||||||
});
|
});
|
||||||
@@ -143,12 +128,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
||||||
@@ -158,12 +141,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
||||||
@@ -174,12 +155,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
||||||
@@ -190,27 +169,24 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
||||||
).toEqual(newState);
|
).toEqual(newState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
it('Returns the state if index matches the route', () => {
|
||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
||||||
).toEqual({ ...state, index: 1 });
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
@@ -218,12 +194,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
NavigationStateUtils.reset(state, [
|
NavigationStateUtils.reset(state, [
|
||||||
@@ -241,12 +215,10 @@ describe('StateUtils', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
const newState = {
|
const newState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||||
isTransitioning: false,
|
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
NavigationStateUtils.reset(
|
NavigationStateUtils.reset(
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`NavigationContainer warnings detached navigators warns when you render more than one navigator explicitly 1`] = `
|
|
||||||
Object {
|
|
||||||
"console": [MockFunction] {
|
|
||||||
"calls": Array [
|
|
||||||
Array [
|
|
||||||
"You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: https://v2.reactnavigation.org/docs/common-mistakes.html#explicitly-rendering-more-than-one-navigator",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
118
src/__tests__/addNavigationHelpers-test.js
Normal file
118
src/__tests__/addNavigationHelpers-test.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import NavigationActions from '../NavigationActions';
|
||||||
|
import addNavigationHelpers from '../addNavigationHelpers';
|
||||||
|
|
||||||
|
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
||||||
|
remove: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addNavigationHelpers', () => {
|
||||||
|
it('handles Back action', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { key: 'A', routeName: 'Home' },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).goBack('A')
|
||||||
|
).toEqual(true);
|
||||||
|
expect(mockedDispatch).toBeCalledWith({
|
||||||
|
type: NavigationActions.BACK,
|
||||||
|
key: 'A',
|
||||||
|
});
|
||||||
|
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles Back action when the key is not defined', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { routeName: 'Home' },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).goBack()
|
||||||
|
).toEqual(true);
|
||||||
|
expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK });
|
||||||
|
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles Navigate action', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { routeName: 'Home' },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).navigate('Profile', { name: 'Matt' })
|
||||||
|
).toEqual(true);
|
||||||
|
expect(mockedDispatch).toBeCalledWith({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
params: { name: 'Matt' },
|
||||||
|
routeName: 'Profile',
|
||||||
|
});
|
||||||
|
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles SetParams action', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { key: 'B', routeName: 'Settings' },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).setParams({ notificationsEnabled: 'yes' })
|
||||||
|
).toEqual(true);
|
||||||
|
expect(mockedDispatch).toBeCalledWith({
|
||||||
|
type: NavigationActions.SET_PARAMS,
|
||||||
|
key: 'B',
|
||||||
|
params: { notificationsEnabled: 'yes' },
|
||||||
|
});
|
||||||
|
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles GetParams action', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { key: 'B', routeName: 'Settings', params: { name: 'Peter' } },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).getParam('name', 'Brent')
|
||||||
|
).toEqual('Peter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles GetParams action with default param', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { key: 'B', routeName: 'Settings' },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).getParam('name', 'Brent')
|
||||||
|
).toEqual('Brent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles GetParams action with param value as null', () => {
|
||||||
|
const mockedDispatch = jest
|
||||||
|
.fn(() => false)
|
||||||
|
.mockImplementationOnce(() => true);
|
||||||
|
expect(
|
||||||
|
addNavigationHelpers({
|
||||||
|
state: { key: 'B', routeName: 'Settings', params: { name: null } },
|
||||||
|
dispatch: mockedDispatch,
|
||||||
|
addListener: dummyEventSubscriber,
|
||||||
|
}).getParam('name')
|
||||||
|
).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
93
src/addNavigationHelpers.js
Normal file
93
src/addNavigationHelpers.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/* Helpers for navigation */
|
||||||
|
|
||||||
|
import NavigationActions from './NavigationActions';
|
||||||
|
import invariant from './utils/invariant';
|
||||||
|
|
||||||
|
export default function(navigation) {
|
||||||
|
return {
|
||||||
|
...navigation,
|
||||||
|
goBack: key => {
|
||||||
|
let actualizedKey = key;
|
||||||
|
if (key === undefined && navigation.state.key) {
|
||||||
|
invariant(
|
||||||
|
typeof navigation.state.key === 'string',
|
||||||
|
'key should be a string'
|
||||||
|
);
|
||||||
|
actualizedKey = navigation.state.key;
|
||||||
|
}
|
||||||
|
return navigation.dispatch(
|
||||||
|
NavigationActions.back({ key: actualizedKey })
|
||||||
|
);
|
||||||
|
},
|
||||||
|
navigate: (navigateTo, params, action) => {
|
||||||
|
if (typeof navigateTo === 'string') {
|
||||||
|
return navigation.dispatch(
|
||||||
|
NavigationActions.navigate({ routeName: navigateTo, params, action })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
invariant(
|
||||||
|
typeof navigateTo === 'object',
|
||||||
|
'Must navigateTo an object or a string'
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
params == null,
|
||||||
|
'Params must not be provided to .navigate() when specifying an object'
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
action == null,
|
||||||
|
'Child action must not be provided to .navigate() when specifying an object'
|
||||||
|
);
|
||||||
|
return navigation.dispatch(NavigationActions.navigate(navigateTo));
|
||||||
|
},
|
||||||
|
pop: (n, params) =>
|
||||||
|
navigation.dispatch(
|
||||||
|
NavigationActions.pop({ n, immediate: params && params.immediate })
|
||||||
|
),
|
||||||
|
popToTop: params =>
|
||||||
|
navigation.dispatch(
|
||||||
|
NavigationActions.popToTop({ immediate: params && params.immediate })
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* For updating current route params. For example the nav bar title and
|
||||||
|
* buttons are based on the route params.
|
||||||
|
* This means `setParams` can be used to update nav bar for example.
|
||||||
|
*/
|
||||||
|
setParams: params => {
|
||||||
|
invariant(
|
||||||
|
navigation.state.key && typeof navigation.state.key === 'string',
|
||||||
|
'setParams cannot be called by root navigator'
|
||||||
|
);
|
||||||
|
const key = navigation.state.key;
|
||||||
|
return navigation.dispatch(NavigationActions.setParams({ params, key }));
|
||||||
|
},
|
||||||
|
|
||||||
|
getParam: (paramName, defaultValue) => {
|
||||||
|
const params = navigation.state.params;
|
||||||
|
|
||||||
|
if (params && paramName in params) {
|
||||||
|
return params[paramName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
push: (routeName, params, action) =>
|
||||||
|
navigation.dispatch(
|
||||||
|
NavigationActions.push({ routeName, params, action })
|
||||||
|
),
|
||||||
|
|
||||||
|
replace: (routeName, params, action) =>
|
||||||
|
navigation.dispatch(
|
||||||
|
NavigationActions.replace({
|
||||||
|
routeName,
|
||||||
|
params,
|
||||||
|
action,
|
||||||
|
key: navigation.state.key,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
|
||||||
|
closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
|
||||||
|
toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,54 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AsyncStorage, Linking, Platform } from 'react-native';
|
import { Linking } from 'react-native';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
|
||||||
|
|
||||||
import { BackHandler } from './PlatformHelpers';
|
import { BackHandler } from './PlatformHelpers';
|
||||||
import NavigationActions from './NavigationActions';
|
import NavigationActions from './NavigationActions';
|
||||||
|
import addNavigationHelpers from './addNavigationHelpers';
|
||||||
import invariant from './utils/invariant';
|
import invariant from './utils/invariant';
|
||||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
|
||||||
import docsUrl from './utils/docsUrl';
|
|
||||||
|
|
||||||
function isStateful(props) {
|
|
||||||
return !props.navigation;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateProps(props) {
|
|
||||||
if (isStateful(props)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { navigation, screenProps, ...containerProps } = props;
|
|
||||||
|
|
||||||
const keys = Object.keys(containerProps);
|
|
||||||
|
|
||||||
if (keys.length !== 0) {
|
|
||||||
throw new Error(
|
|
||||||
'This navigator has both navigation and container props, so it is ' +
|
|
||||||
`unclear if it should own its own state. Remove props: "${keys.join(
|
|
||||||
', '
|
|
||||||
)}" ` +
|
|
||||||
'if the navigator should get its state from the navigation prop. If the ' +
|
|
||||||
'navigator should maintain its own state, do not pass a navigation prop.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track the number of stateful container instances. Warn if >0 and not using the
|
|
||||||
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
|
|
||||||
// stateful navigation containers in a future release and require a provider style pattern
|
|
||||||
// instead in order to eliminate confusion entirely.
|
|
||||||
let _statefulContainerCount = 0;
|
|
||||||
export function _TESTING_ONLY_reset_container_count() {
|
|
||||||
_statefulContainerCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We keep a global flag to catch errors during the state persistence hydrating scenario.
|
|
||||||
// The innermost navigator who catches the error will dispatch a new init action.
|
|
||||||
let _reactNavigationIsHydratingState = false;
|
|
||||||
// Unfortunate to use global state here, but it seems necessesary for the time
|
|
||||||
// being. There seems to be some problems with cascading componentDidCatch
|
|
||||||
// handlers. Ideally the inner non-stateful navigator catches the error and
|
|
||||||
// re-throws it, to be caught by the top-level stateful navigator.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HOC that injects the navigation and manages the navigation state
|
* Create an HOC that injects the navigation and manages the navigation state
|
||||||
@@ -63,17 +18,12 @@ export default function createNavigationContainer(Component) {
|
|||||||
static router = Component.router;
|
static router = Component.router;
|
||||||
static navigationOptions = null;
|
static navigationOptions = null;
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
|
||||||
validateProps(nextProps);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_actionEventSubscribers = new Set();
|
_actionEventSubscribers = new Set();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
validateProps(props);
|
this._validateProps(props);
|
||||||
|
|
||||||
this._initialAction = NavigationActions.init();
|
this._initialAction = NavigationActions.init();
|
||||||
|
|
||||||
@@ -91,21 +41,14 @@ export default function createNavigationContainer(Component) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
nav:
|
nav: this._isStateful()
|
||||||
this._isStateful() && !props.persistenceKey
|
? Component.router.getStateForAction(this._initialAction)
|
||||||
? Component.router.getStateForAction(this._initialAction)
|
: null,
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderLoading() {
|
|
||||||
return this.props.renderLoadingExperimental
|
|
||||||
? this.props.renderLoadingExperimental()
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isStateful() {
|
_isStateful() {
|
||||||
return isStateful(this.props);
|
return !this.props.navigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
_validateProps(props) {
|
_validateProps(props) {
|
||||||
@@ -184,177 +127,74 @@ export default function createNavigationContainer(Component) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this._validateProps(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
// Clear cached _navState every tick
|
// Clear cached _nav every tick
|
||||||
if (this._navState === this.state.nav) {
|
if (this._nav === this.state.nav) {
|
||||||
this._navState = null;
|
this._nav = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
if (!this._isStateful()) {
|
if (!this._isStateful()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__ && !this.props.detached) {
|
|
||||||
if (_statefulContainerCount > 0) {
|
|
||||||
// Temporarily only show this on iOS due to this issue:
|
|
||||||
// https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
|
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
console.warn(
|
|
||||||
`You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
|
|
||||||
'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_statefulContainerCount++;
|
|
||||||
Linking.addEventListener('url', this._handleOpenURL);
|
Linking.addEventListener('url', this._handleOpenURL);
|
||||||
|
|
||||||
// Pull out anything that can impact state
|
Linking.getInitialURL().then(url => url && this._handleOpenURL({ url }));
|
||||||
const { persistenceKey } = this.props;
|
|
||||||
const startupStateJSON =
|
|
||||||
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
|
||||||
const url = await Linking.getInitialURL();
|
|
||||||
const parsedUrl = url && this._urlToPathAndParams(url);
|
|
||||||
|
|
||||||
// Initialize state. This must be done *after* any async code
|
this._actionEventSubscribers.forEach(subscriber =>
|
||||||
// so we don't end up with a different value for this.state.nav
|
subscriber({
|
||||||
// due to changes while async function was resolving
|
type: 'action',
|
||||||
let action = this._initialAction;
|
action: this._initialAction,
|
||||||
let startupState = this.state.nav;
|
state: this.state.nav,
|
||||||
if (!startupState) {
|
lastState: null,
|
||||||
!!process.env.REACT_NAV_LOGGING &&
|
})
|
||||||
console.log('Init new Navigation State');
|
);
|
||||||
startupState = Component.router.getStateForAction(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull persisted state from AsyncStorage
|
|
||||||
if (startupStateJSON) {
|
|
||||||
try {
|
|
||||||
startupState = JSON.parse(startupStateJSON);
|
|
||||||
_reactNavigationIsHydratingState = true;
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull state out of URL
|
|
||||||
if (parsedUrl) {
|
|
||||||
const { path, params } = parsedUrl;
|
|
||||||
const urlAction = Component.router.getActionForPathAndParams(
|
|
||||||
path,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
if (urlAction) {
|
|
||||||
!!process.env.REACT_NAV_LOGGING &&
|
|
||||||
console.log('Applying Navigation Action for Initial URL:', url);
|
|
||||||
action = urlAction;
|
|
||||||
startupState = Component.router.getStateForAction(
|
|
||||||
urlAction,
|
|
||||||
startupState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatchActions = () =>
|
|
||||||
this._actionEventSubscribers.forEach(subscriber =>
|
|
||||||
subscriber({
|
|
||||||
type: 'action',
|
|
||||||
action,
|
|
||||||
state: this.state.nav,
|
|
||||||
lastState: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (startupState === this.state.nav) {
|
|
||||||
dispatchActions();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ nav: startupState }, () => {
|
|
||||||
_reactNavigationIsHydratingState = false;
|
|
||||||
dispatchActions();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(e, errorInfo) {
|
|
||||||
if (_reactNavigationIsHydratingState) {
|
|
||||||
_reactNavigationIsHydratingState = false;
|
|
||||||
console.warn(
|
|
||||||
'Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state..'
|
|
||||||
);
|
|
||||||
this.dispatch(NavigationActions.init());
|
|
||||||
} else {
|
|
||||||
throw new Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_persistNavigationState = async nav => {
|
|
||||||
const { persistenceKey } = this.props;
|
|
||||||
if (!persistenceKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await AsyncStorage.setItem(persistenceKey, JSON.stringify(nav));
|
|
||||||
};
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
Linking.removeEventListener('url', this._handleOpenURL);
|
Linking.removeEventListener('url', this._handleOpenURL);
|
||||||
this.subs && this.subs.remove();
|
this.subs && this.subs.remove();
|
||||||
|
|
||||||
if (this._isStateful()) {
|
|
||||||
_statefulContainerCount--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-tick temporary storage for state.nav
|
// Per-tick temporary storage for state.nav
|
||||||
|
|
||||||
dispatch = action => {
|
dispatch = action => {
|
||||||
if (this.props.navigation) {
|
if (!this._isStateful()) {
|
||||||
return this.props.navigation.dispatch(action);
|
return false;
|
||||||
}
|
}
|
||||||
|
this._nav = this._nav || this.state.nav;
|
||||||
// navState will have the most up-to-date value, because setState sometimes behaves asyncronously
|
const oldNav = this._nav;
|
||||||
this._navState = this._navState || this.state.nav;
|
invariant(oldNav, 'should be set in constructor if stateful');
|
||||||
const lastNavState = this._navState;
|
const nav = Component.router.getStateForAction(action, oldNav);
|
||||||
invariant(lastNavState, 'should be set in constructor if stateful');
|
|
||||||
const reducedState = Component.router.getStateForAction(
|
|
||||||
action,
|
|
||||||
lastNavState
|
|
||||||
);
|
|
||||||
const navState = reducedState === null ? lastNavState : reducedState;
|
|
||||||
|
|
||||||
const dispatchActionEvents = () => {
|
const dispatchActionEvents = () => {
|
||||||
this._actionEventSubscribers.forEach(subscriber =>
|
this._actionEventSubscribers.forEach(subscriber =>
|
||||||
subscriber({
|
subscriber({
|
||||||
type: 'action',
|
type: 'action',
|
||||||
action,
|
action,
|
||||||
state: navState,
|
state: nav,
|
||||||
lastState: lastNavState,
|
lastState: oldNav,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
if (nav && nav !== oldNav) {
|
||||||
if (reducedState === null) {
|
|
||||||
// The router will return null when action has been handled and the state hasn't changed.
|
|
||||||
// dispatch returns true when something has been handled.
|
|
||||||
dispatchActionEvents();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navState !== lastNavState) {
|
|
||||||
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
|
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
|
||||||
this._navState = navState;
|
this._nav = nav;
|
||||||
this.setState({ nav: navState }, () => {
|
this.setState({ nav }, () => {
|
||||||
this._onNavigationStateChange(lastNavState, navState, action);
|
this._onNavigationStateChange(oldNav, nav, action);
|
||||||
dispatchActionEvents();
|
dispatchActionEvents();
|
||||||
this._persistNavigationState(navState);
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
dispatchActionEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchActionEvents();
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -362,11 +202,9 @@ export default function createNavigationContainer(Component) {
|
|||||||
let navigation = this.props.navigation;
|
let navigation = this.props.navigation;
|
||||||
if (this._isStateful()) {
|
if (this._isStateful()) {
|
||||||
const nav = this.state.nav;
|
const nav = this.state.nav;
|
||||||
if (!nav) {
|
invariant(nav, 'should be set in constructor if stateful');
|
||||||
return this._renderLoading();
|
|
||||||
}
|
|
||||||
if (!this._navigation || this._navigation.state !== nav) {
|
if (!this._navigation || this._navigation.state !== nav) {
|
||||||
this._navigation = {
|
this._navigation = addNavigationHelpers({
|
||||||
dispatch: this.dispatch,
|
dispatch: this.dispatch,
|
||||||
state: nav,
|
state: nav,
|
||||||
addListener: (eventName, handler) => {
|
addListener: (eventName, handler) => {
|
||||||
@@ -380,11 +218,6 @@ export default function createNavigationContainer(Component) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
|
||||||
const actionCreators = getNavigationActionCreators(nav);
|
|
||||||
Object.keys(actionCreators).forEach(actionName => {
|
|
||||||
this._navigation[actionName] = (...args) =>
|
|
||||||
this.dispatch(actionCreators[actionName](...args));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
navigation = this._navigation;
|
navigation = this._navigation;
|
||||||
@@ -394,5 +227,5 @@ export default function createNavigationContainer(Component) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return polyfill(NavigationContainer);
|
return NavigationContainer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default function getChildEventSubscriber(addListener, key) {
|
|||||||
action,
|
action,
|
||||||
type: eventName,
|
type: eventName,
|
||||||
};
|
};
|
||||||
const isTransitioning = !!state && state.isTransitioning;
|
const isTransitioning = !!state && !!state.transitioningFromKey;
|
||||||
|
|
||||||
const previouslyLastEmittedEvent = lastEmittedEvent;
|
const previouslyLastEmittedEvent = lastEmittedEvent;
|
||||||
|
|
||||||
@@ -138,14 +138,11 @@ export default function getChildEventSubscriber(addListener, key) {
|
|||||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastEmittedEvent === 'didBlur' && !newRoute) {
|
|
||||||
removeAll();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
removeAll,
|
||||||
addListener(eventName, eventHandler) {
|
addListener(eventName, eventHandler) {
|
||||||
const subscribers = getChildSubscribers(eventName);
|
const subscribers = getChildSubscribers(eventName);
|
||||||
if (!subscribers) {
|
if (!subscribers) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import SafeAreaView from 'react-native-safe-area-view';
|
|||||||
import createNavigator from './createNavigator';
|
import createNavigator from './createNavigator';
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
import DrawerRouter from '../routers/DrawerRouter';
|
import DrawerRouter from '../routers/DrawerRouter';
|
||||||
|
import DrawerScreen from '../views/Drawer/DrawerScreen';
|
||||||
import DrawerView from '../views/Drawer/DrawerView';
|
import DrawerView from '../views/Drawer/DrawerView';
|
||||||
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
||||||
|
|
||||||
@@ -50,7 +51,6 @@ const DrawerNavigator = (routeConfigs, config = {}) => {
|
|||||||
paths,
|
paths,
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
backBehavior,
|
backBehavior,
|
||||||
getCustomActionCreators,
|
|
||||||
...drawerConfig
|
...drawerConfig
|
||||||
} = mergedConfig;
|
} = mergedConfig;
|
||||||
|
|
||||||
@@ -59,7 +59,6 @@ const DrawerNavigator = (routeConfigs, config = {}) => {
|
|||||||
paths,
|
paths,
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
backBehavior,
|
backBehavior,
|
||||||
getCustomActionCreators,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
|
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
|
||||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import DrawerNavigator from '../createDrawerNavigator';
|
import DrawerNavigator from '../DrawerNavigator';
|
||||||
|
|
||||||
class HomeScreen extends Component {
|
class HomeScreen extends Component {
|
||||||
static navigationOptions = ({ navigation }) => ({
|
static navigationOptions = ({ navigation }) => ({
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { StyleSheet, View } from 'react-native';
|
|||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import StackNavigator from '../createStackNavigator';
|
import StackNavigator from '../createStackNavigator';
|
||||||
import withNavigation from '../../views/withNavigation';
|
|
||||||
import { _TESTING_ONLY_reset_container_count } from '../../createNavigationContainer';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
header: {
|
header: {
|
||||||
@@ -33,10 +31,6 @@ const routeConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('StackNavigator', () => {
|
describe('StackNavigator', () => {
|
||||||
beforeEach(() => {
|
|
||||||
_TESTING_ONLY_reset_container_count();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders successfully', () => {
|
it('renders successfully', () => {
|
||||||
const MyStackNavigator = StackNavigator(routeConfig);
|
const MyStackNavigator = StackNavigator(routeConfig);
|
||||||
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
||||||
@@ -57,37 +51,4 @@ describe('StackNavigator', () => {
|
|||||||
|
|
||||||
expect(rendered).toMatchSnapshot();
|
expect(rendered).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes navigation to headerRight when wrapped in withNavigation', () => {
|
|
||||||
const spy = jest.fn();
|
|
||||||
|
|
||||||
class TestComponent extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <View>{this.props.onPress(this.props.navigation)}</View>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestComponentWithNavigation = withNavigation(TestComponent);
|
|
||||||
|
|
||||||
class A extends React.Component {
|
|
||||||
static navigationOptions = {
|
|
||||||
headerRight: <TestComponentWithNavigation onPress={spy} />,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <View />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Nav = StackNavigator({ A: { screen: A } });
|
|
||||||
|
|
||||||
renderer.create(<Nav />);
|
|
||||||
|
|
||||||
expect(spy).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
navigate: expect.any(Function),
|
|
||||||
addListener: expect.any(Function),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { View } from 'react-native';
|
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
|
|
||||||
import SwitchNavigator from '../createSwitchNavigator';
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,9 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
const {
|
import TabNavigator from '../createTabNavigator';
|
||||||
createTabNavigator,
|
|
||||||
} = require('react-navigation-deprecated-tab-navigator');
|
|
||||||
|
|
||||||
class HomeScreen extends Component {
|
class HomeScreen extends Component {
|
||||||
static navigationOptions = ({ navigation }) => ({
|
static navigationOptions = ({ navigation }) => ({
|
||||||
@@ -27,7 +25,7 @@ const routeConfig = {
|
|||||||
|
|
||||||
describe('TabNavigator', () => {
|
describe('TabNavigator', () => {
|
||||||
it('renders successfully', () => {
|
it('renders successfully', () => {
|
||||||
const MyTabNavigator = createTabNavigator(routeConfig);
|
const MyTabNavigator = TabNavigator(routeConfig);
|
||||||
const rendered = renderer.create(<MyTabNavigator />).toJSON();
|
const rendered = renderer.create(<MyTabNavigator />).toJSON();
|
||||||
|
|
||||||
expect(rendered).toMatchSnapshot();
|
expect(rendered).toMatchSnapshot();
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
"backgroundColor": "#E9E9EF",
|
"backgroundColor": "#E9E9EF",
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
"left": 0,
|
"left": 0,
|
||||||
"marginTop": 0,
|
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
@@ -77,31 +76,37 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"transform": Array [
|
"backgroundColor": "red",
|
||||||
Object {
|
"borderBottomColor": "#A7A7AA",
|
||||||
"translateX": 0,
|
"borderBottomWidth": 0.5,
|
||||||
},
|
"height": 64,
|
||||||
],
|
"opacity": 0.5,
|
||||||
|
"paddingBottom": 0,
|
||||||
|
"paddingLeft": 0,
|
||||||
|
"paddingRight": 0,
|
||||||
|
"paddingTop": 20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
|
||||||
onLayout={[Function]}
|
|
||||||
pointerEvents="box-none"
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "red",
|
"bottom": 0,
|
||||||
"borderBottomColor": "#A7A7AA",
|
"left": 0,
|
||||||
"borderBottomWidth": 0.5,
|
"position": "absolute",
|
||||||
"height": 64,
|
"right": 0,
|
||||||
"opacity": 0.5,
|
"top": 0,
|
||||||
"paddingBottom": 0,
|
}
|
||||||
"paddingLeft": 0,
|
}
|
||||||
"paddingRight": 0,
|
/>
|
||||||
"paddingTop": 20,
|
<View
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"flex": 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -109,89 +114,70 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
|
"flexDirection": "row",
|
||||||
"left": 0,
|
"left": 0,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
"top": 0,
|
"top": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"flex": 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
|
collapsable={undefined}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"left": 0,
|
"justifyContent": "center",
|
||||||
|
"left": 70,
|
||||||
|
"opacity": 1,
|
||||||
|
"position": "absolute",
|
||||||
|
"right": 70,
|
||||||
|
"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,
|
||||||
|
"flexDirection": "row",
|
||||||
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
"top": 0,
|
"top": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<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,
|
|
||||||
"flexDirection": "row",
|
|
||||||
"opacity": 1,
|
|
||||||
"position": "absolute",
|
|
||||||
"right": 0,
|
|
||||||
"top": 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<View />
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -251,7 +237,6 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
"backgroundColor": "#E9E9EF",
|
"backgroundColor": "#E9E9EF",
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
"left": 0,
|
"left": 0,
|
||||||
"marginTop": 0,
|
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
@@ -277,31 +262,37 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"transform": Array [
|
"backgroundColor": "red",
|
||||||
Object {
|
"borderBottomColor": "#A7A7AA",
|
||||||
"translateX": 0,
|
"borderBottomWidth": 0.5,
|
||||||
},
|
"height": 64,
|
||||||
],
|
"opacity": 0.5,
|
||||||
|
"paddingBottom": 0,
|
||||||
|
"paddingLeft": 0,
|
||||||
|
"paddingRight": 0,
|
||||||
|
"paddingTop": 20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
|
||||||
onLayout={[Function]}
|
|
||||||
pointerEvents="box-none"
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "red",
|
"bottom": 0,
|
||||||
"borderBottomColor": "#A7A7AA",
|
"left": 0,
|
||||||
"borderBottomWidth": 0.5,
|
"position": "absolute",
|
||||||
"height": 64,
|
"right": 0,
|
||||||
"opacity": 0.5,
|
"top": 0,
|
||||||
"paddingBottom": 0,
|
}
|
||||||
"paddingLeft": 0,
|
}
|
||||||
"paddingRight": 0,
|
/>
|
||||||
"paddingTop": 20,
|
<View
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"flex": 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -309,71 +300,52 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
|
"flexDirection": "row",
|
||||||
"left": 0,
|
"left": 0,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
"top": 0,
|
"top": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"flex": 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
|
collapsable={undefined}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
"bottom": 0,
|
"bottom": 0,
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
|
"justifyContent": "center",
|
||||||
"left": 0,
|
"left": 0,
|
||||||
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"right": 0,
|
"right": 0,
|
||||||
"top": 0,
|
"top": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<Text
|
||||||
|
accessibilityTraits="header"
|
||||||
|
accessible={true}
|
||||||
|
allowFontScaling={true}
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
pointerEvents="box-none"
|
ellipsizeMode="tail"
|
||||||
|
numberOfLines={1}
|
||||||
|
onLayout={[Function]}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"color": "rgba(0, 0, 0, .9)",
|
||||||
"backgroundColor": "transparent",
|
"fontSize": 17,
|
||||||
"bottom": 0,
|
"fontWeight": "700",
|
||||||
"flexDirection": "row",
|
"marginHorizontal": 16,
|
||||||
"justifyContent": "center",
|
"textAlign": "center",
|
||||||
"left": 0,
|
|
||||||
"opacity": 1,
|
|
||||||
"position": "absolute",
|
|
||||||
"right": 0,
|
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text
|
Welcome anonymous
|
||||||
accessibilityTraits="header"
|
</Text>
|
||||||
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>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`SwitchNavigator renders successfully 1`] = `<View />`;
|
|
||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
exports[`TabNavigator renders successfully 1`] = `
|
exports[`TabNavigator renders successfully 1`] = `
|
||||||
<View
|
<View
|
||||||
collapsable={false}
|
loaded={
|
||||||
|
Array [
|
||||||
|
0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onLayout={[Function]}
|
||||||
style={
|
style={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -16,65 +21,56 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
onLayout={[Function]}
|
collapsable={undefined}
|
||||||
|
onMoveShouldSetResponder={[Function]}
|
||||||
|
onMoveShouldSetResponderCapture={[Function]}
|
||||||
|
onResponderEnd={[Function]}
|
||||||
|
onResponderGrant={[Function]}
|
||||||
|
onResponderMove={[Function]}
|
||||||
|
onResponderReject={[Function]}
|
||||||
|
onResponderRelease={[Function]}
|
||||||
|
onResponderStart={[Function]}
|
||||||
|
onResponderTerminate={[Function]}
|
||||||
|
onResponderTerminationRequest={[Function]}
|
||||||
|
onStartShouldSetResponder={[Function]}
|
||||||
|
onStartShouldSetResponderCapture={[Function]}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"alignItems": "stretch",
|
||||||
"flex": 1,
|
"flex": 1,
|
||||||
|
"flexDirection": "row",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
|
||||||
onMoveShouldSetResponder={[Function]}
|
|
||||||
onMoveShouldSetResponderCapture={[Function]}
|
|
||||||
onResponderEnd={[Function]}
|
|
||||||
onResponderGrant={[Function]}
|
|
||||||
onResponderMove={[Function]}
|
|
||||||
onResponderReject={[Function]}
|
|
||||||
onResponderRelease={[Function]}
|
|
||||||
onResponderStart={[Function]}
|
|
||||||
onResponderTerminate={[Function]}
|
|
||||||
onResponderTerminationRequest={[Function]}
|
|
||||||
onStartShouldSetResponder={[Function]}
|
|
||||||
onStartShouldSetResponderCapture={[Function]}
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "stretch",
|
"bottom": 0,
|
||||||
"flex": 1,
|
"left": 0,
|
||||||
"flexDirection": "row",
|
"position": "absolute",
|
||||||
|
"right": 0,
|
||||||
|
"top": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
testID={undefined}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
|
collapsable={false}
|
||||||
|
removeClippedSubviews={false}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"bottom": 0,
|
"flex": 1,
|
||||||
"left": 0,
|
"overflow": "hidden",
|
||||||
"position": "absolute",
|
|
||||||
"right": 0,
|
|
||||||
"top": 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
testID={undefined}
|
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
collapsable={false}
|
|
||||||
removeClippedSubviews={false}
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"flex": 1,
|
"flex": 1,
|
||||||
"overflow": "hidden",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<View
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"flex": 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -141,15 +137,9 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
Array [
|
Object {
|
||||||
Object {
|
"flexGrow": 1,
|
||||||
"height": 29,
|
}
|
||||||
},
|
|
||||||
false,
|
|
||||||
Object {
|
|
||||||
"flex": 1,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -160,7 +150,6 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
"alignSelf": "center",
|
"alignSelf": "center",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"minWidth": 30,
|
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
@@ -175,7 +164,6 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
"alignSelf": "center",
|
"alignSelf": "center",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"minWidth": 30,
|
|
||||||
"opacity": 0,
|
"opacity": 0,
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TextInput } from 'react-native';
|
|
||||||
|
|
||||||
export default Navigator =>
|
|
||||||
class KeyboardAwareNavigator extends React.Component {
|
|
||||||
static router = Navigator.router;
|
|
||||||
_previouslyFocusedTextInput = null;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Navigator
|
|
||||||
{...this.props}
|
|
||||||
onGestureBegin={this._handleGestureBegin}
|
|
||||||
onGestureCanceled={this._handleGestureCanceled}
|
|
||||||
onGestureFinish={this._handleGestureFinish}
|
|
||||||
onTransitionStart={this._handleTransitionStart}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleGestureBegin = () => {
|
|
||||||
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
|
|
||||||
if (this._previouslyFocusedTextInput) {
|
|
||||||
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
|
|
||||||
}
|
|
||||||
this.props.onGestureBegin && this.props.onGestureBegin();
|
|
||||||
};
|
|
||||||
|
|
||||||
_handleGestureCanceled = () => {
|
|
||||||
if (this._previouslyFocusedTextInput) {
|
|
||||||
TextInput.State.focusTextInput(this._previouslyFocusedTextInput);
|
|
||||||
}
|
|
||||||
this.props.onGestureFinish && this.props.onGestureFinish();
|
|
||||||
};
|
|
||||||
|
|
||||||
_handleGestureFinish = () => {
|
|
||||||
this._previouslyFocusedTextInput = null;
|
|
||||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
|
||||||
};
|
|
||||||
|
|
||||||
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
|
|
||||||
// TODO: We should not even have received the transition start event
|
|
||||||
// in the case where the index did not change, I believe. We
|
|
||||||
// should revisit this after 2.0 release.
|
|
||||||
if (transitionProps.index !== prevTransitionProps.index) {
|
|
||||||
const currentField = TextInput.State.currentlyFocusedField();
|
|
||||||
if (currentField) {
|
|
||||||
TextInput.State.blurTextInput(currentField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onTransitionStart &&
|
|
||||||
this.props.onTransitionStart(transitionProps, prevTransitionProps);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,137 +1,81 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
|
||||||
|
|
||||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||||
|
import addNavigationHelpers from '../addNavigationHelpers';
|
||||||
|
|
||||||
function createNavigator(NavigatorView, router, navigationConfig) {
|
function createNavigator(NavigatorView, router, navigationConfig) {
|
||||||
class Navigator extends React.Component {
|
class Navigator extends React.Component {
|
||||||
static router = router;
|
static router = router;
|
||||||
static navigationOptions = null;
|
static navigationOptions = null;
|
||||||
|
|
||||||
state = {
|
childEventSubscribers = {};
|
||||||
descriptors: {},
|
|
||||||
childEventSubscribers: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
|
||||||
const { navigation, screenProps } = nextProps;
|
|
||||||
const { dispatch, state, addListener } = navigation;
|
|
||||||
const { routes } = state;
|
|
||||||
|
|
||||||
const descriptors = { ...prevState.descriptors };
|
|
||||||
const childEventSubscribers = { ...prevState.childEventSubscribers };
|
|
||||||
routes.forEach(route => {
|
|
||||||
if (!descriptors[route.key] || descriptors[route.key].state !== route) {
|
|
||||||
const getComponent = () =>
|
|
||||||
router.getComponentForRouteName(route.routeName);
|
|
||||||
|
|
||||||
if (!childEventSubscribers[route.key]) {
|
|
||||||
childEventSubscribers[route.key] = getChildEventSubscriber(
|
|
||||||
addListener,
|
|
||||||
route.key
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionCreators = {
|
|
||||||
...navigation.actions,
|
|
||||||
...router.getActionCreators(route, state.key),
|
|
||||||
};
|
|
||||||
const actionHelpers = {};
|
|
||||||
Object.keys(actionCreators).forEach(actionName => {
|
|
||||||
actionHelpers[actionName] = (...args) => {
|
|
||||||
const actionCreator = actionCreators[actionName];
|
|
||||||
const action = actionCreator(...args);
|
|
||||||
dispatch(action);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const childNavigation = {
|
|
||||||
...actionHelpers,
|
|
||||||
actions: actionCreators,
|
|
||||||
dispatch,
|
|
||||||
state: route,
|
|
||||||
addListener: childEventSubscribers[route.key].addListener,
|
|
||||||
getParam: (paramName, defaultValue) => {
|
|
||||||
const params = route.params;
|
|
||||||
|
|
||||||
if (params && paramName in params) {
|
|
||||||
return params[paramName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = router.getScreenOptions(childNavigation, screenProps);
|
|
||||||
descriptors[route.key] = {
|
|
||||||
key: route.key,
|
|
||||||
getComponent,
|
|
||||||
options,
|
|
||||||
state: route,
|
|
||||||
navigation: childNavigation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
descriptors,
|
|
||||||
childEventSubscribers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup subscriptions for routes that no longer exist
|
// Cleanup subscriptions for routes that no longer exist
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
|
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
|
||||||
let childEventSubscribers = { ...this.state.childEventSubscribers };
|
Object.keys(this.childEventSubscribers).forEach(key => {
|
||||||
Object.keys(childEventSubscribers).forEach(key => {
|
|
||||||
if (!activeKeys.includes(key)) {
|
if (!activeKeys.includes(key)) {
|
||||||
delete childEventSubscribers[key];
|
this.childEventSubscribers[key].removeAll();
|
||||||
|
delete this.childEventSubscribers[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (
|
|
||||||
childEventSubscribers.length !== this.state.childEventSubscribers.length
|
|
||||||
) {
|
|
||||||
this.setState({ childEventSubscribers });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_isRouteFocused = route => {
|
// Remove all subscriptions
|
||||||
|
componentWillUnmount() {
|
||||||
|
Object.values(this.childEventSubscribers).map(s => s.removeAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRouteFocused = route => () => {
|
||||||
const { state } = this.props.navigation;
|
const { state } = this.props.navigation;
|
||||||
const focusedRoute = state.routes[state.index];
|
const focusedRoute = state.routes[state.index];
|
||||||
return route === focusedRoute;
|
return route === focusedRoute;
|
||||||
};
|
};
|
||||||
|
|
||||||
_dangerouslyGetParent = () => {
|
|
||||||
return this.props.navigation;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// Mutation in render 😩
|
const { navigation, screenProps } = this.props;
|
||||||
// The problem:
|
const { dispatch, state, addListener } = navigation;
|
||||||
// - We don't want to re-render each screen every time the parent navigator changes
|
const { routes } = state;
|
||||||
// - But we need to be able to access the parent navigator from callbacks
|
|
||||||
// - These functions should only be used within callbacks, but they are passed in props,
|
const descriptors = {};
|
||||||
// which is what makes this awkward. What's a good way to pass in stuff that we don't
|
routes.forEach(route => {
|
||||||
// want people to depend on in render?
|
const getComponent = () =>
|
||||||
let descriptors = { ...this.state.descriptors };
|
router.getComponentForRouteName(route.routeName);
|
||||||
Object.values(descriptors).forEach(descriptor => {
|
|
||||||
descriptor.navigation.isFocused = () =>
|
if (!this.childEventSubscribers[route.key]) {
|
||||||
this._isRouteFocused(descriptor.state);
|
this.childEventSubscribers[route.key] = getChildEventSubscriber(
|
||||||
descriptor.navigation.dangerouslyGetParent = this._dangerouslyGetParent;
|
addListener,
|
||||||
|
route.key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const childNavigation = addNavigationHelpers({
|
||||||
|
dispatch,
|
||||||
|
state: route,
|
||||||
|
addListener: this.childEventSubscribers[route.key].addListener,
|
||||||
|
isFocused: this._isRouteFocused.bind(this, route),
|
||||||
|
});
|
||||||
|
const options = router.getScreenOptions(childNavigation, screenProps);
|
||||||
|
descriptors[route.key] = {
|
||||||
|
key: route.key,
|
||||||
|
getComponent,
|
||||||
|
options,
|
||||||
|
state: route,
|
||||||
|
navigation: childNavigation,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigatorView
|
<NavigatorView
|
||||||
{...this.props}
|
screenProps={screenProps}
|
||||||
screenProps={this.props.screenProps}
|
navigation={navigation}
|
||||||
navigation={this.props.navigation}
|
|
||||||
navigationConfig={navigationConfig}
|
navigationConfig={navigationConfig}
|
||||||
descriptors={descriptors}
|
descriptors={descriptors}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Navigator;
|
||||||
return polyfill(Navigator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createNavigator;
|
export default createNavigator;
|
||||||
|
|||||||
@@ -1,37 +1,28 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
import createKeyboardAwareNavigator from './createKeyboardAwareNavigator';
|
|
||||||
import createNavigator from './createNavigator';
|
import createNavigator from './createNavigator';
|
||||||
import StackView from '../views/StackView/StackView';
|
import StackView from '../views/StackView/StackView2';
|
||||||
import StackRouter from '../routers/StackRouter';
|
import StackRouter from '../routers/StackRouter';
|
||||||
|
|
||||||
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
||||||
const {
|
const {
|
||||||
initialRouteKey,
|
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
initialRouteParams,
|
initialRouteParams,
|
||||||
paths,
|
paths,
|
||||||
navigationOptions,
|
navigationOptions,
|
||||||
disableKeyboardHandling,
|
|
||||||
getCustomActionCreators,
|
|
||||||
} = stackConfig;
|
} = stackConfig;
|
||||||
|
|
||||||
const stackRouterConfig = {
|
const stackRouterConfig = {
|
||||||
initialRouteKey,
|
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
initialRouteParams,
|
initialRouteParams,
|
||||||
paths,
|
paths,
|
||||||
navigationOptions,
|
navigationOptions,
|
||||||
getCustomActionCreators,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = StackRouter(routeConfigMap, stackRouterConfig);
|
const router = StackRouter(routeConfigMap, stackRouterConfig);
|
||||||
|
|
||||||
// Create a navigator with StackView as the view
|
// Create a navigator with StackView as the view
|
||||||
let Navigator = createNavigator(StackView, router, stackConfig);
|
const Navigator = createNavigator(StackView, router, stackConfig);
|
||||||
if (!disableKeyboardHandling) {
|
|
||||||
Navigator = createKeyboardAwareNavigator(Navigator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
|
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
|
||||||
return createNavigationContainer(Navigator);
|
return createNavigationContainer(Navigator);
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
|
||||||
import createNavigator from '../navigators/createNavigator';
|
|
||||||
import SwitchRouter from '../routers/SwitchRouter';
|
|
||||||
import SwitchView from '../views/SwitchView/SwitchView';
|
|
||||||
|
|
||||||
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
|
||||||
const router = SwitchRouter(routeConfigMap, switchConfig);
|
|
||||||
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
|
||||||
return createNavigationContainer(Navigator);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createSwitchNavigator;
|
|
||||||
64
src/navigators/createTabNavigator.js
Normal file
64
src/navigators/createTabNavigator.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
import createNavigator from './createNavigator';
|
||||||
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
|
import TabRouter from '../routers/TabRouter';
|
||||||
|
import TabView from '../views/TabView/TabView';
|
||||||
|
import TabBarTop from '../views/TabView/TabBarTop';
|
||||||
|
import TabBarBottom from '../views/TabView/TabBarBottom';
|
||||||
|
|
||||||
|
const TabNavigator = (routeConfigs, config = {}) => {
|
||||||
|
// Use the look native to the platform by default
|
||||||
|
const tabsConfig = { ...TabNavigator.Presets.Default, ...config };
|
||||||
|
|
||||||
|
const router = TabRouter(routeConfigs, tabsConfig);
|
||||||
|
|
||||||
|
const navigator = createNavigator(TabView, router, tabsConfig);
|
||||||
|
|
||||||
|
return createNavigationContainer(navigator);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Presets = {
|
||||||
|
iOSBottomTabs: {
|
||||||
|
tabBarComponent: TabBarBottom,
|
||||||
|
tabBarPosition: 'bottom',
|
||||||
|
swipeEnabled: false,
|
||||||
|
animationEnabled: false,
|
||||||
|
initialLayout: undefined,
|
||||||
|
},
|
||||||
|
AndroidTopTabs: {
|
||||||
|
tabBarComponent: TabBarTop,
|
||||||
|
tabBarPosition: 'top',
|
||||||
|
swipeEnabled: true,
|
||||||
|
animationEnabled: true,
|
||||||
|
initialLayout: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use these to get Android-style top tabs even on iOS or vice versa.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```
|
||||||
|
* const HomeScreenTabNavigator = TabNavigator({
|
||||||
|
* Chat: {
|
||||||
|
* screen: ChatScreen,
|
||||||
|
* },
|
||||||
|
* ...
|
||||||
|
* }, {
|
||||||
|
* ...TabNavigator.Presets.AndroidTopTabs,
|
||||||
|
* tabBarOptions: {
|
||||||
|
* ...
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*```
|
||||||
|
*/
|
||||||
|
TabNavigator.Presets = {
|
||||||
|
iOSBottomTabs: Presets.iOSBottomTabs,
|
||||||
|
AndroidTopTabs: Presets.AndroidTopTabs,
|
||||||
|
Default:
|
||||||
|
Platform.OS === 'ios' ? Presets.iOSBottomTabs : Presets.AndroidTopTabs,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabNavigator;
|
||||||
102
src/react-navigation.js
vendored
102
src/react-navigation.js
vendored
@@ -8,71 +8,25 @@ module.exports = {
|
|||||||
get StateUtils() {
|
get StateUtils() {
|
||||||
return require('./StateUtils').default;
|
return require('./StateUtils').default;
|
||||||
},
|
},
|
||||||
|
get addNavigationHelpers() {
|
||||||
|
return require('./addNavigationHelpers').default;
|
||||||
|
},
|
||||||
|
get NavigationActions() {
|
||||||
|
return require('./NavigationActions').default;
|
||||||
|
},
|
||||||
|
|
||||||
// Navigators
|
// Navigators
|
||||||
get createNavigator() {
|
get createNavigator() {
|
||||||
return require('./navigators/createNavigator').default;
|
return require('./navigators/createNavigator').default;
|
||||||
},
|
},
|
||||||
get createStackNavigator() {
|
|
||||||
return require('./navigators/createStackNavigator').default;
|
|
||||||
},
|
|
||||||
get StackNavigator() {
|
get StackNavigator() {
|
||||||
console.warn(
|
|
||||||
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
|
|
||||||
);
|
|
||||||
return require('./navigators/createStackNavigator').default;
|
return require('./navigators/createStackNavigator').default;
|
||||||
},
|
},
|
||||||
get createSwitchNavigator() {
|
|
||||||
return require('./navigators/createSwitchNavigator').default;
|
|
||||||
},
|
|
||||||
get SwitchNavigator() {
|
|
||||||
console.warn(
|
|
||||||
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
|
|
||||||
);
|
|
||||||
return require('./navigators/createSwitchNavigator').default;
|
|
||||||
},
|
|
||||||
get createDrawerNavigator() {
|
|
||||||
return require('./navigators/createDrawerNavigator').default;
|
|
||||||
},
|
|
||||||
get DrawerNavigator() {
|
|
||||||
console.warn(
|
|
||||||
'The DrawerNavigator function name is deprecated, please use createDrawerNavigator instead'
|
|
||||||
);
|
|
||||||
return require('./navigators/createDrawerNavigator').default;
|
|
||||||
},
|
|
||||||
get createTabNavigator() {
|
|
||||||
console.warn(
|
|
||||||
'createTabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
|
||||||
);
|
|
||||||
return require('react-navigation-deprecated-tab-navigator')
|
|
||||||
.createTabNavigator;
|
|
||||||
},
|
|
||||||
get TabNavigator() {
|
get TabNavigator() {
|
||||||
console.warn(
|
return require('./navigators/createTabNavigator').default;
|
||||||
'TabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
|
||||||
);
|
|
||||||
return require('react-navigation-deprecated-tab-navigator')
|
|
||||||
.createTabNavigator;
|
|
||||||
},
|
},
|
||||||
get createBottomTabNavigator() {
|
get DrawerNavigator() {
|
||||||
return require('react-navigation-tabs').createBottomTabNavigator;
|
return require('./navigators/DrawerNavigator').default;
|
||||||
},
|
|
||||||
get createMaterialTopTabNavigator() {
|
|
||||||
return require('react-navigation-tabs').createMaterialTopTabNavigator;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
get NavigationActions() {
|
|
||||||
return require('./NavigationActions').default;
|
|
||||||
},
|
|
||||||
get StackActions() {
|
|
||||||
return require('./routers/StackActions').default;
|
|
||||||
},
|
|
||||||
get DrawerActions() {
|
|
||||||
return require('./routers/DrawerActions').default;
|
|
||||||
},
|
|
||||||
get getNavigationActionCreators() {
|
|
||||||
return require('./routers/getNavigationActionCreators').default;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Routers
|
// Routers
|
||||||
@@ -82,12 +36,6 @@ module.exports = {
|
|||||||
get TabRouter() {
|
get TabRouter() {
|
||||||
return require('./routers/TabRouter').default;
|
return require('./routers/TabRouter').default;
|
||||||
},
|
},
|
||||||
get DrawerRouter() {
|
|
||||||
return require('./routers/DrawerRouter').default;
|
|
||||||
},
|
|
||||||
get SwitchRouter() {
|
|
||||||
return require('./routers/SwitchRouter').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
get Transitioner() {
|
get Transitioner() {
|
||||||
@@ -102,12 +50,6 @@ module.exports = {
|
|||||||
get SafeAreaView() {
|
get SafeAreaView() {
|
||||||
return require('react-native-safe-area-view').default;
|
return require('react-native-safe-area-view').default;
|
||||||
},
|
},
|
||||||
get SceneView() {
|
|
||||||
return require('./views/SceneView').default;
|
|
||||||
},
|
|
||||||
get ResourceSavingSceneView() {
|
|
||||||
return require('./views/ResourceSavingSceneView').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
get Header() {
|
get Header() {
|
||||||
@@ -127,33 +69,16 @@ module.exports = {
|
|||||||
get DrawerItems() {
|
get DrawerItems() {
|
||||||
return require('./views/Drawer/DrawerNavigatorItems').default;
|
return require('./views/Drawer/DrawerNavigatorItems').default;
|
||||||
},
|
},
|
||||||
get DrawerSidebar() {
|
|
||||||
return require('./views/Drawer/DrawerSidebar').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// TabView
|
// TabView
|
||||||
get TabView() {
|
get TabView() {
|
||||||
console.warn(
|
return require('./views/TabView/TabView').default;
|
||||||
'TabView is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
|
||||||
);
|
|
||||||
return require('react-navigation-deprecated-tab-navigator').TabView;
|
|
||||||
},
|
},
|
||||||
get TabBarTop() {
|
get TabBarTop() {
|
||||||
console.warn(
|
return require('./views/TabView/TabBarTop').default;
|
||||||
'TabBarTop is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
|
||||||
);
|
|
||||||
return require('react-navigation-deprecated-tab-navigator').TabBarTop;
|
|
||||||
},
|
},
|
||||||
get TabBarBottom() {
|
get TabBarBottom() {
|
||||||
console.warn(
|
return require('./views/TabView/TabBarBottom').default;
|
||||||
'TabBarBottom is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
|
||||||
);
|
|
||||||
return require('react-navigation-deprecated-tab-navigator').TabBarBottom;
|
|
||||||
},
|
|
||||||
|
|
||||||
// SwitchView
|
|
||||||
get SwitchView() {
|
|
||||||
return require('./views/SwitchView/SwitchView').default;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
@@ -163,7 +88,4 @@ module.exports = {
|
|||||||
get withNavigationFocus() {
|
get withNavigationFocus() {
|
||||||
return require('./views/withNavigationFocus').default;
|
return require('./views/withNavigationFocus').default;
|
||||||
},
|
},
|
||||||
get withOrientation() {
|
|
||||||
return require('./views/withOrientation').default;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,26 +8,18 @@ module.exports = {
|
|||||||
get StateUtils() {
|
get StateUtils() {
|
||||||
return require('./StateUtils').default;
|
return require('./StateUtils').default;
|
||||||
},
|
},
|
||||||
|
get addNavigationHelpers() {
|
||||||
|
return require('./addNavigationHelpers').default;
|
||||||
|
},
|
||||||
|
get NavigationActions() {
|
||||||
|
return require('./NavigationActions').default;
|
||||||
|
},
|
||||||
|
|
||||||
// Navigators
|
// Navigators
|
||||||
get createNavigator() {
|
get createNavigator() {
|
||||||
return require('./navigators/createNavigator').default;
|
return require('./navigators/createNavigator').default;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Actions
|
|
||||||
get NavigationActions() {
|
|
||||||
return require('./NavigationActions').default;
|
|
||||||
},
|
|
||||||
get StackActions() {
|
|
||||||
return require('./routers/StackActions').default;
|
|
||||||
},
|
|
||||||
get DrawerActions() {
|
|
||||||
return require('./routers/DrawerActions').default;
|
|
||||||
},
|
|
||||||
get getNavigationActionCreators() {
|
|
||||||
return require('./routers/getNavigationActionCreators').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Routers
|
// Routers
|
||||||
get StackRouter() {
|
get StackRouter() {
|
||||||
return require('./routers/StackRouter').default;
|
return require('./routers/StackRouter').default;
|
||||||
@@ -35,9 +27,6 @@ module.exports = {
|
|||||||
get TabRouter() {
|
get TabRouter() {
|
||||||
return require('./routers/TabRouter').default;
|
return require('./routers/TabRouter').default;
|
||||||
},
|
},
|
||||||
get SwitchRouter() {
|
|
||||||
return require('./routers/SwitchRouter').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
get withNavigation() {
|
get withNavigation() {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
|
|
||||||
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
|
|
||||||
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
|
|
||||||
|
|
||||||
const openDrawer = payload => ({
|
|
||||||
type: OPEN_DRAWER,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeDrawer = payload => ({
|
|
||||||
type: CLOSE_DRAWER,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const toggleDrawer = payload => ({
|
|
||||||
type: TOGGLE_DRAWER,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
OPEN_DRAWER,
|
|
||||||
CLOSE_DRAWER,
|
|
||||||
TOGGLE_DRAWER,
|
|
||||||
|
|
||||||
openDrawer,
|
|
||||||
closeDrawer,
|
|
||||||
toggleDrawer,
|
|
||||||
};
|
|
||||||
@@ -1,97 +1,54 @@
|
|||||||
import SwitchRouter from './SwitchRouter';
|
import invariant from '../utils/invariant';
|
||||||
|
import TabRouter from './TabRouter';
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
import withDefaultValue from '../utils/withDefaultValue';
|
|
||||||
|
|
||||||
import DrawerActions from './DrawerActions';
|
|
||||||
|
|
||||||
const getActiveRouteKey = route => {
|
|
||||||
if (route.routes && route.routes[route.index]) {
|
|
||||||
return getActiveRouteKey(route.routes[route.index]);
|
|
||||||
}
|
|
||||||
return route.key;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (routeConfigs, config = {}) => {
|
export default (routeConfigs, config = {}) => {
|
||||||
config = { ...config };
|
const tabRouter = TabRouter(routeConfigs, config);
|
||||||
config = withDefaultValue(config, 'resetOnBlur', false);
|
|
||||||
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
|
|
||||||
|
|
||||||
const switchRouter = SwitchRouter(routeConfigs, config);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...switchRouter,
|
...tabRouter,
|
||||||
|
|
||||||
getActionCreators(route, navStateKey) {
|
getStateForAction(action, lastState) {
|
||||||
return {
|
const state = lastState || {
|
||||||
openDrawer: () => DrawerActions.openDrawer({ key: navStateKey }),
|
...tabRouter.getStateForAction(action, undefined),
|
||||||
closeDrawer: () => DrawerActions.closeDrawer({ key: navStateKey }),
|
isDrawerOpen: false,
|
||||||
toggleDrawer: () => DrawerActions.toggleDrawer({ key: navStateKey }),
|
|
||||||
...switchRouter.getActionCreators(route, navStateKey),
|
|
||||||
};
|
};
|
||||||
},
|
|
||||||
|
|
||||||
getStateForAction(action, state) {
|
// Handle explicit drawer actions
|
||||||
// Set up the initial state if needed
|
if (
|
||||||
if (!state) {
|
state.isDrawerOpen &&
|
||||||
|
action.type === NavigationActions.CLOSE_DRAWER
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
...switchRouter.getStateForAction(action, undefined),
|
...state,
|
||||||
isDrawerOpen: false,
|
isDrawerOpen: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
const isRouterTargeted = action.key == null || action.key === state.key;
|
!state.isDrawerOpen &&
|
||||||
|
action.type === NavigationActions.OPEN_DRAWER
|
||||||
if (isRouterTargeted) {
|
) {
|
||||||
// Only handle actions that are meant for this drawer, as specified by action.key.
|
return {
|
||||||
|
...state,
|
||||||
if (action.type === DrawerActions.CLOSE_DRAWER && state.isDrawerOpen) {
|
isDrawerOpen: true,
|
||||||
return {
|
};
|
||||||
...state,
|
}
|
||||||
isDrawerOpen: false,
|
if (action.type === NavigationActions.TOGGLE_DRAWER) {
|
||||||
};
|
return {
|
||||||
}
|
...state,
|
||||||
|
isDrawerOpen: !state.isDrawerOpen,
|
||||||
if (action.type === DrawerActions.OPEN_DRAWER && !state.isDrawerOpen) {
|
};
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isDrawerOpen: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === DrawerActions.TOGGLE_DRAWER) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isDrawerOpen: !state.isDrawerOpen,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back on switch router for screen switching logic, and handling of child routers
|
// Fall back on tab router for screen switching logic
|
||||||
const switchedState = switchRouter.getStateForAction(action, state);
|
const tabState = tabRouter.getStateForAction(action, state);
|
||||||
|
if (tabState !== null && tabState !== state) {
|
||||||
if (switchedState === null) {
|
// If the tabs have changed, make sure to close the drawer
|
||||||
// The switch router or a child router is attempting to swallow this action. We return null to allow this.
|
return {
|
||||||
return null;
|
...tabState,
|
||||||
|
isDrawerOpen: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has the switch router changed the state?
|
|
||||||
if (switchedState !== state) {
|
|
||||||
if (getActiveRouteKey(switchedState) !== getActiveRouteKey(state)) {
|
|
||||||
// If any navigation has happened, make sure to close the drawer
|
|
||||||
return {
|
|
||||||
...switchedState,
|
|
||||||
isDrawerOpen: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, return the state as defined by the switch router.
|
|
||||||
// The active route key hasn't changed, so this most likely means that a child router has returned
|
|
||||||
// a new state like a param change, but the same key is still active and the drawer will remain open
|
|
||||||
return switchedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
const POP = 'Navigation/POP';
|
|
||||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
|
||||||
const PUSH = 'Navigation/PUSH';
|
|
||||||
const RESET = 'Navigation/RESET';
|
|
||||||
const REPLACE = 'Navigation/REPLACE';
|
|
||||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
|
||||||
|
|
||||||
const pop = payload => ({
|
|
||||||
type: POP,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const popToTop = payload => ({
|
|
||||||
type: POP_TO_TOP,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const push = payload => ({
|
|
||||||
type: PUSH,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reset = payload => ({
|
|
||||||
type: RESET,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const replace = payload => ({
|
|
||||||
type: REPLACE,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const completeTransition = payload => ({
|
|
||||||
type: COMPLETE_TRANSITION,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
POP,
|
|
||||||
POP_TO_TOP,
|
|
||||||
PUSH,
|
|
||||||
RESET,
|
|
||||||
REPLACE,
|
|
||||||
COMPLETE_TRANSITION,
|
|
||||||
|
|
||||||
pop,
|
|
||||||
popToTop,
|
|
||||||
push,
|
|
||||||
reset,
|
|
||||||
replace,
|
|
||||||
completeTransition,
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import pathToRegexp from 'path-to-regexp';
|
import pathToRegexp from 'path-to-regexp';
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import StackActions from './StackActions';
|
|
||||||
import createConfigGetter from './createConfigGetter';
|
import createConfigGetter from './createConfigGetter';
|
||||||
import getScreenForRouteName from './getScreenForRouteName';
|
import getScreenForRouteName from './getScreenForRouteName';
|
||||||
import StateUtils from '../StateUtils';
|
import StateUtils from '../StateUtils';
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
import invariant from '../utils/invariant';
|
import invariant from '../utils/invariant';
|
||||||
import { generateKey } from './KeyGenerator';
|
import { generateKey } from './KeyGenerator';
|
||||||
import getNavigationActionCreators from './getNavigationActionCreators';
|
|
||||||
|
|
||||||
function isEmpty(obj) {
|
function isEmpty(obj) {
|
||||||
if (!obj) return true;
|
if (!obj) return true;
|
||||||
@@ -21,16 +19,10 @@ function isEmpty(obj) {
|
|||||||
function behavesLikePushAction(action) {
|
function behavesLikePushAction(action) {
|
||||||
return (
|
return (
|
||||||
action.type === NavigationActions.NAVIGATE ||
|
action.type === NavigationActions.NAVIGATE ||
|
||||||
action.type === StackActions.PUSH
|
action.type === NavigationActions.PUSH
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
|
||||||
|
|
||||||
function isResetToRootStack(action) {
|
|
||||||
return action.type === StackActions.RESET && action.key === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (routeConfigs, stackConfig = {}) => {
|
export default (routeConfigs, stackConfig = {}) => {
|
||||||
// Fail fast on invalid route definitions
|
// Fail fast on invalid route definitions
|
||||||
validateRouteConfigMap(routeConfigs);
|
validateRouteConfigMap(routeConfigs);
|
||||||
@@ -51,8 +43,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { initialRouteParams } = stackConfig;
|
const { initialRouteParams } = stackConfig;
|
||||||
const getCustomActionCreators =
|
|
||||||
stackConfig.getCustomActionCreators || defaultActionCreators;
|
|
||||||
|
|
||||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
||||||
|
|
||||||
@@ -75,7 +65,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -110,7 +100,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
isTransitioning: false,
|
transitioningFromKey: false,
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [route],
|
routes: [route],
|
||||||
};
|
};
|
||||||
@@ -146,7 +136,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
paths = Object.entries(pathsByRouteNames);
|
paths = Object.entries(pathsByRouteNames);
|
||||||
paths.sort((a, b) => b[1].priority - a[1].priority);
|
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getComponentForState(state) {
|
getComponentForState(state) {
|
||||||
@@ -162,75 +152,16 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
return getScreenForRouteName(routeConfigs, routeName);
|
return getScreenForRouteName(routeConfigs, routeName);
|
||||||
},
|
},
|
||||||
|
|
||||||
getActionCreators(route, navStateKey) {
|
|
||||||
return {
|
|
||||||
...getNavigationActionCreators(route),
|
|
||||||
...getCustomActionCreators(route, navStateKey),
|
|
||||||
pop: (n, params) =>
|
|
||||||
StackActions.pop({
|
|
||||||
n,
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
popToTop: params => StackActions.popToTop(params),
|
|
||||||
push: (routeName, params, action) =>
|
|
||||||
StackActions.push({
|
|
||||||
routeName,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
}),
|
|
||||||
replace: (replaceWith, params, action, newKey) => {
|
|
||||||
if (typeof replaceWith === 'string') {
|
|
||||||
return StackActions.replace({
|
|
||||||
routeName: replaceWith,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
key: route.key,
|
|
||||||
newKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
invariant(
|
|
||||||
typeof replaceWith === 'object',
|
|
||||||
'Must replaceWith an object or a string'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
params == null,
|
|
||||||
'Params must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
action == null,
|
|
||||||
'Child action must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
newKey == null,
|
|
||||||
'Child action must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
return StackActions.replace(replaceWith);
|
|
||||||
},
|
|
||||||
reset: (actions, index) =>
|
|
||||||
StackActions.reset({
|
|
||||||
actions,
|
|
||||||
index: index == null ? actions.length - 1 : index,
|
|
||||||
key: navStateKey,
|
|
||||||
}),
|
|
||||||
dismiss: () =>
|
|
||||||
NavigationActions.back({
|
|
||||||
key: navStateKey,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getStateForAction(action, state) {
|
getStateForAction(action, state) {
|
||||||
// Set up the initial state if needed
|
// Set up the initial state if needed
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return getInitialState(action);
|
return getInitialState(action);
|
||||||
}
|
}
|
||||||
|
const lastRouteKey = state.routes[state.index].key;
|
||||||
|
|
||||||
// Check if the focused child scene wants to handle the action, as long as
|
// Check if the focused child scene wants to handle the action, as long as
|
||||||
// it is not a reset to the root stack
|
// it is not a reset to the root stack
|
||||||
if (
|
if (action.type !== NavigationActions.RESET || action.key !== null) {
|
||||||
!isResetToRootStack(action) &&
|
|
||||||
action.type !== NavigationActions.NAVIGATE
|
|
||||||
) {
|
|
||||||
const keyIndex = action.key
|
const keyIndex = action.key
|
||||||
? StateUtils.indexOf(state, action.key)
|
? StateUtils.indexOf(state, action.key)
|
||||||
: -1;
|
: -1;
|
||||||
@@ -250,38 +181,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
return StateUtils.replaceAt(state, childRoute.key, route);
|
return StateUtils.replaceAt(state, childRoute.key, route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (action.type === NavigationActions.NAVIGATE) {
|
|
||||||
// Traverse routes from the top of the stack to the bottom, so the
|
|
||||||
// active route has the first opportunity, then the one before it, etc.
|
|
||||||
for (let childRoute of state.routes.slice().reverse()) {
|
|
||||||
let childRouter = childRouters[childRoute.routeName];
|
|
||||||
let childAction =
|
|
||||||
action.routeName === childRoute.routeName && action.action
|
|
||||||
? action.action
|
|
||||||
: action;
|
|
||||||
|
|
||||||
if (childRouter) {
|
|
||||||
const nextRouteState = childRouter.getStateForAction(
|
|
||||||
childAction,
|
|
||||||
childRoute
|
|
||||||
);
|
|
||||||
|
|
||||||
if (nextRouteState === null || nextRouteState !== childRoute) {
|
|
||||||
const newState = StateUtils.replaceAndPrune(
|
|
||||||
state,
|
|
||||||
nextRouteState ? nextRouteState.key : childRoute.key,
|
|
||||||
nextRouteState ? nextRouteState : childRoute
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...newState,
|
|
||||||
isTransitioning:
|
|
||||||
state.index !== newState.index
|
|
||||||
? action.immediate !== true
|
|
||||||
: state.isTransitioning,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle explicit push navigation action. This must happen after the
|
// Handle explicit push navigation action. This must happen after the
|
||||||
@@ -294,50 +193,46 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
let route;
|
let route;
|
||||||
|
|
||||||
invariant(
|
invariant(
|
||||||
action.type !== StackActions.PUSH || action.key == null,
|
action.type !== NavigationActions.PUSH || action.key == null,
|
||||||
'StackRouter does not support key on the push action'
|
'StackRouter does not support key on the push action'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Before pushing a new route we first try to find one in the existing route stack
|
// With the navigate action, the key may be provided for pushing, or to navigate back to the key
|
||||||
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
|
if (action.key) {
|
||||||
const lastRouteIndex = state.routes.findIndex(r => {
|
const lastRouteIndex = state.routes.findIndex(
|
||||||
if (action.key) {
|
r => r.key === action.key
|
||||||
return r.key === action.key;
|
);
|
||||||
} else {
|
if (lastRouteIndex !== -1) {
|
||||||
return r.routeName === action.routeName;
|
// If index is unchanged and params are not being set, leave state identity intact
|
||||||
}
|
if (state.index === lastRouteIndex && !action.params) {
|
||||||
});
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type !== StackActions.PUSH && lastRouteIndex !== -1) {
|
// Remove the now unused routes at the tail of the routes array
|
||||||
// If index is unchanged and params are not being set, leave state identity intact
|
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
||||||
if (state.index === lastRouteIndex && !action.params) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the now unused routes at the tail of the routes array
|
// Apply params if provided, otherwise leave route identity intact
|
||||||
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
if (action.params) {
|
||||||
|
const route = state.routes.find(r => r.key === action.key);
|
||||||
// Apply params if provided, otherwise leave route identity intact
|
routes[lastRouteIndex] = {
|
||||||
if (action.params) {
|
...route,
|
||||||
const route = state.routes[lastRouteIndex];
|
params: {
|
||||||
routes[lastRouteIndex] = {
|
...route.params,
|
||||||
...route,
|
...action.params,
|
||||||
params: {
|
},
|
||||||
...route.params,
|
};
|
||||||
...action.params,
|
}
|
||||||
},
|
// Return state with new index. Change transitioningFromKey only if index has changed
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
transitioningFromKey:
|
||||||
|
state.index !== lastRouteIndex
|
||||||
|
? action.immediate !== true ? lastRouteKey : null
|
||||||
|
: null,
|
||||||
|
index: lastRouteIndex,
|
||||||
|
routes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Return state with new index. Change isTransitioning only if index has changed
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isTransitioning:
|
|
||||||
state.index !== lastRouteIndex
|
|
||||||
? action.immediate !== true
|
|
||||||
: state.isTransitioning,
|
|
||||||
index: lastRouteIndex,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (childRouter) {
|
if (childRouter) {
|
||||||
@@ -359,14 +254,18 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...StateUtils.push(state, route),
|
...StateUtils.push(state, route),
|
||||||
isTransitioning: action.immediate !== true,
|
transitioningFromKey: action.immediate !== true ? lastRouteKey : null,
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (
|
||||||
action.type === StackActions.PUSH &&
|
action.type === NavigationActions.PUSH &&
|
||||||
childRouters[action.routeName] === undefined
|
childRouters[action.routeName] === undefined
|
||||||
) {
|
) {
|
||||||
// Return the state identity to bubble the action up
|
// If we've made it this far with a push action, we return the
|
||||||
return state;
|
// state with a new identity to prevent the action from bubbling
|
||||||
|
// back up.
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation to other child routers that are not yet pushed
|
// Handle navigation to other child routers that are not yet pushed
|
||||||
@@ -406,7 +305,7 @@ 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.
|
// 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 === StackActions.POP_TO_TOP) {
|
if (action.type === NavigationActions.POP_TO_TOP) {
|
||||||
// Refuse to handle pop to top if a key is given that doesn't correspond
|
// Refuse to handle pop to top if a key is given that doesn't correspond
|
||||||
// to this router
|
// to this router
|
||||||
if (action.key && state.key !== action.key) {
|
if (action.key && state.key !== action.key) {
|
||||||
@@ -415,10 +314,14 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
|
|
||||||
// If we're already at the top, then we return the state with a new
|
// 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.
|
// identity so that the action is handled by this router.
|
||||||
if (state.index > 0) {
|
if (state.index === 0) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isTransitioning: action.immediate !== true,
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastRouteKey: action.immediate !== true ? lastRouteKey : null,
|
||||||
index: 0,
|
index: 0,
|
||||||
routes: [state.routes[0]],
|
routes: [state.routes[0]],
|
||||||
};
|
};
|
||||||
@@ -427,7 +330,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle replace action
|
// Handle replace action
|
||||||
if (action.type === StackActions.REPLACE) {
|
if (action.type === NavigationActions.REPLACE) {
|
||||||
const routeIndex = state.routes.findIndex(r => r.key === action.key);
|
const routeIndex = state.routes.findIndex(r => r.key === action.key);
|
||||||
// Only replace if the key matches one of our routes
|
// Only replace if the key matches one of our routes
|
||||||
if (routeIndex !== -1) {
|
if (routeIndex !== -1) {
|
||||||
@@ -453,13 +356,13 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
|
|
||||||
// Update transitioning state
|
// Update transitioning state
|
||||||
if (
|
if (
|
||||||
action.type === StackActions.COMPLETE_TRANSITION &&
|
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
||||||
(action.key == null || action.key === state.key) &&
|
(action.key == null || action.key === state.key) &&
|
||||||
state.isTransitioning
|
state.transitioningFromKey
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +386,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === StackActions.RESET) {
|
if (action.type === NavigationActions.RESET) {
|
||||||
// Only handle reset actions that are unspecified or match this state key
|
// Only handle reset actions that are unspecified or match this state key
|
||||||
if (action.key != null && action.key != state.key) {
|
if (action.key != null && action.key != state.key) {
|
||||||
// Deliberately use != instead of !== so we can match null with
|
// Deliberately use != instead of !== so we can match null with
|
||||||
@@ -520,11 +423,11 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
action.type === NavigationActions.BACK ||
|
action.type === NavigationActions.BACK ||
|
||||||
action.type === StackActions.POP
|
action.type === NavigationActions.POP
|
||||||
) {
|
) {
|
||||||
const { key, n, immediate } = action;
|
const { key, n, immediate } = action;
|
||||||
let backRouteIndex = state.index;
|
let backRouteIndex = state.index;
|
||||||
if (action.type === StackActions.POP && n != null) {
|
if (action.type === NavigationActions.POP && n != null) {
|
||||||
// determine the index to go back *from*. In this case, n=1 means to go
|
// determine the index to go back *from*. In this case, n=1 means to go
|
||||||
// back from state.index, as if it were a normal "BACK" action
|
// back from state.index, as if it were a normal "BACK" action
|
||||||
backRouteIndex = Math.max(1, state.index - n + 1);
|
backRouteIndex = Math.max(1, state.index - n + 1);
|
||||||
@@ -538,11 +441,17 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
...state,
|
...state,
|
||||||
routes: state.routes.slice(0, backRouteIndex),
|
routes: state.routes.slice(0, backRouteIndex),
|
||||||
index: backRouteIndex - 1,
|
index: backRouteIndex - 1,
|
||||||
isTransitioning: immediate !== true,
|
transitioningFromKey: immediate !== true ? lastRouteKey : null,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
backRouteIndex === 0 &&
|
||||||
|
action.type === NavigationActions.POP
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -573,7 +482,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
if (!pathToResolve) {
|
if (!pathToResolve) {
|
||||||
return NavigationActions.navigate({
|
return NavigationActions.navigate({
|
||||||
routeName: initialRouteName,
|
routeName: initialRouteName,
|
||||||
params: inputParams,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,7 +552,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
if (key.asterisk || !key) {
|
if (key.asterisk || !key) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const nextResult = result || inputParams || {};
|
const nextResult = result || {};
|
||||||
const paramName = key.name;
|
const paramName = key.name;
|
||||||
|
|
||||||
let decodedMatchResult;
|
let decodedMatchResult;
|
||||||
|
|||||||
@@ -1,388 +0,0 @@
|
|||||||
import invariant from '../utils/invariant';
|
|
||||||
import getScreenForRouteName from './getScreenForRouteName';
|
|
||||||
import createConfigGetter from './createConfigGetter';
|
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import StackActions from './StackActions';
|
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
|
||||||
import getNavigationActionCreators from './getNavigationActionCreators';
|
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
|
||||||
|
|
||||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
|
||||||
return [
|
|
||||||
NavigationActions.SET_PARAMS,
|
|
||||||
// Todo: make SwitchRouter not depend on StackActions..
|
|
||||||
StackActions.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 getCustomActionCreators =
|
|
||||||
config.getCustomActionCreators || defaultActionCreators;
|
|
||||||
|
|
||||||
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];
|
|
||||||
if (!paths[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) {
|
|
||||||
if (!prevState) {
|
|
||||||
return 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;
|
|
||||||
},
|
|
||||||
|
|
||||||
getActionCreators(route, stateKey) {
|
|
||||||
return {
|
|
||||||
...getNavigationActionCreators(route),
|
|
||||||
...getCustomActionCreators(route, stateKey),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
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
|
|
||||||
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) {
|
|
||||||
didNavigate = !!order.find((childId, i) => {
|
|
||||||
if (childId === action.routeName) {
|
|
||||||
activeChildIndex = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (didNavigate) {
|
|
||||||
const childState = state.routes[activeChildIndex];
|
|
||||||
const childRouter = childRouters[action.routeName];
|
|
||||||
let newChildState;
|
|
||||||
|
|
||||||
if (action.action) {
|
|
||||||
newChildState = childRouter
|
|
||||||
? childRouter.getStateForAction(action.action, childState)
|
|
||||||
: null;
|
|
||||||
} else if (!action.action && !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,
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
!newChildState &&
|
|
||||||
state.index === activeChildIndex &&
|
|
||||||
prevState
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 { ...state };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
if (!path) {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
order
|
|
||||||
.map(childId => {
|
|
||||||
const parts = path.split('/');
|
|
||||||
const pathToTest = paths[childId];
|
|
||||||
const partsInTestPath = pathToTest.split('/').length;
|
|
||||||
const pathPartsToTest = parts.slice(0, partsInTestPath).join('/');
|
|
||||||
if (pathPartsToTest === pathToTest) {
|
|
||||||
const childRouter = childRouters[childId];
|
|
||||||
const action = NavigationActions.navigate({
|
|
||||||
routeName: childId,
|
|
||||||
});
|
|
||||||
if (childRouter && childRouter.getActionForPathAndParams) {
|
|
||||||
action.action = childRouter.getActionForPathAndParams(
|
|
||||||
parts.slice(partsInTestPath).join('/'),
|
|
||||||
params
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,11 +1,326 @@
|
|||||||
import SwitchRouter from './SwitchRouter';
|
import invariant from '../utils/invariant';
|
||||||
import withDefaultValue from '../utils/withDefaultValue';
|
import getScreenForRouteName from './getScreenForRouteName';
|
||||||
|
import createConfigGetter from './createConfigGetter';
|
||||||
|
|
||||||
|
import NavigationActions from '../NavigationActions';
|
||||||
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
|
|
||||||
|
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||||
|
return [
|
||||||
|
NavigationActions.SET_PARAMS,
|
||||||
|
NavigationActions.COMPLETE_TRANSITION,
|
||||||
|
].includes(actionType);
|
||||||
|
}
|
||||||
|
|
||||||
export default (routeConfigs, config = {}) => {
|
export default (routeConfigs, config = {}) => {
|
||||||
config = { ...config };
|
// Fail fast on invalid route definitions
|
||||||
config = withDefaultValue(config, 'resetOnBlur', false);
|
validateRouteConfigMap(routeConfigs);
|
||||||
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
|
|
||||||
|
|
||||||
const switchRouter = SwitchRouter(routeConfigs, config);
|
const order = config.order || Object.keys(routeConfigs);
|
||||||
return switchRouter;
|
const paths = config.paths || {};
|
||||||
|
const initialRouteParams = config.initialRouteParams;
|
||||||
|
const initialRouteName = config.initialRouteName || order[0];
|
||||||
|
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||||
|
const backBehavior = config.backBehavior || 'initialRoute';
|
||||||
|
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||||
|
const tabRouters = {};
|
||||||
|
order.forEach(routeName => {
|
||||||
|
const routeConfig = routeConfigs[routeName];
|
||||||
|
paths[routeName] =
|
||||||
|
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
||||||
|
tabRouters[routeName] = null;
|
||||||
|
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||||
|
if (screen.router) {
|
||||||
|
tabRouters[routeName] = screen.router;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (initialRouteIndex === -1) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid initialRouteName '${initialRouteName}' for TabRouter. ` +
|
||||||
|
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
getStateForAction(action, inputState) {
|
||||||
|
// Establish a default state
|
||||||
|
let state = inputState;
|
||||||
|
if (!state) {
|
||||||
|
const routes = order.map(routeName => {
|
||||||
|
const params =
|
||||||
|
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||||
|
const tabRouter = tabRouters[routeName];
|
||||||
|
if (tabRouter) {
|
||||||
|
const childAction = NavigationActions.init();
|
||||||
|
return {
|
||||||
|
...tabRouter.getStateForAction(childAction),
|
||||||
|
key: routeName,
|
||||||
|
routeName,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: routeName,
|
||||||
|
routeName,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
state = {
|
||||||
|
routes,
|
||||||
|
index: initialRouteIndex,
|
||||||
|
transitioningFromKey: null,
|
||||||
|
};
|
||||||
|
// console.log(`${order.join('-')}: Initial state`, {state});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === NavigationActions.INIT) {
|
||||||
|
// 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 tab handle it
|
||||||
|
const activeTabLastState = state.routes[state.index];
|
||||||
|
const activeTabRouter = tabRouters[order[state.index]];
|
||||||
|
if (activeTabRouter) {
|
||||||
|
const activeTabState = activeTabRouter.getStateForAction(
|
||||||
|
action,
|
||||||
|
activeTabLastState
|
||||||
|
);
|
||||||
|
if (!activeTabState && inputState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (activeTabState && activeTabState !== activeTabLastState) {
|
||||||
|
const routes = [...state.routes];
|
||||||
|
routes[state.index] = activeTabState;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tab changing. Do this after letting the current tab try to
|
||||||
|
// handle the action, to allow inner tabs to change first
|
||||||
|
let activeTabIndex = state.index;
|
||||||
|
const isBackEligible =
|
||||||
|
action.key == null || action.key === activeTabLastState.key;
|
||||||
|
if (action.type === NavigationActions.BACK) {
|
||||||
|
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||||
|
activeTabIndex = initialRouteIndex;
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let didNavigate = false;
|
||||||
|
if (action.type === NavigationActions.NAVIGATE) {
|
||||||
|
const navigateAction = action;
|
||||||
|
didNavigate = !!order.find((tabId, i) => {
|
||||||
|
if (tabId === navigateAction.routeName) {
|
||||||
|
activeTabIndex = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (didNavigate) {
|
||||||
|
const childState = state.routes[activeTabIndex];
|
||||||
|
let newChildState;
|
||||||
|
|
||||||
|
const tabRouter = tabRouters[action.routeName];
|
||||||
|
|
||||||
|
if (action.action) {
|
||||||
|
newChildState = tabRouter
|
||||||
|
? tabRouter.getStateForAction(action.action, childState)
|
||||||
|
: null;
|
||||||
|
} else if (!tabRouter && action.params) {
|
||||||
|
newChildState = {
|
||||||
|
...childState,
|
||||||
|
params: {
|
||||||
|
...(childState.params || {}),
|
||||||
|
...action.params,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChildState && newChildState !== childState) {
|
||||||
|
const routes = [...state.routes];
|
||||||
|
routes[activeTabIndex] = newChildState;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
index: activeTabIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
...state,
|
||||||
|
routes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activeTabIndex !== state.index) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
index: activeTabIndex,
|
||||||
|
};
|
||||||
|
} else if (didNavigate && !inputState) {
|
||||||
|
return state;
|
||||||
|
} else if (didNavigate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let other tabs handle it and switch to the first tab that returns a new state
|
||||||
|
let index = state.index;
|
||||||
|
let routes = state.routes;
|
||||||
|
order.find((tabId, i) => {
|
||||||
|
const tabRouter = tabRouters[tabId];
|
||||||
|
if (i === index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let tabState = routes[i];
|
||||||
|
if (tabRouter) {
|
||||||
|
// console.log(`${order.join('-')}: Processing child router:`, {action, tabState});
|
||||||
|
tabState = tabRouter.getStateForAction(action, tabState);
|
||||||
|
}
|
||||||
|
if (!tabState) {
|
||||||
|
index = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (tabState !== routes[i]) {
|
||||||
|
routes = [...routes];
|
||||||
|
routes[i] = tabState;
|
||||||
|
index = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
|
||||||
|
|
||||||
|
// Nested routers can be updated after switching tabs 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 {
|
||||||
|
...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 = tabRouters[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(tabId => {
|
||||||
|
const parts = path.split('/');
|
||||||
|
const pathToTest = paths[tabId];
|
||||||
|
if (parts[0] === pathToTest) {
|
||||||
|
const tabRouter = tabRouters[tabId];
|
||||||
|
const action = NavigationActions.navigate({
|
||||||
|
routeName: tabId,
|
||||||
|
});
|
||||||
|
if (tabRouter && tabRouter.getActionForPathAndParams) {
|
||||||
|
action.action = tabRouter.getActionForPathAndParams(
|
||||||
|
parts.slice(1).join('/'),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
} else if (params) {
|
||||||
|
action.params = params;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.find(action => !!action) ||
|
||||||
|
order
|
||||||
|
.map(tabId => {
|
||||||
|
const tabRouter = tabRouters[tabId];
|
||||||
|
return (
|
||||||
|
tabRouter && tabRouter.getActionForPathAndParams(path, params)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.find(action => !!action) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getScreenOptions: createConfigGetter(
|
||||||
|
routeConfigs,
|
||||||
|
config.navigationOptions
|
||||||
|
),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import React from 'react';
|
|||||||
import DrawerRouter from '../DrawerRouter';
|
import DrawerRouter from '../DrawerRouter';
|
||||||
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import DrawerActions from '../../routers/DrawerActions';
|
|
||||||
|
|
||||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
const INIT_ACTION = { type: NavigationActions.INIT };
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ describe('DrawerRouter', () => {
|
|||||||
const state = router.getStateForAction(INIT_ACTION);
|
const state = router.getStateForAction(INIT_ACTION);
|
||||||
const expectedState = {
|
const expectedState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||||
@@ -33,7 +32,7 @@ describe('DrawerRouter', () => {
|
|||||||
);
|
);
|
||||||
const expectedState2 = {
|
const expectedState2 = {
|
||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||||
@@ -45,43 +44,6 @@ describe('DrawerRouter', () => {
|
|||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles initial route navigation', () => {
|
|
||||||
const FooScreen = () => <div />;
|
|
||||||
const BarScreen = () => <div />;
|
|
||||||
const router = DrawerRouter(
|
|
||||||
{
|
|
||||||
Foo: {
|
|
||||||
screen: FooScreen,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: BarScreen,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'Bar' }
|
|
||||||
);
|
|
||||||
const state = router.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
});
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isDrawerOpen: false,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'Foo',
|
|
||||||
params: undefined,
|
|
||||||
routeName: 'Foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Bar',
|
|
||||||
params: undefined,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Drawer opens closes and toggles', () => {
|
test('Drawer opens closes and toggles', () => {
|
||||||
const ScreenA = () => <div />;
|
const ScreenA = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const ScreenB = () => <div />;
|
||||||
@@ -92,85 +54,19 @@ describe('DrawerRouter', () => {
|
|||||||
const state = router.getStateForAction(INIT_ACTION);
|
const state = router.getStateForAction(INIT_ACTION);
|
||||||
expect(state.isDrawerOpen).toEqual(false);
|
expect(state.isDrawerOpen).toEqual(false);
|
||||||
const state2 = router.getStateForAction(
|
const state2 = router.getStateForAction(
|
||||||
{ type: DrawerActions.OPEN_DRAWER },
|
{ type: NavigationActions.OPEN_DRAWER },
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(state2.isDrawerOpen).toEqual(true);
|
expect(state2.isDrawerOpen).toEqual(true);
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{ type: DrawerActions.CLOSE_DRAWER },
|
{ type: NavigationActions.CLOSE_DRAWER },
|
||||||
state2
|
state2
|
||||||
);
|
);
|
||||||
expect(state3.isDrawerOpen).toEqual(false);
|
expect(state3.isDrawerOpen).toEqual(false);
|
||||||
const state4 = router.getStateForAction(
|
const state4 = router.getStateForAction(
|
||||||
{ type: DrawerActions.TOGGLE_DRAWER },
|
{ type: NavigationActions.TOGGLE_DRAWER },
|
||||||
state3
|
state3
|
||||||
);
|
);
|
||||||
expect(state4.isDrawerOpen).toEqual(true);
|
expect(state4.isDrawerOpen).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Drawer opens closes with key targeted', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = DrawerRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
Bar: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction(INIT_ACTION);
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: DrawerActions.OPEN_DRAWER, key: 'wrong' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.isDrawerOpen).toEqual(false);
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: DrawerActions.OPEN_DRAWER, key: state.key },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.isDrawerOpen).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Nested routers bubble up blocked actions', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
ScreenA.router = {
|
|
||||||
getStateForAction(action, lastState) {
|
|
||||||
if (action.type === 'CHILD_ACTION') return null;
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = DrawerRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
Bar: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction(INIT_ACTION);
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction({ type: 'CHILD_ACTION' }, state);
|
|
||||||
expect(state2).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Drawer stays open when child routers return new state', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
ScreenA.router = {
|
|
||||||
getStateForAction(action, lastState = { changed: false }) {
|
|
||||||
if (action.type === 'CHILD_ACTION')
|
|
||||||
return { ...lastState, changed: true };
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const router = DrawerRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = router.getStateForAction(INIT_ACTION);
|
|
||||||
expect(state.isDrawerOpen).toEqual(false);
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: DrawerActions.OPEN_DRAWER, key: state.key },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.isDrawerOpen).toEqual(true);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction({ type: 'CHILD_ACTION' }, state2);
|
|
||||||
expect(state3.isDrawerOpen).toEqual(true);
|
|
||||||
expect(state3.routes[0].changed).toEqual(true);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
/* eslint react/no-multi-comp:0, react/display-name:0 */
|
/* eslint react/no-multi-comp:0 */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import StackRouter from '../StackRouter';
|
import StackRouter from '../StackRouter';
|
||||||
import TabRouter from '../TabRouter';
|
import TabRouter from '../TabRouter';
|
||||||
import SwitchRouter from '../SwitchRouter';
|
|
||||||
import DrawerRouter from '../DrawerRouter';
|
|
||||||
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import DrawerActions from '../DrawerActions';
|
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -18,8 +16,6 @@ beforeEach(() => {
|
|||||||
const ROUTERS = {
|
const ROUTERS = {
|
||||||
TabRouter,
|
TabRouter,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
DrawerRouter,
|
|
||||||
SwitchRouter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dummyEventSubscriber = (name, handler) => ({
|
const dummyEventSubscriber = (name, handler) => ({
|
||||||
@@ -30,7 +26,7 @@ Object.keys(ROUTERS).forEach(routerName => {
|
|||||||
const Router = ROUTERS[routerName];
|
const Router = ROUTERS[routerName];
|
||||||
|
|
||||||
describe(`General router features - ${routerName}`, () => {
|
describe(`General router features - ${routerName}`, () => {
|
||||||
test(`title is configurable using navigationOptions and getScreenOptions - ${routerName}`, () => {
|
test('title is configurable using navigationOptions and getScreenOptions', () => {
|
||||||
class FooView extends React.Component {
|
class FooView extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <div />;
|
return <div />;
|
||||||
@@ -62,132 +58,38 @@ Object.keys(ROUTERS).forEach(routerName => {
|
|||||||
];
|
];
|
||||||
expect(
|
expect(
|
||||||
router.getScreenOptions(
|
router.getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[0],
|
state: routes[0],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual(undefined);
|
).toEqual(undefined);
|
||||||
expect(
|
expect(
|
||||||
router.getScreenOptions(
|
router.getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[1],
|
state: routes[1],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('BarTitle');
|
).toEqual('BarTitle');
|
||||||
expect(
|
expect(
|
||||||
router.getScreenOptions(
|
router.getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[2],
|
state: routes[2],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('Baz-123');
|
).toEqual('Baz-123');
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`set params works in ${routerName}`, () => {
|
|
||||||
class FooView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const router = Router({
|
|
||||||
Foo: { screen: FooView },
|
|
||||||
Bar: { screen: FooView },
|
|
||||||
});
|
|
||||||
|
|
||||||
const initState = router.getStateForAction(NavigationActions.init());
|
|
||||||
const initRoute = initState.routes[initState.index];
|
|
||||||
expect(initRoute.params).toEqual(undefined);
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(
|
|
||||||
NavigationActions.setParams({
|
|
||||||
params: { foo: 42 },
|
|
||||||
key: initRoute.key,
|
|
||||||
}),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(state0.routes[state0.index].params.foo).toEqual(42);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Nested navigate behavior test', () => {
|
|
||||||
const Leaf = () => <div />;
|
|
||||||
|
|
||||||
const First = () => <div />;
|
|
||||||
First.router = StackRouter({
|
|
||||||
First1: Leaf,
|
|
||||||
First2: Leaf,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Second = () => <div />;
|
|
||||||
Second.router = StackRouter({
|
|
||||||
Second1: Leaf,
|
|
||||||
Second2: Leaf,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Main = () => <div />;
|
|
||||||
Main.router = StackRouter({
|
|
||||||
First,
|
|
||||||
Second,
|
|
||||||
});
|
|
||||||
const TestRouter = SwitchRouter({
|
|
||||||
Login: Leaf,
|
|
||||||
Main,
|
|
||||||
});
|
|
||||||
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'First' },
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
expect(state2.index).toEqual(1);
|
|
||||||
expect(state2.routes[1].index).toEqual(0);
|
|
||||||
expect(state2.routes[1].routes[0].index).toEqual(0);
|
|
||||||
|
|
||||||
const state3 = TestRouter.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Second2' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.index).toEqual(1);
|
|
||||||
expect(state3.routes[1].index).toEqual(1); // second
|
|
||||||
expect(state3.routes[1].routes[1].index).toEqual(1); //second.second2
|
|
||||||
|
|
||||||
const state4 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'First',
|
|
||||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First2' },
|
|
||||||
},
|
|
||||||
state3,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
expect(state4.index).toEqual(1); // main
|
|
||||||
expect(state4.routes[1].index).toEqual(0); // first
|
|
||||||
expect(state4.routes[1].routes[0].index).toEqual(1); // first2
|
|
||||||
|
|
||||||
const state5 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'First',
|
|
||||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First1' },
|
|
||||||
},
|
|
||||||
state3 // second.second2 is active on state3
|
|
||||||
);
|
|
||||||
expect(state5.index).toEqual(1); // main
|
|
||||||
expect(state5.routes[1].index).toEqual(0); // first
|
|
||||||
expect(state5.routes[1].routes[0].index).toEqual(0); // first.first1
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles no-op actions with tabs within stack router', () => {
|
test('Handles no-op actions with tabs within stack router', () => {
|
||||||
const BarView = () => <div />;
|
const BarView = () => <div />;
|
||||||
const FooTabNavigator = () => <div />;
|
const FooTabNavigator = () => <div />;
|
||||||
@@ -233,7 +135,7 @@ test('Handles deep action', () => {
|
|||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||||
const expectedState = {
|
const expectedState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: false,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -256,70 +158,6 @@ test('Handles deep action', () => {
|
|||||||
expect(state2 && state2.routes[1].index).toEqual(1);
|
expect(state2 && state2.routes[1].index).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles the navigate action with params', () => {
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
Boo: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: { screen: () => <div /> },
|
|
||||||
Bar: { screen: FooTabNavigator },
|
|
||||||
});
|
|
||||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
immediate: true,
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
|
|
||||||
expect(state2 && state2.routes[1].routes).toEqual([
|
|
||||||
{
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the setParams action', () => {
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: { screen: FooTabNavigator },
|
|
||||||
Bar: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'foobar' },
|
|
||||||
key: 'Baz',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.index).toEqual(0);
|
|
||||||
expect(state2 && state2.routes[0].routes).toEqual([
|
|
||||||
{
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
params: { name: 'foobar' },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Supports lazily-evaluated getScreen', () => {
|
test('Supports lazily-evaluated getScreen', () => {
|
||||||
const BarView = () => <div />;
|
const BarView = () => <div />;
|
||||||
const FooTabNavigator = () => <div />;
|
const FooTabNavigator = () => <div />;
|
||||||
@@ -412,112 +250,3 @@ test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION
|
|||||||
expect(stateAfterCompleteTransition.index).toEqual(1);
|
expect(stateAfterCompleteTransition.index).toEqual(1);
|
||||||
expect(stateAfterSetParams.index).toEqual(1);
|
expect(stateAfterSetParams.index).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
|
||||||
const PlainScreen = () => <div />;
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenB.router = StackRouter({
|
|
||||||
Baz: { screen: PlainScreen },
|
|
||||||
Zoo: { screen: PlainScreen },
|
|
||||||
});
|
|
||||||
ScreenA.router = StackRouter({
|
|
||||||
Bar: { screen: PlainScreen },
|
|
||||||
Boo: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const TestRouter = TabRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
});
|
|
||||||
const screenApreState = {
|
|
||||||
index: 0,
|
|
||||||
key: 'Init',
|
|
||||||
isTransitioning: false,
|
|
||||||
routeName: 'Foo',
|
|
||||||
routes: [{ key: 'Init', routeName: 'Bar' }],
|
|
||||||
};
|
|
||||||
const preState = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [screenApreState],
|
|
||||||
};
|
|
||||||
|
|
||||||
const comparable = state => {
|
|
||||||
let result = {};
|
|
||||||
if (typeof state.routeName === 'string') {
|
|
||||||
result = { ...result, routeName: state.routeName };
|
|
||||||
}
|
|
||||||
if (state.routes instanceof Array) {
|
|
||||||
result = {
|
|
||||||
...result,
|
|
||||||
routes: state.routes.map(comparable),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = NavigationActions.navigate({
|
|
||||||
routeName: 'Boo',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedState = ScreenA.router.getStateForAction(
|
|
||||||
action,
|
|
||||||
screenApreState
|
|
||||||
);
|
|
||||||
const state = TestRouter.getStateForAction(action, preState);
|
|
||||||
const innerState = state ? state.routes[0] : state;
|
|
||||||
|
|
||||||
expect(expectedState && comparable(expectedState)).toEqual(
|
|
||||||
innerState && comparable(innerState)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('DrawerRouter will close drawer on child navigaton, not on child param changes', () => {
|
|
||||||
class FooView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const BarRouter = SwitchRouter({
|
|
||||||
Qux: FooView,
|
|
||||||
Quo: FooView,
|
|
||||||
});
|
|
||||||
class BarView extends React.Component {
|
|
||||||
static router = BarRouter;
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const router = DrawerRouter({
|
|
||||||
Bar: BarView,
|
|
||||||
Foo: FooView,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emptyState = router.getStateForAction(NavigationActions.init());
|
|
||||||
const initState = router.getStateForAction(
|
|
||||||
DrawerActions.openDrawer(),
|
|
||||||
emptyState
|
|
||||||
);
|
|
||||||
expect(initState.isDrawerOpen).toBe(true);
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'Quo' }),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(state0.isDrawerOpen).toBe(false);
|
|
||||||
|
|
||||||
const initSwitchState = initState.routes[initState.index];
|
|
||||||
const initQuxState = initSwitchState.routes[initSwitchState.index];
|
|
||||||
|
|
||||||
const state1 = router.getStateForAction(
|
|
||||||
NavigationActions.setParams({
|
|
||||||
key: initQuxState.key,
|
|
||||||
params: { foo: 'bar' },
|
|
||||||
}),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(state1.isDrawerOpen).toBe(true);
|
|
||||||
const state1switchState = state1.routes[state1.index];
|
|
||||||
const state1quxState = state1switchState.routes[state1switchState.index];
|
|
||||||
expect(state1quxState.params.foo).toEqual('bar');
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import StackRouter from '../StackRouter';
|
import StackRouter from '../StackRouter';
|
||||||
import StackActions from '../StackActions';
|
import TabRouter from '../TabRouter';
|
||||||
import NavigationActions from '../../NavigationActions';
|
|
||||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||||
|
|
||||||
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_TESTING_ONLY_normalize_keys();
|
_TESTING_ONLY_normalize_keys();
|
||||||
});
|
});
|
||||||
@@ -91,7 +92,7 @@ describe('StackRouter', () => {
|
|||||||
expect(
|
expect(
|
||||||
router.getComponentForState({
|
router.getComponentForState({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'a', routeName: 'foo' },
|
{ key: 'a', routeName: 'foo' },
|
||||||
{ key: 'b', routeName: 'bar' },
|
{ key: 'b', routeName: 'bar' },
|
||||||
@@ -102,7 +103,7 @@ describe('StackRouter', () => {
|
|||||||
expect(
|
expect(
|
||||||
router.getComponentForState({
|
router.getComponentForState({
|
||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'a', routeName: 'foo' },
|
{ key: 'a', routeName: 'foo' },
|
||||||
{ key: 'b', routeName: 'bar' },
|
{ key: 'b', routeName: 'bar' },
|
||||||
@@ -126,7 +127,7 @@ describe('StackRouter', () => {
|
|||||||
expect(
|
expect(
|
||||||
router.getComponentForState({
|
router.getComponentForState({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'a', routeName: 'foo' },
|
{ key: 'a', routeName: 'foo' },
|
||||||
{ key: 'b', routeName: 'bar' },
|
{ key: 'b', routeName: 'bar' },
|
||||||
@@ -137,7 +138,7 @@ describe('StackRouter', () => {
|
|||||||
expect(
|
expect(
|
||||||
router.getComponentForState({
|
router.getComponentForState({
|
||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'a', routeName: 'foo' },
|
{ key: 'a', routeName: 'foo' },
|
||||||
{ key: 'b', routeName: 'bar' },
|
{ key: 'b', routeName: 'bar' },
|
||||||
@@ -352,7 +353,7 @@ describe('StackRouter', () => {
|
|||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
expect(initState).toEqual({
|
expect(initState).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [{ key: 'id-0', routeName: 'foo' }],
|
routes: [{ key: 'id-0', routeName: 'foo' }],
|
||||||
});
|
});
|
||||||
@@ -365,38 +366,7 @@ describe('StackRouter', () => {
|
|||||||
expect(pushedState.routes[1].routes[1].routeName).toEqual('qux');
|
expect(pushedState.routes[1].routes[1].routeName).toEqual('qux');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('push bubbles up', () => {
|
test('pop does not bubble up', () => {
|
||||||
const ChildNavigator = () => <div />;
|
|
||||||
ChildNavigator.router = StackRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
Qux: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: { screen: () => <div /> },
|
|
||||||
Bar: { screen: ChildNavigator },
|
|
||||||
Bad: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
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: StackActions.PUSH,
|
|
||||||
routeName: 'Bad',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3 && state3.index).toEqual(2);
|
|
||||||
expect(state3 && state3.routes.length).toEqual(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pop bubbles up', () => {
|
|
||||||
const ChildNavigator = () => <div />;
|
const ChildNavigator = () => <div />;
|
||||||
ChildNavigator.router = StackRouter({
|
ChildNavigator.router = StackRouter({
|
||||||
Baz: { screen: () => <div /> },
|
Baz: { screen: () => <div /> },
|
||||||
@@ -419,14 +389,46 @@ describe('StackRouter', () => {
|
|||||||
const barKey = state2.routes[1].routes[0].key;
|
const barKey = state2.routes[1].routes[0].key;
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.POP,
|
type: NavigationActions.POP,
|
||||||
},
|
},
|
||||||
state2
|
state2
|
||||||
);
|
);
|
||||||
expect(state3 && state3.index).toEqual(0);
|
expect(state3 && state3.index).toEqual(1);
|
||||||
|
expect(state3 && state3.routes[1].index).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('popToTop bubbles up', () => {
|
test('push does not bubble up', () => {
|
||||||
|
const ChildNavigator = () => <div />;
|
||||||
|
ChildNavigator.router = StackRouter({
|
||||||
|
Baz: { screen: () => <div /> },
|
||||||
|
Qux: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
const router = StackRouter({
|
||||||
|
Foo: { screen: () => <div /> },
|
||||||
|
Bar: { screen: ChildNavigator },
|
||||||
|
Bad: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
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.PUSH,
|
||||||
|
routeName: 'Bad',
|
||||||
|
},
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
expect(state3 && state3.index).toEqual(1);
|
||||||
|
expect(state3 && state3.routes.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popToTop does not bubble up', () => {
|
||||||
const ChildNavigator = () => <div />;
|
const ChildNavigator = () => <div />;
|
||||||
ChildNavigator.router = StackRouter({
|
ChildNavigator.router = StackRouter({
|
||||||
Baz: { screen: () => <div /> },
|
Baz: { screen: () => <div /> },
|
||||||
@@ -447,11 +449,12 @@ describe('StackRouter', () => {
|
|||||||
const barKey = state2.routes[1].routes[0].key;
|
const barKey = state2.routes[1].routes[0].key;
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.POP_TO_TOP,
|
type: NavigationActions.POP_TO_TOP,
|
||||||
},
|
},
|
||||||
state2
|
state2
|
||||||
);
|
);
|
||||||
expect(state3 && state3.index).toEqual(0);
|
expect(state3 && state3.index).toEqual(1);
|
||||||
|
expect(state3 && state3.routes[1].index).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('popToTop targets StackRouter by key if specified', () => {
|
test('popToTop targets StackRouter by key if specified', () => {
|
||||||
@@ -475,7 +478,7 @@ describe('StackRouter', () => {
|
|||||||
const barKey = state2.routes[1].routes[0].key;
|
const barKey = state2.routes[1].routes[0].key;
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.POP_TO_TOP,
|
type: NavigationActions.POP_TO_TOP,
|
||||||
key: state2.key,
|
key: state2.key,
|
||||||
},
|
},
|
||||||
state2
|
state2
|
||||||
@@ -483,44 +486,6 @@ describe('StackRouter', () => {
|
|||||||
expect(state3 && state3.index).toEqual(0);
|
expect(state3 && state3.index).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pop action works as expected', () => {
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
foo: { screen: () => <div /> },
|
|
||||||
bar: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
index: 3,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'A', routeName: 'foo' },
|
|
||||||
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
|
||||||
{ key: 'C', routeName: 'foo' },
|
|
||||||
{ key: 'D', routeName: 'bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const poppedState = TestRouter.getStateForAction(StackActions.pop(), state);
|
|
||||||
expect(poppedState.routes.length).toBe(3);
|
|
||||||
expect(poppedState.index).toBe(2);
|
|
||||||
expect(poppedState.isTransitioning).toBe(true);
|
|
||||||
|
|
||||||
const poppedState2 = TestRouter.getStateForAction(
|
|
||||||
StackActions.pop({ n: 2, immediate: true }),
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(poppedState2.routes.length).toBe(2);
|
|
||||||
expect(poppedState2.index).toBe(1);
|
|
||||||
expect(poppedState2.isTransitioning).toBe(false);
|
|
||||||
|
|
||||||
const poppedState3 = TestRouter.getStateForAction(
|
|
||||||
StackActions.pop({ n: 5 }),
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(poppedState3.routes.length).toBe(1);
|
|
||||||
expect(poppedState3.index).toBe(0);
|
|
||||||
expect(poppedState3.isTransitioning).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popToTop works as expected', () => {
|
test('popToTop works as expected', () => {
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
@@ -529,7 +494,7 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
index: 2,
|
index: 2,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'A', routeName: 'foo' },
|
{ key: 'A', routeName: 'foo' },
|
||||||
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
||||||
@@ -537,79 +502,27 @@ describe('StackRouter', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
const poppedState = TestRouter.getStateForAction(
|
const poppedState = TestRouter.getStateForAction(
|
||||||
StackActions.popToTop(),
|
NavigationActions.popToTop(),
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(poppedState.routes.length).toBe(1);
|
expect(poppedState.routes.length).toBe(1);
|
||||||
expect(poppedState.index).toBe(0);
|
expect(poppedState.index).toBe(0);
|
||||||
expect(poppedState.isTransitioning).toBe(true);
|
expect(poppedState.transitioningFromKey).toBe('C');
|
||||||
const poppedState2 = TestRouter.getStateForAction(
|
const poppedState2 = TestRouter.getStateForAction(
|
||||||
StackActions.popToTop(),
|
NavigationActions.popToTop(),
|
||||||
poppedState
|
poppedState
|
||||||
);
|
);
|
||||||
expect(poppedState).toEqual(poppedState2);
|
expect(poppedState).toEqual(poppedState2);
|
||||||
const poppedImmediatelyState = TestRouter.getStateForAction(
|
const poppedImmediatelyState = TestRouter.getStateForAction(
|
||||||
StackActions.popToTop({ immediate: true }),
|
NavigationActions.popToTop({ immediate: true }),
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(poppedImmediatelyState.routes.length).toBe(1);
|
expect(poppedImmediatelyState.routes.length).toBe(1);
|
||||||
expect(poppedImmediatelyState.index).toBe(0);
|
expect(poppedImmediatelyState.index).toBe(0);
|
||||||
expect(poppedImmediatelyState.isTransitioning).toBe(false);
|
expect(poppedImmediatelyState.transitioningFromKey).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Navigate does not push duplicate routeName', () => {
|
test('Navigate Pushes duplicate routeName', () => {
|
||||||
const TestRouter = StackRouter(
|
|
||||||
{
|
|
||||||
foo: { screen: () => <div /> },
|
|
||||||
bar: { screen: () => <div /> },
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'foo' }
|
|
||||||
);
|
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
|
||||||
const barState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'bar' }),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(barState.index).toEqual(1);
|
|
||||||
expect(barState.routes[1].routeName).toEqual('bar');
|
|
||||||
const navigateOnBarState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'bar' }),
|
|
||||||
barState
|
|
||||||
);
|
|
||||||
expect(navigateOnBarState).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Navigate focuses given routeName if already active in stack', () => {
|
|
||||||
const TestRouter = StackRouter(
|
|
||||||
{
|
|
||||||
foo: { screen: () => <div /> },
|
|
||||||
bar: { screen: () => <div /> },
|
|
||||||
baz: { screen: () => <div /> },
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'foo' }
|
|
||||||
);
|
|
||||||
const initialState = TestRouter.getStateForAction(NavigationActions.init());
|
|
||||||
const fooBarState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'bar' }),
|
|
||||||
initialState
|
|
||||||
);
|
|
||||||
const fooBarBazState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'baz' }),
|
|
||||||
fooBarState
|
|
||||||
);
|
|
||||||
expect(fooBarBazState.index).toEqual(2);
|
|
||||||
expect(fooBarBazState.routes[2].routeName).toEqual('baz');
|
|
||||||
|
|
||||||
const fooState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'foo' }),
|
|
||||||
fooBarBazState
|
|
||||||
);
|
|
||||||
expect(fooState.index).toEqual(0);
|
|
||||||
expect(fooState.routes.length).toEqual(1);
|
|
||||||
expect(fooState.routes[0].routeName).toEqual('foo');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Navigate pushes duplicate routeName if unique key is provided', () => {
|
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
bar: { screen: () => <div /> },
|
bar: { screen: () => <div /> },
|
||||||
@@ -622,7 +535,7 @@ describe('StackRouter', () => {
|
|||||||
expect(pushedState.index).toEqual(1);
|
expect(pushedState.index).toEqual(1);
|
||||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
expect(pushedState.routes[1].routeName).toEqual('bar');
|
||||||
const pushedTwiceState = TestRouter.getStateForAction(
|
const pushedTwiceState = TestRouter.getStateForAction(
|
||||||
NavigationActions.navigate({ routeName: 'bar', key: 'new-unique-key!' }),
|
NavigationActions.navigate({ routeName: 'bar' }),
|
||||||
pushedState
|
pushedState
|
||||||
);
|
);
|
||||||
expect(pushedTwiceState.index).toEqual(2);
|
expect(pushedTwiceState.index).toEqual(2);
|
||||||
@@ -676,90 +589,28 @@ describe('StackRouter', () => {
|
|||||||
NavigationActions.navigate({ routeName: 'foo', key: 'foo' }),
|
NavigationActions.navigate({ routeName: 'foo', key: 'foo' }),
|
||||||
initState
|
initState
|
||||||
);
|
);
|
||||||
expect(pushedState).toEqual(null);
|
expect(pushedState.index).toEqual(0);
|
||||||
|
expect(pushedState.routes[0].routeName).toEqual('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Navigate with key and without it is idempotent', () => {
|
test('Navigate with key is idempotent', () => {
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
bar: { screen: () => <div /> },
|
bar: { screen: () => <div /> },
|
||||||
});
|
});
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
for (key of ['a', null]) {
|
const pushedState = TestRouter.getStateForAction(
|
||||||
const pushedState = TestRouter.getStateForAction(
|
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
|
||||||
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(pushedState.index).toEqual(1);
|
|
||||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
|
||||||
const pushedTwiceState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
|
|
||||||
pushedState
|
|
||||||
);
|
|
||||||
expect(pushedTwiceState).toEqual(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/react-navigation/react-navigation/issues/4063
|
|
||||||
test('Navigate on inactive stackrouter is idempotent', () => {
|
|
||||||
const FirstChildNavigator = () => <div />;
|
|
||||||
FirstChildNavigator.router = StackRouter({
|
|
||||||
First1: () => <div />,
|
|
||||||
First2: () => <div />,
|
|
||||||
});
|
|
||||||
|
|
||||||
const SecondChildNavigator = () => <div />;
|
|
||||||
SecondChildNavigator.router = StackRouter({
|
|
||||||
Second1: () => <div />,
|
|
||||||
Second2: () => <div />,
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = StackRouter({
|
|
||||||
Leaf: () => <div />,
|
|
||||||
First: FirstChildNavigator,
|
|
||||||
Second: SecondChildNavigator,
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
|
|
||||||
const first = router.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'First2' }),
|
|
||||||
state
|
|
||||||
);
|
|
||||||
|
|
||||||
const second = router.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'Second2' }),
|
|
||||||
first
|
|
||||||
);
|
|
||||||
|
|
||||||
const firstAgain = router.getStateForAction(
|
|
||||||
NavigationActions.navigate({
|
|
||||||
routeName: 'First2',
|
|
||||||
params: { debug: true },
|
|
||||||
}),
|
|
||||||
second
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(first.routes.length).toEqual(2);
|
|
||||||
expect(first.index).toEqual(1);
|
|
||||||
expect(second.routes.length).toEqual(3);
|
|
||||||
expect(second.index).toEqual(2);
|
|
||||||
|
|
||||||
expect(firstAgain.index).toEqual(1);
|
|
||||||
expect(firstAgain.routes.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Navigate to current routeName returns null to indicate handled action', () => {
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
foo: { screen: () => <div /> },
|
|
||||||
bar: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
|
||||||
const navigatedState = TestRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({ routeName: 'foo' }),
|
|
||||||
initState
|
initState
|
||||||
);
|
);
|
||||||
expect(navigatedState).toBe(null);
|
expect(pushedState.index).toEqual(1);
|
||||||
|
expect(pushedState.routes[1].routeName).toEqual('bar');
|
||||||
|
const pushedTwiceState = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
|
||||||
|
pushedState
|
||||||
|
);
|
||||||
|
expect(pushedTwiceState.index).toEqual(1);
|
||||||
|
expect(pushedTwiceState.routes[1].routeName).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Push behaves like navigate, except for key', () => {
|
test('Push behaves like navigate, except for key', () => {
|
||||||
@@ -769,39 +620,19 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
const pushedState = TestRouter.getStateForAction(
|
const pushedState = TestRouter.getStateForAction(
|
||||||
StackActions.push({ routeName: 'bar' }),
|
NavigationActions.push({ routeName: 'bar' }),
|
||||||
initState
|
initState
|
||||||
);
|
);
|
||||||
expect(pushedState.index).toEqual(1);
|
expect(pushedState.index).toEqual(1);
|
||||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
expect(pushedState.routes[1].routeName).toEqual('bar');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
TestRouter.getStateForAction(
|
TestRouter.getStateForAction(
|
||||||
{ type: StackActions.PUSH, routeName: 'bar', key: 'a' },
|
{ type: NavigationActions.PUSH, routeName: 'bar', key: 'a' },
|
||||||
pushedState
|
pushedState
|
||||||
);
|
);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Push adds new routes every time', () => {
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
foo: { screen: () => <div /> },
|
|
||||||
bar: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
|
||||||
const pushedState = TestRouter.getStateForAction(
|
|
||||||
StackActions.push({ routeName: 'bar' }),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(pushedState.index).toEqual(1);
|
|
||||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
|
||||||
const secondPushedState = TestRouter.getStateForAction(
|
|
||||||
StackActions.push({ routeName: 'bar' }),
|
|
||||||
pushedState
|
|
||||||
);
|
|
||||||
expect(secondPushedState.index).toEqual(2);
|
|
||||||
expect(secondPushedState.routes[2].routeName).toEqual('bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Navigate backwards with key removes leading routes', () => {
|
test('Navigate backwards with key removes leading routes', () => {
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
@@ -847,7 +678,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state).toEqual({
|
expect(state).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -875,7 +706,7 @@ describe('StackRouter', () => {
|
|||||||
);
|
);
|
||||||
expect(state3).toEqual({
|
expect(state3).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -895,7 +726,7 @@ describe('StackRouter', () => {
|
|||||||
NavigationActions.navigate({ routeName: 'foo' })
|
NavigationActions.navigate({ routeName: 'foo' })
|
||||||
);
|
);
|
||||||
const replacedState = TestRouter.getStateForAction(
|
const replacedState = TestRouter.getStateForAction(
|
||||||
StackActions.replace({
|
NavigationActions.replace({
|
||||||
routeName: 'bar',
|
routeName: 'bar',
|
||||||
params: { meaning: 42 },
|
params: { meaning: 42 },
|
||||||
key: initState.routes[0].key,
|
key: initState.routes[0].key,
|
||||||
@@ -908,7 +739,7 @@ describe('StackRouter', () => {
|
|||||||
expect(replacedState.routes[0].routeName).toEqual('bar');
|
expect(replacedState.routes[0].routeName).toEqual('bar');
|
||||||
expect(replacedState.routes[0].params.meaning).toEqual(42);
|
expect(replacedState.routes[0].params.meaning).toEqual(42);
|
||||||
const replacedState2 = TestRouter.getStateForAction(
|
const replacedState2 = TestRouter.getStateForAction(
|
||||||
StackActions.replace({
|
NavigationActions.replace({
|
||||||
routeName: 'bar',
|
routeName: 'bar',
|
||||||
key: initState.routes[0].key,
|
key: initState.routes[0].key,
|
||||||
newKey: 'wow',
|
newKey: 'wow',
|
||||||
@@ -942,15 +773,15 @@ describe('StackRouter', () => {
|
|||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(state2 && state2.index).toEqual(1);
|
expect(state2 && state2.index).toEqual(1);
|
||||||
expect(state2 && state2.isTransitioning).toEqual(true);
|
expect(state2 && state2.transitioningFromKey).toEqual(state.routes[0].key);
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.COMPLETE_TRANSITION,
|
type: NavigationActions.COMPLETE_TRANSITION,
|
||||||
},
|
},
|
||||||
state2
|
state2
|
||||||
);
|
);
|
||||||
expect(state3 && state3.index).toEqual(1);
|
expect(state3 && state3.index).toEqual(1);
|
||||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
expect(state3 && state3.transitioningFromKey).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handle basic stack logic for components with router', () => {
|
test('Handle basic stack logic for components with router', () => {
|
||||||
@@ -972,7 +803,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state).toEqual({
|
expect(state).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -1000,7 +831,7 @@ describe('StackRouter', () => {
|
|||||||
);
|
);
|
||||||
expect(state3).toEqual({
|
expect(state3).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -1074,7 +905,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state).toEqual({
|
expect(state).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -1098,7 +929,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state).toEqual({
|
expect(state).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -1167,51 +998,15 @@ describe('StackRouter', () => {
|
|||||||
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles the SetParams action for inactive routes', () => {
|
|
||||||
const router = StackRouter(
|
|
||||||
{
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Bar',
|
|
||||||
initialRouteParams: { name: 'Zoo' },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const initialState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'RouteA',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: { name: 'InitialParam', other: 'Unchanged' },
|
|
||||||
},
|
|
||||||
{ key: 'RouteB', routeName: 'Bar', params: {} },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const state = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'NewParam' },
|
|
||||||
key: 'RouteA',
|
|
||||||
},
|
|
||||||
initialState
|
|
||||||
);
|
|
||||||
expect(state.index).toEqual(1);
|
|
||||||
expect(state.routes[0].params).toEqual({
|
|
||||||
name: 'NewParam',
|
|
||||||
other: 'Unchanged',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the setParams action with nested routers', () => {
|
test('Handles the setParams action with nested routers', () => {
|
||||||
const ChildNavigator = () => <div />;
|
const ChildNavigator = () => <div />;
|
||||||
ChildNavigator.router = StackRouter({
|
const GrandChildNavigator = () => <div />;
|
||||||
Baz: { screen: () => <div /> },
|
GrandChildNavigator.router = StackRouter({
|
||||||
|
Quux: { screen: () => <div /> },
|
||||||
|
Corge: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
ChildNavigator.router = TabRouter({
|
||||||
|
Baz: { screen: GrandChildNavigator },
|
||||||
Qux: { screen: () => <div /> },
|
Qux: { screen: () => <div /> },
|
||||||
});
|
});
|
||||||
const router = StackRouter({
|
const router = StackRouter({
|
||||||
@@ -1228,10 +1023,10 @@ describe('StackRouter', () => {
|
|||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(state2 && state2.index).toEqual(0);
|
expect(state2 && state2.index).toEqual(0);
|
||||||
expect(state2 && state2.routes[0].routes).toEqual([
|
expect(state2 && state2.routes[0].routes[0].routes).toEqual([
|
||||||
{
|
{
|
||||||
key: 'id-0',
|
key: 'id-0',
|
||||||
routeName: 'Baz',
|
routeName: 'Quux',
|
||||||
params: { name: 'foobar' },
|
params: { name: 'foobar' },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -1249,7 +1044,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
const state2 = router.getStateForAction(
|
const state2 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.RESET,
|
type: NavigationActions.RESET,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
@@ -1284,7 +1079,7 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
const state1 = router.getStateForAction({ type: NavigationActions.INIT });
|
const state1 = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
const resetAction = {
|
const resetAction = {
|
||||||
type: StackActions.RESET,
|
type: NavigationActions.RESET,
|
||||||
key: 'Bad Key',
|
key: 'Bad Key',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@@ -1317,7 +1112,7 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Handles the reset action with nested Router', () => {
|
test('Handles the reset action with nested Router', () => {
|
||||||
const ChildRouter = StackRouter({
|
const ChildRouter = TabRouter({
|
||||||
baz: {
|
baz: {
|
||||||
screen: () => <div />,
|
screen: () => <div />,
|
||||||
},
|
},
|
||||||
@@ -1337,8 +1132,7 @@ describe('StackRouter', () => {
|
|||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
const state2 = router.getStateForAction(
|
const state2 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.RESET,
|
type: NavigationActions.RESET,
|
||||||
key: null,
|
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
@@ -1390,7 +1184,7 @@ describe('StackRouter', () => {
|
|||||||
);
|
);
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.RESET,
|
type: NavigationActions.RESET,
|
||||||
key: 'Init',
|
key: 'Init',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@@ -1405,7 +1199,7 @@ describe('StackRouter', () => {
|
|||||||
);
|
);
|
||||||
const state4 = router.getStateForAction(
|
const state4 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.RESET,
|
type: NavigationActions.RESET,
|
||||||
key: null,
|
key: null,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@@ -1450,34 +1244,6 @@ describe('StackRouter', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Navigate action to previous nested StackRouter causes isTransitioning start', () => {
|
|
||||||
const ChildNavigator = () => <div />;
|
|
||||||
ChildNavigator.router = StackRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Bar: { screen: ChildNavigator },
|
|
||||||
Foo: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
immediate: true,
|
|
||||||
routeName: 'Foo',
|
|
||||||
},
|
|
||||||
router.getStateForAction({ type: NavigationActions.INIT })
|
|
||||||
);
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.index).toEqual(0);
|
|
||||||
expect(state2.isTransitioning).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the navigate action with params and nested StackRouter as a first action', () => {
|
test('Handles the navigate action with params and nested StackRouter as a first action', () => {
|
||||||
const state = TestStackRouter.getStateForAction({
|
const state = TestStackRouter.getStateForAction({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
@@ -1508,19 +1274,19 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
expect(state).toEqual({
|
expect(state).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'id-2',
|
key: 'id-2',
|
||||||
params: { code: 'test', foo: 'bar' },
|
params: { code: 'test', foo: 'bar' },
|
||||||
routeName: 'main',
|
routeName: 'main',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'id-1',
|
key: 'id-1',
|
||||||
params: { code: 'test', foo: 'bar', id: '4' },
|
params: { code: 'test', foo: 'bar', id: '4' },
|
||||||
routeName: 'profile',
|
routeName: 'profile',
|
||||||
@@ -1567,19 +1333,19 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
expect(state2).toEqual({
|
expect(state2).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'id-5',
|
key: 'id-5',
|
||||||
params: { code: '', foo: 'bar' },
|
params: { code: '', foo: 'bar' },
|
||||||
routeName: 'main',
|
routeName: 'main',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
key: 'id-4',
|
key: 'id-4',
|
||||||
params: { code: '', foo: 'bar', id: '4' },
|
params: { code: '', foo: 'bar', id: '4' },
|
||||||
routeName: 'profile',
|
routeName: 'profile',
|
||||||
@@ -1598,6 +1364,42 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Handles the navigate action with params and nested TabRouter', () => {
|
||||||
|
const ChildNavigator = () => <div />;
|
||||||
|
ChildNavigator.router = TabRouter({
|
||||||
|
Baz: { screen: () => <div /> },
|
||||||
|
Boo: { 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,
|
||||||
|
immediate: true,
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: '42' },
|
||||||
|
},
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
|
||||||
|
expect(state2 && state2.routes[1].routes).toEqual([
|
||||||
|
{
|
||||||
|
key: 'Baz',
|
||||||
|
routeName: 'Baz',
|
||||||
|
params: { foo: '42' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Boo',
|
||||||
|
routeName: 'Boo',
|
||||||
|
params: { foo: '42' },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('Handles empty URIs', () => {
|
test('Handles empty URIs', () => {
|
||||||
const router = StackRouter(
|
const router = StackRouter(
|
||||||
{
|
{
|
||||||
@@ -1646,7 +1448,7 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
index: 0,
|
index: 0,
|
||||||
isTransitioning: false,
|
transitioningFromKey: null,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 1,
|
index: 1,
|
||||||
@@ -1862,136 +1664,21 @@ test('Handles deep navigate completion action', () => {
|
|||||||
},
|
},
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
expect(state2 && state2.index).toEqual(0);
|
expect(state2.index).toEqual(0);
|
||||||
expect(state2 && state2.isTransitioning).toEqual(false);
|
expect(state2.transitioningFromKey).toEqual(null);
|
||||||
expect(state2 && state2.routes[0].index).toEqual(1);
|
expect(state2.routes[0].index).toEqual(1);
|
||||||
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
expect(state2.routes[0].transitioningFromKey).toEqual(
|
||||||
|
state.routes[0].routes[state.routes[0].index].key
|
||||||
|
);
|
||||||
expect(!!key).toEqual(true);
|
expect(!!key).toEqual(true);
|
||||||
const state3 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: StackActions.COMPLETE_TRANSITION,
|
type: NavigationActions.COMPLETE_TRANSITION,
|
||||||
},
|
},
|
||||||
state2
|
state2
|
||||||
);
|
);
|
||||||
expect(state3 && state3.index).toEqual(0);
|
expect(state3.index).toEqual(0);
|
||||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
expect(state3.transitioningFromKey).toEqual(null);
|
||||||
expect(state3 && state3.routes[0].index).toEqual(1);
|
expect(state3.routes[0].index).toEqual(1);
|
||||||
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
expect(state3.routes[0].transitioningFromKey).toEqual(null);
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const NestedStack = () => <div />;
|
|
||||||
let nestedRouter = StackRouter({
|
|
||||||
Foo: Screen,
|
|
||||||
Bar: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
NestedStack.router = nestedRouter;
|
|
||||||
|
|
||||||
let router = StackRouter(
|
|
||||||
{
|
|
||||||
NestedStack,
|
|
||||||
Bar: Screen,
|
|
||||||
Baz: Screen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Baz',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.routes[state3.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
let activeState4 = state4.routes[state4.index];
|
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
|
|
||||||
|
|
||||||
const state5 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state4
|
|
||||||
);
|
|
||||||
let activeState5 = state5.routes[state5.index];
|
|
||||||
expect(activeState5.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const NestedStack = () => <div />;
|
|
||||||
const OtherNestedStack = () => <div />;
|
|
||||||
|
|
||||||
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
|
|
||||||
let otherNestedRouter = StackRouter({ Foo: Screen });
|
|
||||||
NestedStack.router = nestedRouter;
|
|
||||||
OtherNestedStack.router = otherNestedRouter;
|
|
||||||
|
|
||||||
let router = StackRouter(
|
|
||||||
{
|
|
||||||
NestedStack,
|
|
||||||
OtherNestedStack,
|
|
||||||
Bar: Screen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'OtherNestedStack',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'NestedStack',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
let activeState4 = state4.routes[state4.index];
|
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,252 +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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('paths option on SwitchRouter overrides path from route config', () => {
|
|
||||||
const router = getExampleRouter({ paths: { A: 'overridden' } });
|
|
||||||
const action = router.getActionForPathAndParams('overridden', {});
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('A');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('provides correct action for getActionForPathAndParams', () => {
|
|
||||||
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
|
||||||
const action = router.getActionForPathAndParams('A1', { foo: 'bar' });
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('A1');
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('', {});
|
|
||||||
expect(action1.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action1.routeName).toEqual('A');
|
|
||||||
|
|
||||||
const action2 = router.getActionForPathAndParams(null, {});
|
|
||||||
expect(action2.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action2.routeName).toEqual('A');
|
|
||||||
|
|
||||||
const action3 = router.getActionForPathAndParams('great/path', {
|
|
||||||
foo: 'baz',
|
|
||||||
});
|
|
||||||
expect(action3).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B1',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const action4 = router.getActionForPathAndParams('great/path/B2', {
|
|
||||||
foo: 'baz',
|
|
||||||
});
|
|
||||||
expect(action4).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B2',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested switchrouters', () => {
|
|
||||||
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
|
||||||
// if we are focused on Other and navigate to Bar, what should happen?
|
|
||||||
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const NestedSwitch = () => <div />;
|
|
||||||
const OtherNestedSwitch = () => <div />;
|
|
||||||
|
|
||||||
let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
|
|
||||||
let otherNestedRouter = SwitchRouter({ Foo: Screen });
|
|
||||||
NestedSwitch.router = nestedRouter;
|
|
||||||
OtherNestedSwitch.router = otherNestedRouter;
|
|
||||||
|
|
||||||
let router = SwitchRouter(
|
|
||||||
{
|
|
||||||
NestedSwitch,
|
|
||||||
OtherNestedSwitch,
|
|
||||||
Bar: Screen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'OtherNestedSwitch',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'NestedSwitch',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
let activeState4 = state4.routes[state4.index];
|
|
||||||
expect(activeState4.routeName).toEqual('NestedSwitch');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
|
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const MainStack = () => <div />;
|
|
||||||
const LoginStack = () => <div />;
|
|
||||||
MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
|
|
||||||
LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
|
|
||||||
|
|
||||||
let router = SwitchRouter(
|
|
||||||
{
|
|
||||||
Home: Screen,
|
|
||||||
Login: LoginStack,
|
|
||||||
Main: MainStack,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Login',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('Login');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Home',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Home');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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: {
|
|
||||||
screen: StackA,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
B: {
|
|
||||||
screen: StackB,
|
|
||||||
path: 'great/path',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'A',
|
|
||||||
...config,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TabRouter from '../TabRouter';
|
import TabRouter from '../TabRouter';
|
||||||
|
import StackRouter from '../StackRouter';
|
||||||
|
|
||||||
import StackActions from '../../routers/StackActions';
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
|
||||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
const INIT_ACTION = { type: NavigationActions.INIT };
|
||||||
@@ -140,46 +140,6 @@ describe('TabRouter', () => {
|
|||||||
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles the SetParams action for inactive routes', () => {
|
|
||||||
const router = TabRouter(
|
|
||||||
{
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Bar',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const initialState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'RouteA',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: { name: 'InitialParam', other: 'Unchanged' },
|
|
||||||
},
|
|
||||||
{ key: 'RouteB', routeName: 'Bar', params: {} },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const state = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'NewParam' },
|
|
||||||
key: 'RouteA',
|
|
||||||
},
|
|
||||||
initialState
|
|
||||||
);
|
|
||||||
expect(state.index).toEqual(1);
|
|
||||||
expect(state.routes[0].params).toEqual({
|
|
||||||
name: 'NewParam',
|
|
||||||
other: 'Unchanged',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getStateForAction returns null when navigating to same tab', () => {
|
test('getStateForAction returns null when navigating to same tab', () => {
|
||||||
const router = TabRouter(
|
const router = TabRouter(
|
||||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
||||||
@@ -221,7 +181,6 @@ describe('TabRouter', () => {
|
|||||||
const navAction = {
|
const navAction = {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Baz',
|
routeName: 'Baz',
|
||||||
params: { foo: '42' },
|
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Bar',
|
routeName: 'Bar',
|
||||||
@@ -392,7 +351,7 @@ describe('TabRouter', () => {
|
|||||||
});
|
});
|
||||||
const MidNavigator = () => <div />;
|
const MidNavigator = () => <div />;
|
||||||
MidNavigator.router = TabRouter({
|
MidNavigator.router = TabRouter({
|
||||||
Fee: { screen: ChildNavigator0 },
|
Foo: { screen: ChildNavigator0 },
|
||||||
Bar: { screen: ChildNavigator1 },
|
Bar: { screen: ChildNavigator1 },
|
||||||
});
|
});
|
||||||
const router = TabRouter({
|
const router = TabRouter({
|
||||||
@@ -412,8 +371,8 @@ describe('TabRouter', () => {
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
key: 'Fee',
|
key: 'Foo',
|
||||||
routeName: 'Fee',
|
routeName: 'Foo',
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
{ key: 'Boo', routeName: 'Boo' },
|
||||||
@@ -451,8 +410,8 @@ describe('TabRouter', () => {
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
key: 'Fee',
|
key: 'Foo',
|
||||||
routeName: 'Fee',
|
routeName: 'Foo',
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
{ key: 'Boo', routeName: 'Boo' },
|
||||||
@@ -485,10 +444,7 @@ describe('TabRouter', () => {
|
|||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Bar',
|
routeName: 'Bar',
|
||||||
action: {
|
action: { type: NavigationActions.NAVIGATE, routeName: 'Zap' },
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Zap',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(state4).toEqual({
|
expect(state4).toEqual({
|
||||||
@@ -503,8 +459,8 @@ describe('TabRouter', () => {
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
key: 'Fee',
|
key: 'Foo',
|
||||||
routeName: 'Fee',
|
routeName: 'Foo',
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
{ key: 'Boo', routeName: 'Boo' },
|
||||||
@@ -734,15 +690,56 @@ describe('TabRouter', () => {
|
|||||||
expect(state2).toEqual(state0);
|
expect(state2).toEqual(state0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('pop action works as expected', () => {
|
||||||
|
const TestRouter = StackRouter({
|
||||||
|
foo: { screen: () => <div /> },
|
||||||
|
bar: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
index: 3,
|
||||||
|
isTransitioning: false,
|
||||||
|
routes: [
|
||||||
|
{ key: 'A', routeName: 'foo' },
|
||||||
|
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
||||||
|
{ key: 'C', routeName: 'foo' },
|
||||||
|
{ key: 'D', routeName: 'bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const poppedState = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.pop(),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(poppedState.routes.length).toBe(3);
|
||||||
|
expect(poppedState.index).toBe(2);
|
||||||
|
expect(poppedState.isTransitioning).toBe(true);
|
||||||
|
|
||||||
|
const poppedState2 = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.pop({ n: 2, immediate: true }),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(poppedState2.routes.length).toBe(2);
|
||||||
|
expect(poppedState2.index).toBe(1);
|
||||||
|
expect(poppedState2.isTransitioning).toBe(false);
|
||||||
|
|
||||||
|
const poppedState3 = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.pop({ n: 5 }),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(poppedState3.routes.length).toBe(1);
|
||||||
|
expect(poppedState3.index).toBe(0);
|
||||||
|
expect(poppedState3.isTransitioning).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
test('Inner actions are only unpacked if the current tab matches', () => {
|
||||||
const PlainScreen = () => <div />;
|
const PlainScreen = () => <div />;
|
||||||
const ScreenA = () => <div />;
|
const ScreenA = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const ScreenB = () => <div />;
|
||||||
ScreenB.router = TabRouter({
|
ScreenB.router = StackRouter({
|
||||||
Baz: { screen: PlainScreen },
|
Baz: { screen: PlainScreen },
|
||||||
Zoo: { screen: PlainScreen },
|
Zoo: { screen: PlainScreen },
|
||||||
});
|
});
|
||||||
ScreenA.router = TabRouter({
|
ScreenA.router = StackRouter({
|
||||||
Bar: { screen: PlainScreen },
|
Bar: { screen: PlainScreen },
|
||||||
Boo: { screen: ScreenB },
|
Boo: { screen: ScreenB },
|
||||||
});
|
});
|
||||||
@@ -751,10 +748,10 @@ describe('TabRouter', () => {
|
|||||||
});
|
});
|
||||||
const screenApreState = {
|
const screenApreState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
key: 'Foo',
|
key: 'Init',
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routeName: 'Foo',
|
routeName: 'Foo',
|
||||||
routes: [{ key: 'Bar', routeName: 'Bar' }],
|
routes: [{ key: 'Init', routeName: 'Bar' }],
|
||||||
};
|
};
|
||||||
const preState = {
|
const preState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -780,6 +777,7 @@ describe('TabRouter', () => {
|
|||||||
routeName: 'Boo',
|
routeName: 'Boo',
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedState = ScreenA.router.getStateForAction(
|
const expectedState = ScreenA.router.getStateForAction(
|
||||||
action,
|
action,
|
||||||
screenApreState
|
screenApreState
|
||||||
@@ -787,25 +785,8 @@ describe('TabRouter', () => {
|
|||||||
const state = router.getStateForAction(action, preState);
|
const state = router.getStateForAction(action, preState);
|
||||||
const innerState = state ? state.routes[0] : state;
|
const innerState = state ? state.routes[0] : state;
|
||||||
|
|
||||||
expect(innerState.routes[1].index).toEqual(1);
|
|
||||||
expect(expectedState && comparable(expectedState)).toEqual(
|
expect(expectedState && comparable(expectedState)).toEqual(
|
||||||
innerState && comparable(innerState)
|
innerState && comparable(innerState)
|
||||||
);
|
);
|
||||||
|
|
||||||
const noMatchAction = NavigationActions.navigate({
|
|
||||||
routeName: 'Qux',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
|
||||||
});
|
|
||||||
const expectedState2 = ScreenA.router.getStateForAction(
|
|
||||||
noMatchAction,
|
|
||||||
screenApreState
|
|
||||||
);
|
|
||||||
const state2 = router.getStateForAction(noMatchAction, preState);
|
|
||||||
const innerState2 = state2 ? state2.routes[0] : state2;
|
|
||||||
|
|
||||||
expect(innerState2.routes[1].index).toEqual(0);
|
|
||||||
expect(expectedState2 && comparable(expectedState2)).toEqual(
|
|
||||||
innerState2 && comparable(innerState2)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import createConfigGetter from '../createConfigGetter';
|
import createConfigGetter from '../createConfigGetter';
|
||||||
|
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||||
|
|
||||||
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
||||||
remove: () => {},
|
remove: () => {},
|
||||||
@@ -66,81 +67,81 @@ test('should get config for screen', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[0],
|
state: routes[0],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('Welcome anonymous');
|
).toEqual('Welcome anonymous');
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[1],
|
state: routes[1],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('Welcome jane');
|
).toEqual('Welcome jane');
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[0],
|
state: routes[0],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).gesturesEnabled
|
).gesturesEnabled
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[2],
|
state: routes[2],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('Settings!!!');
|
).toEqual('Settings!!!');
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[2],
|
state: routes[2],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).gesturesEnabled
|
).gesturesEnabled
|
||||||
).toEqual(false);
|
).toEqual(false);
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[3],
|
state: routes[3],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).title
|
).title
|
||||||
).toEqual('10 new notifications');
|
).toEqual('10 new notifications');
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[3],
|
state: routes[3],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).gesturesEnabled
|
).gesturesEnabled
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
expect(
|
expect(
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[4],
|
state: routes[4],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
).gesturesEnabled
|
).gesturesEnabled
|
||||||
).toEqual(false);
|
).toEqual(false);
|
||||||
@@ -163,11 +164,11 @@ test('should throw if the route does not exist', () => {
|
|||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getScreenOptions(
|
getScreenOptions(
|
||||||
{
|
addNavigationHelpers({
|
||||||
state: routes[0],
|
state: routes[0],
|
||||||
dispatch: () => false,
|
dispatch: () => false,
|
||||||
addListener: dummyEventSubscriber,
|
addListener: dummyEventSubscriber,
|
||||||
},
|
}),
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
).toThrowError(
|
).toThrowError(
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ export default (routeConfigs, navigatorScreenConfig) => (
|
|||||||
|
|
||||||
const routeConfig = routeConfigs[route.routeName];
|
const routeConfig = routeConfigs[route.routeName];
|
||||||
|
|
||||||
const routeScreenConfig =
|
const routeScreenConfig = routeConfig.navigationOptions;
|
||||||
routeConfig === Component ? null : routeConfig.navigationOptions;
|
|
||||||
const componentScreenConfig = Component.navigationOptions;
|
const componentScreenConfig = Component.navigationOptions;
|
||||||
|
|
||||||
const configOptions = { navigation, screenProps: screenProps || {} };
|
const configOptions = { navigation, screenProps: screenProps || {} };
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
const getNavigationActionCreators = route => {
|
|
||||||
return {
|
|
||||||
goBack: key => {
|
|
||||||
let actualizedKey = key;
|
|
||||||
if (key === undefined && route.key) {
|
|
||||||
invariant(typeof route.key === 'string', 'key should be a string');
|
|
||||||
actualizedKey = route.key;
|
|
||||||
}
|
|
||||||
return NavigationActions.back({ key: actualizedKey });
|
|
||||||
},
|
|
||||||
navigate: (navigateTo, params, action) => {
|
|
||||||
if (typeof navigateTo === 'string') {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: navigateTo,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
invariant(
|
|
||||||
typeof navigateTo === 'object',
|
|
||||||
'Must navigateTo an object or a string'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
params == null,
|
|
||||||
'Params must not be provided to .navigate() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
action == null,
|
|
||||||
'Child action must not be provided to .navigate() when specifying an object'
|
|
||||||
);
|
|
||||||
return NavigationActions.navigate(navigateTo);
|
|
||||||
},
|
|
||||||
setParams: params => {
|
|
||||||
invariant(
|
|
||||||
route.key && typeof route.key === 'string',
|
|
||||||
'setParams cannot be called by root navigator'
|
|
||||||
);
|
|
||||||
return NavigationActions.setParams({ params, key: route.key });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getNavigationActionCreators;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default function docsUrl(path) {
|
|
||||||
return `https://v2.reactnavigation.org/docs/${path}`;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export default (obj, key, defaultValue) => {
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[key] = defaultValue;
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
24
src/views/Drawer/DrawerScreen.js
Normal file
24
src/views/Drawer/DrawerScreen.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import SceneView from '../SceneView';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders the child screen of the drawer.
|
||||||
|
*/
|
||||||
|
class DrawerScreen extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { descriptors, navigation, screenProps } = this.props;
|
||||||
|
const { routes, index } = navigation.state;
|
||||||
|
const descriptor = descriptors[routes[index].key];
|
||||||
|
const Content = descriptor.getComponent();
|
||||||
|
return (
|
||||||
|
<SceneView
|
||||||
|
screenProps={screenProps}
|
||||||
|
component={Content}
|
||||||
|
navigation={descriptor.navigation}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DrawerScreen;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import StackActions from '../../routers/StackActions';
|
|
||||||
import invariant from '../../utils/invariant';
|
import invariant from '../../utils/invariant';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,10 +47,9 @@ class DrawerSidebar extends React.PureComponent {
|
|||||||
_onItemPress = ({ route, focused }) => {
|
_onItemPress = ({ route, focused }) => {
|
||||||
if (!focused) {
|
if (!focused) {
|
||||||
let subAction;
|
let subAction;
|
||||||
// TODO (v3): Revisit and repeal this behavior:
|
|
||||||
// if the child screen is a StackRouter then always navigate to its first screen (see #1914)
|
// if the child screen is a StackRouter then always navigate to its first screen (see #1914)
|
||||||
if (route.index != null && route.index !== 0) {
|
if (route.index !== undefined && route.index !== 0) {
|
||||||
subAction = StackActions.reset({
|
subAction = NavigationActions.reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
actions: [
|
actions: [
|
||||||
NavigationActions.navigate({
|
NavigationActions.navigate({
|
||||||
@@ -59,12 +58,7 @@ class DrawerSidebar extends React.PureComponent {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.props.navigation.dispatch(
|
this.props.navigation.navigate(route.routeName, undefined, subAction);
|
||||||
NavigationActions.navigate({
|
|
||||||
routeName: route.routeName,
|
|
||||||
action: subAction,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,14 @@ import React from 'react';
|
|||||||
import { Dimensions } from 'react-native';
|
import { Dimensions } from 'react-native';
|
||||||
import DrawerLayout from 'react-native-drawer-layout-polyfill';
|
import DrawerLayout from 'react-native-drawer-layout-polyfill';
|
||||||
|
|
||||||
|
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||||
import DrawerSidebar from './DrawerSidebar';
|
import DrawerSidebar from './DrawerSidebar';
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import DrawerActions from '../../routers/DrawerActions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders the drawer.
|
* Component that renders the drawer.
|
||||||
*/
|
*/
|
||||||
export default class DrawerView extends React.PureComponent {
|
export default class DrawerView extends React.PureComponent {
|
||||||
_drawerState = 'closed';
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
drawerWidth:
|
drawerWidth:
|
||||||
typeof this.props.navigationConfig.drawerWidth === 'function'
|
typeof this.props.navigationConfig.drawerWidth === 'function'
|
||||||
@@ -19,7 +17,7 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
: this.props.navigationConfig.drawerWidth,
|
: this.props.navigationConfig.drawerWidth,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentWillMount() {
|
||||||
Dimensions.addEventListener('change', this._updateWidth);
|
Dimensions.addEventListener('change', this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,35 +25,24 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
Dimensions.removeEventListener('change', this._updateWidth);
|
Dimensions.removeEventListener('change', this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { isDrawerOpen } = this.props.navigation.state;
|
const { isDrawerOpen } = nextProps.navigation.state;
|
||||||
const wasDrawerOpen = prevProps.navigation.state.isDrawerOpen;
|
const wasDrawerOpen = this.props.navigation.state.isDrawerOpen;
|
||||||
|
if (isDrawerOpen && !wasDrawerOpen) {
|
||||||
if (isDrawerOpen && !wasDrawerOpen && this._drawerState === 'closed') {
|
|
||||||
this._drawerState = 'opening';
|
|
||||||
this._drawer.openDrawer();
|
this._drawer.openDrawer();
|
||||||
} else if (wasDrawerOpen && !isDrawerOpen && this._drawerState === 'open') {
|
} else if (wasDrawerOpen && !isDrawerOpen) {
|
||||||
this._drawerState = 'closing';
|
|
||||||
this._drawer.closeDrawer();
|
this._drawer.closeDrawer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleDrawerOpen = () => {
|
_handleDrawerOpen = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const { isDrawerOpen } = navigation.state;
|
navigation.dispatch({ type: NavigationActions.OPEN_DRAWER });
|
||||||
if (!isDrawerOpen && this._drawerState === 'closed') {
|
|
||||||
navigation.dispatch({ type: DrawerActions.OPEN_DRAWER });
|
|
||||||
}
|
|
||||||
this._drawerState = 'open';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_handleDrawerClose = () => {
|
_handleDrawerClose = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const { isDrawerOpen } = navigation.state;
|
navigation.dispatch({ type: NavigationActions.CLOSE_DRAWER });
|
||||||
if (isDrawerOpen && this._drawerState === 'open') {
|
|
||||||
navigation.dispatch({ type: DrawerActions.CLOSE_DRAWER });
|
|
||||||
}
|
|
||||||
this._drawerState = 'closed';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateWidth = () => {
|
_updateWidth = () => {
|
||||||
@@ -99,7 +86,6 @@ export default class DrawerView extends React.PureComponent {
|
|||||||
this._drawer = c;
|
this._drawer = c;
|
||||||
}}
|
}}
|
||||||
drawerLockMode={
|
drawerLockMode={
|
||||||
drawerLockMode ||
|
|
||||||
(this.props.screenProps && this.props.screenProps.drawerLockMode) ||
|
(this.props.screenProps && this.props.screenProps.drawerLockMode) ||
|
||||||
this.props.navigationConfig.drawerLockMode
|
this.props.navigationConfig.drawerLockMode
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
I18nManager,
|
|
||||||
ViewPropTypes,
|
ViewPropTypes,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { MaskedViewIOS } from '../../PlatformHelpers';
|
import { MaskedViewIOS } from '../../PlatformHelpers';
|
||||||
@@ -25,15 +24,12 @@ const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
|
|||||||
|
|
||||||
const getAppBarHeight = isLandscape => {
|
const getAppBarHeight = isLandscape => {
|
||||||
return Platform.OS === 'ios'
|
return Platform.OS === 'ios'
|
||||||
? isLandscape && !Platform.isPad
|
? isLandscape && !Platform.isPad ? 32 : 44
|
||||||
? 32
|
|
||||||
: 44
|
|
||||||
: 56;
|
: 56;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
layoutInterpolator: HeaderStyleInterpolator.forLayout,
|
|
||||||
leftInterpolator: HeaderStyleInterpolator.forLeft,
|
leftInterpolator: HeaderStyleInterpolator.forLeft,
|
||||||
leftButtonInterpolator: HeaderStyleInterpolator.forLeftButton,
|
leftButtonInterpolator: HeaderStyleInterpolator.forLeftButton,
|
||||||
leftLabelInterpolator: HeaderStyleInterpolator.forLeftLabel,
|
leftLabelInterpolator: HeaderStyleInterpolator.forLeftLabel,
|
||||||
@@ -147,7 +143,7 @@ class Header extends React.PureComponent {
|
|||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
// Go back on next tick because button ripple effect needs to happen on Android
|
// Go back on next tick because button ripple effect needs to happen on Android
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
props.scene.descriptor.navigation.goBack(props.scene.descriptor.key);
|
this.props.navigation.goBack(props.scene.descriptor.key);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@@ -155,7 +151,7 @@ class Header extends React.PureComponent {
|
|||||||
onPress={goBack}
|
onPress={goBack}
|
||||||
pressColorAndroid={options.headerPressColorAndroid}
|
pressColorAndroid={options.headerPressColorAndroid}
|
||||||
tintColor={options.headerTintColor}
|
tintColor={options.headerTintColor}
|
||||||
backImage={options.headerBackImage}
|
buttonImage={options.headerBackImage}
|
||||||
title={backButtonTitle}
|
title={backButtonTitle}
|
||||||
truncatedTitle={truncatedBackButtonTitle}
|
truncatedTitle={truncatedBackButtonTitle}
|
||||||
titleStyle={options.headerBackTitleStyle}
|
titleStyle={options.headerBackTitleStyle}
|
||||||
@@ -169,7 +165,7 @@ class Header extends React.PureComponent {
|
|||||||
ButtonContainerComponent,
|
ButtonContainerComponent,
|
||||||
LabelContainerComponent
|
LabelContainerComponent
|
||||||
) => {
|
) => {
|
||||||
const { options, navigation } = props.scene.descriptor;
|
const { options } = props.scene.descriptor;
|
||||||
const backButtonTitle = this._getBackButtonTitleString(props.scene);
|
const backButtonTitle = this._getBackButtonTitleString(props.scene);
|
||||||
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
|
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
|
||||||
props.scene
|
props.scene
|
||||||
@@ -178,21 +174,14 @@ class Header extends React.PureComponent {
|
|||||||
? (this.props.layout.initWidth - this.state.widths[props.scene.key]) / 2
|
? (this.props.layout.initWidth - this.state.widths[props.scene.key]) / 2
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const goBack = () => {
|
|
||||||
// Go back on next tick because button ripple effect needs to happen on Android
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
navigation.goBack(props.scene.descriptor.key);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModularHeaderBackButton
|
<ModularHeaderBackButton
|
||||||
onPress={goBack}
|
onPress={this._navigateBack}
|
||||||
ButtonContainerComponent={ButtonContainerComponent}
|
ButtonContainerComponent={ButtonContainerComponent}
|
||||||
LabelContainerComponent={LabelContainerComponent}
|
LabelContainerComponent={LabelContainerComponent}
|
||||||
pressColorAndroid={options.headerPressColorAndroid}
|
pressColorAndroid={options.headerPressColorAndroid}
|
||||||
tintColor={options.headerTintColor}
|
tintColor={options.headerTintColor}
|
||||||
backImage={options.headerBackImage}
|
buttonImage={options.headerBackImage}
|
||||||
title={backButtonTitle}
|
title={backButtonTitle}
|
||||||
truncatedTitle={truncatedBackButtonTitle}
|
truncatedTitle={truncatedBackButtonTitle}
|
||||||
titleStyle={options.headerBackTitleStyle}
|
titleStyle={options.headerBackTitleStyle}
|
||||||
@@ -374,10 +363,6 @@ class Header extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_renderHeader(props) {
|
_renderHeader(props) {
|
||||||
const { options } = props.scene.descriptor;
|
|
||||||
if (options.header === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const left = this._renderLeft(props);
|
const left = this._renderLeft(props);
|
||||||
const right = this._renderRight(props);
|
const right = this._renderRight(props);
|
||||||
const title = this._renderTitle(props, {
|
const title = this._renderTitle(props, {
|
||||||
@@ -386,6 +371,7 @@ class Header extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { isLandscape, transitionPreset } = this.props;
|
const { isLandscape, transitionPreset } = this.props;
|
||||||
|
const { options } = props.scene.descriptor;
|
||||||
|
|
||||||
const wrapperProps = {
|
const wrapperProps = {
|
||||||
style: styles.header,
|
style: styles.header,
|
||||||
@@ -491,14 +477,10 @@ class Header extends React.PureComponent {
|
|||||||
const forceInset = headerForceInset || { top: 'always', bottom: 'never' };
|
const forceInset = headerForceInset || { top: 'always', bottom: 'never' };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={this.props.layoutInterpolator(this.props)}>
|
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
||||||
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
<View style={StyleSheet.absoluteFill}>{options.headerBackground}</View>
|
||||||
<View style={StyleSheet.absoluteFill}>
|
<View style={{ flex: 1 }}>{appBar}</View>
|
||||||
{options.headerBackground}
|
</SafeAreaView>
|
||||||
</View>
|
|
||||||
<View style={styles.flexOne}>{appBar}</View>
|
|
||||||
</SafeAreaView>
|
|
||||||
</Animated.View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,7 +548,6 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: -0.5, // resizes down to 20.5
|
marginTop: -0.5, // resizes down to 20.5
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
resizeMode: 'contain',
|
resizeMode: 'contain',
|
||||||
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
|
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@@ -594,9 +575,6 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
flexOne: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withOrientation(Header);
|
export default withOrientation(Header);
|
||||||
|
|||||||
594
src/views/Header/Header2.js
Normal file
594
src/views/Header/Header2.js
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Animated,
|
||||||
|
Dimensions,
|
||||||
|
Image,
|
||||||
|
Platform,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
ViewPropTypes,
|
||||||
|
} from 'react-native';
|
||||||
|
import { MaskedViewIOS } from '../../PlatformHelpers';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
|
import HeaderTitle from './HeaderTitle';
|
||||||
|
import HeaderBackButton from './HeaderBackButton';
|
||||||
|
import ModularHeaderBackButton from './ModularHeaderBackButton';
|
||||||
|
import HeaderStyleInterpolator from './HeaderStyleInterpolator2';
|
||||||
|
import withOrientation from '../withOrientation';
|
||||||
|
import { last } from 'rxjs/operators';
|
||||||
|
|
||||||
|
const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
|
||||||
|
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;
|
||||||
|
const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
|
||||||
|
|
||||||
|
const getAppBarHeight = isLandscape => {
|
||||||
|
return Platform.OS === 'ios'
|
||||||
|
? isLandscape && !Platform.isPad ? 32 : 44
|
||||||
|
: 56;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Header extends React.PureComponent {
|
||||||
|
static defaultProps = {
|
||||||
|
leftInterpolator: HeaderStyleInterpolator.forLeft,
|
||||||
|
leftButtonInterpolator: HeaderStyleInterpolator.forLeftButton,
|
||||||
|
leftLabelInterpolator: HeaderStyleInterpolator.forLeftLabel,
|
||||||
|
titleFromLeftInterpolator: HeaderStyleInterpolator.forCenterFromLeft,
|
||||||
|
titleInterpolator: HeaderStyleInterpolator.forCenter,
|
||||||
|
rightInterpolator: HeaderStyleInterpolator.forRight,
|
||||||
|
};
|
||||||
|
|
||||||
|
static get HEIGHT() {
|
||||||
|
return APPBAR_HEIGHT + STATUSBAR_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
widths: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
_getHeaderTitleString({ options }) {
|
||||||
|
if (typeof options.headerTitle === 'string') {
|
||||||
|
return options.headerTitle;
|
||||||
|
}
|
||||||
|
return options.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getLastSceneDescriptor(descriptor) {
|
||||||
|
const { state } = this.props.navigation;
|
||||||
|
const index = state.routes.findIndex(r => r.key === descriptor.key);
|
||||||
|
if (index < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const lastKey = state.routes[index - 1].key;
|
||||||
|
return this.props.descriptors[lastKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
_getBackButtonTitleString(descriptor) {
|
||||||
|
const lastSceneDescriptor = this._getLastSceneDescriptor(descriptor);
|
||||||
|
if (!lastSceneDescriptor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { headerBackTitle } = lastSceneDescriptor.options;
|
||||||
|
if (headerBackTitle || headerBackTitle === null) {
|
||||||
|
return headerBackTitle;
|
||||||
|
}
|
||||||
|
return this._getHeaderTitleString(lastSceneDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTruncatedBackButtonTitle(descriptor) {
|
||||||
|
const lastSceneDescriptor = this._getLastSceneDescriptor(descriptor);
|
||||||
|
if (!lastSceneDescriptor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastSceneDescriptor.options.headerTruncatedBackTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderTitleComponent = props => {
|
||||||
|
const { options } = props.descriptor;
|
||||||
|
const headerTitle = options.headerTitle;
|
||||||
|
if (React.isValidElement(headerTitle)) {
|
||||||
|
return headerTitle;
|
||||||
|
}
|
||||||
|
const titleString = this._getHeaderTitleString(props.descriptor);
|
||||||
|
|
||||||
|
const titleStyle = options.headerTitleStyle;
|
||||||
|
const color = options.headerTintColor;
|
||||||
|
const allowFontScaling = options.headerTitleAllowFontScaling;
|
||||||
|
|
||||||
|
// On iOS, width of left/right components depends on the calculated
|
||||||
|
// size of the title.
|
||||||
|
const onLayoutIOS =
|
||||||
|
Platform.OS === 'ios'
|
||||||
|
? e => {
|
||||||
|
this.setState({
|
||||||
|
widths: {
|
||||||
|
...this.state.widths,
|
||||||
|
[props.descriptor.key]: e.nativeEvent.layout.width,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const RenderedHeaderTitle =
|
||||||
|
headerTitle && typeof headerTitle !== 'string'
|
||||||
|
? headerTitle
|
||||||
|
: HeaderTitle;
|
||||||
|
return (
|
||||||
|
<RenderedHeaderTitle
|
||||||
|
onLayout={onLayoutIOS}
|
||||||
|
allowFontScaling={allowFontScaling == null ? true : allowFontScaling}
|
||||||
|
style={[color ? { color } : null, titleStyle]}
|
||||||
|
>
|
||||||
|
{titleString}
|
||||||
|
</RenderedHeaderTitle>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderLeftComponent = props => {
|
||||||
|
const { options } = props.descriptor;
|
||||||
|
if (
|
||||||
|
React.isValidElement(options.headerLeft) ||
|
||||||
|
options.headerLeft === null
|
||||||
|
) {
|
||||||
|
return options.headerLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backButtonTitle = this._getBackButtonTitleString(props.descriptor);
|
||||||
|
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
|
||||||
|
props.descriptor
|
||||||
|
);
|
||||||
|
const width = this.state.widths[props.descriptor.key]
|
||||||
|
? (this.props.layout.initWidth -
|
||||||
|
this.state.widths[props.descriptor.key]) /
|
||||||
|
2
|
||||||
|
: undefined;
|
||||||
|
const RenderedLeftComponent = options.headerLeft || HeaderBackButton;
|
||||||
|
const goBack = () => {
|
||||||
|
// Go back on next tick because button ripple effect needs to happen on Android
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.props.navigation.goBack(props.descriptor.key);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<RenderedLeftComponent
|
||||||
|
onPress={goBack}
|
||||||
|
pressColorAndroid={options.headerPressColorAndroid}
|
||||||
|
tintColor={options.headerTintColor}
|
||||||
|
buttonImage={options.headerBackImage}
|
||||||
|
title={backButtonTitle}
|
||||||
|
truncatedTitle={truncatedBackButtonTitle}
|
||||||
|
titleStyle={options.headerBackTitleStyle}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderModularLeftComponent = (
|
||||||
|
props,
|
||||||
|
ButtonContainerComponent,
|
||||||
|
LabelContainerComponent
|
||||||
|
) => {
|
||||||
|
const { options } = props.descriptor;
|
||||||
|
const backButtonTitle = this._getBackButtonTitleString(props.descriptor);
|
||||||
|
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
|
||||||
|
props.descriptor
|
||||||
|
);
|
||||||
|
const width = this.state.widths[props.descriptor.key]
|
||||||
|
? (this.props.layout.initWidth -
|
||||||
|
this.state.widths[props.descriptor.key]) /
|
||||||
|
2
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModularHeaderBackButton
|
||||||
|
onPress={this._navigateBack}
|
||||||
|
ButtonContainerComponent={ButtonContainerComponent}
|
||||||
|
LabelContainerComponent={LabelContainerComponent}
|
||||||
|
pressColorAndroid={options.headerPressColorAndroid}
|
||||||
|
tintColor={options.headerTintColor}
|
||||||
|
buttonImage={options.headerBackImage}
|
||||||
|
title={backButtonTitle}
|
||||||
|
truncatedTitle={truncatedBackButtonTitle}
|
||||||
|
titleStyle={options.headerBackTitleStyle}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderRightComponent = props => {
|
||||||
|
const { headerRight } = props.descriptor.options;
|
||||||
|
return headerRight || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderLeft(props) {
|
||||||
|
const { options } = props.descriptor;
|
||||||
|
|
||||||
|
const { transitionPreset } = this.props;
|
||||||
|
|
||||||
|
// On Android, or if we have a custom header left, or if we have a custom back image, we
|
||||||
|
// do not use the modular header (which is the one that imitates UINavigationController)
|
||||||
|
if (
|
||||||
|
transitionPreset !== 'uikit' ||
|
||||||
|
options.headerBackImage ||
|
||||||
|
options.headerLeft ||
|
||||||
|
options.headerLeft === null
|
||||||
|
) {
|
||||||
|
return this._renderSubView(
|
||||||
|
props,
|
||||||
|
'left',
|
||||||
|
this._renderLeftComponent,
|
||||||
|
this.props.leftInterpolator
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this._renderModularSubView(
|
||||||
|
props,
|
||||||
|
'left',
|
||||||
|
this._renderModularLeftComponent,
|
||||||
|
this.props.leftLabelInterpolator,
|
||||||
|
this.props.leftButtonInterpolator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderTitle(props, options) {
|
||||||
|
const style = {};
|
||||||
|
const { transitionPreset } = this.props;
|
||||||
|
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
if (!options.hasLeftComponent) {
|
||||||
|
style.left = 0;
|
||||||
|
}
|
||||||
|
if (!options.hasRightComponent) {
|
||||||
|
style.right = 0;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
Platform.OS === 'ios' &&
|
||||||
|
!options.hasLeftComponent &&
|
||||||
|
!options.hasRightComponent
|
||||||
|
) {
|
||||||
|
style.left = 0;
|
||||||
|
style.right = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._renderSubView(
|
||||||
|
{ ...props, style },
|
||||||
|
'title',
|
||||||
|
this._renderTitleComponent,
|
||||||
|
transitionPreset === 'uikit'
|
||||||
|
? this.props.titleFromLeftInterpolator
|
||||||
|
: this.props.titleInterpolator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderRight(props) {
|
||||||
|
return this._renderSubView(
|
||||||
|
props,
|
||||||
|
'right',
|
||||||
|
this._renderRightComponent,
|
||||||
|
this.props.rightInterpolator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderModularSubView(
|
||||||
|
props,
|
||||||
|
name,
|
||||||
|
renderer,
|
||||||
|
labelStyleInterpolator,
|
||||||
|
buttonStyleInterpolator
|
||||||
|
) {
|
||||||
|
const { descriptor, index, navigation } = props;
|
||||||
|
const { key } = descriptor;
|
||||||
|
|
||||||
|
// Never render a modular back button on the first screen in a stack.
|
||||||
|
if (index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = navigation.state.index - index;
|
||||||
|
|
||||||
|
if (Math.abs(offset) > 2) {
|
||||||
|
// Scene is far away from the active scene. Hides it to avoid unnecessary
|
||||||
|
// rendering.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonContainer = ({ children }) => (
|
||||||
|
<Animated.View style={[buttonStyleInterpolator(props)]}>
|
||||||
|
{children}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const LabelContainer = ({ children }) => (
|
||||||
|
<Animated.View style={[labelStyleInterpolator(props)]}>
|
||||||
|
{children}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const subView = renderer(props, ButtonContainer, LabelContainer);
|
||||||
|
|
||||||
|
if (subView === null) {
|
||||||
|
return subView;
|
||||||
|
}
|
||||||
|
const isTransitioning = !!navigation.state.transitioningFromKey;
|
||||||
|
|
||||||
|
const pointerEvents = offset !== 0 || isTransitioning ? 'none' : 'box-none';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={`${name}_${key}`}
|
||||||
|
pointerEvents={pointerEvents}
|
||||||
|
style={[styles.item, styles[name], props.style]}
|
||||||
|
>
|
||||||
|
{subView}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderSubView(props, name, renderer, styleInterpolator) {
|
||||||
|
const { descriptor, index, navigation } = props;
|
||||||
|
const { key } = descriptor;
|
||||||
|
|
||||||
|
const offset = navigation.state.index - index;
|
||||||
|
|
||||||
|
if (Math.abs(offset) > 2) {
|
||||||
|
// Scene is far away from the active scene. Hides it to avoid unnecessary
|
||||||
|
// rendering.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subView = renderer(props);
|
||||||
|
|
||||||
|
if (subView == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const isTransitioning = !!navigation.state.transitioningFromKey;
|
||||||
|
|
||||||
|
const pointerEvents = offset !== 0 || isTransitioning ? 'none' : 'box-none';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
pointerEvents={pointerEvents}
|
||||||
|
key={`${name}_${key}`}
|
||||||
|
style={[
|
||||||
|
styles.item,
|
||||||
|
styles[name],
|
||||||
|
props.style,
|
||||||
|
styleInterpolator({
|
||||||
|
// todo: determine if we really need to splat all this.props
|
||||||
|
...this.props,
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{subView}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderHeader(props) {
|
||||||
|
const left = this._renderLeft(props);
|
||||||
|
const right = this._renderRight(props);
|
||||||
|
const title = this._renderTitle(props, {
|
||||||
|
hasLeftComponent: !!left,
|
||||||
|
hasRightComponent: !!right,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isLandscape, transitionPreset } = this.props;
|
||||||
|
const { options } = props.descriptor;
|
||||||
|
|
||||||
|
const wrapperProps = {
|
||||||
|
style: styles.header,
|
||||||
|
key: `header_${props.descriptor.key}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
options.headerLeft ||
|
||||||
|
options.headerBackImage ||
|
||||||
|
Platform.OS !== 'ios' ||
|
||||||
|
transitionPreset !== 'uikit'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<View {...wrapperProps}>
|
||||||
|
{title}
|
||||||
|
{left}
|
||||||
|
{right}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<MaskedViewIOS
|
||||||
|
{...wrapperProps}
|
||||||
|
maskElement={
|
||||||
|
<View style={styles.iconMaskContainer}>
|
||||||
|
<Image
|
||||||
|
source={require('../assets/back-icon-mask.png')}
|
||||||
|
style={styles.iconMask}
|
||||||
|
/>
|
||||||
|
<View style={styles.iconMaskFillerRect} />
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
{left}
|
||||||
|
{right}
|
||||||
|
</MaskedViewIOS>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let appBar;
|
||||||
|
const {
|
||||||
|
mode,
|
||||||
|
isLandscape,
|
||||||
|
navigation,
|
||||||
|
descriptors,
|
||||||
|
descriptor,
|
||||||
|
transition,
|
||||||
|
} = this.props;
|
||||||
|
const { index } = navigation.state;
|
||||||
|
if (mode === 'float') {
|
||||||
|
const scenesDescriptorsByIndex = [];
|
||||||
|
const { state } = navigation;
|
||||||
|
state.routes.forEach((route, routeIndex) => {
|
||||||
|
scenesDescriptorsByIndex[routeIndex] = descriptors[route.key];
|
||||||
|
});
|
||||||
|
const scenesProps = scenesDescriptorsByIndex.map(
|
||||||
|
(descriptor, descriptorIndex) => ({
|
||||||
|
...this.props,
|
||||||
|
descriptor,
|
||||||
|
index: descriptorIndex,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
appBar = scenesProps.map(this._renderHeader, this);
|
||||||
|
} else {
|
||||||
|
appBar = this._renderHeader({ ...this.props, index });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { options } = descriptor;
|
||||||
|
const { headerStyle = {} } = options;
|
||||||
|
const headerStyleObj = StyleSheet.flatten(headerStyle);
|
||||||
|
const appBarHeight = getAppBarHeight(isLandscape);
|
||||||
|
|
||||||
|
const {
|
||||||
|
alignItems,
|
||||||
|
justifyContent,
|
||||||
|
flex,
|
||||||
|
flexDirection,
|
||||||
|
flexGrow,
|
||||||
|
flexShrink,
|
||||||
|
flexBasis,
|
||||||
|
flexWrap,
|
||||||
|
...safeHeaderStyle
|
||||||
|
} = headerStyleObj;
|
||||||
|
|
||||||
|
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 containerStyles = [
|
||||||
|
options.headerTransparent
|
||||||
|
? styles.transparentContainer
|
||||||
|
: styles.container,
|
||||||
|
{ height: appBarHeight },
|
||||||
|
safeHeaderStyle,
|
||||||
|
];
|
||||||
|
|
||||||
|
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.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let platformContainerStyles;
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
platformContainerStyles = {
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: '#A7A7AA',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
platformContainerStyles = {
|
||||||
|
shadowColor: 'black',
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: StyleSheet.hairlineWidth,
|
||||||
|
shadowOffset: {
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
elevation: 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
||||||
|
...platformContainerStyles,
|
||||||
|
},
|
||||||
|
transparentContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
...platformContainerStyles,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
iconMaskContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
iconMaskFillerRect: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#d8d8d8',
|
||||||
|
marginLeft: -3,
|
||||||
|
},
|
||||||
|
iconMask: {
|
||||||
|
// These are mostly the same as the icon in ModularHeaderBackButton
|
||||||
|
height: 21,
|
||||||
|
width: 12,
|
||||||
|
marginLeft: 9,
|
||||||
|
marginTop: -0.5, // resizes down to 20.5
|
||||||
|
alignSelf: 'center',
|
||||||
|
resizeMode: 'contain',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
left: TITLE_OFFSET,
|
||||||
|
right: TITLE_OFFSET,
|
||||||
|
position: 'absolute',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withOrientation(Header);
|
||||||
@@ -10,8 +10,6 @@ import {
|
|||||||
|
|
||||||
import TouchableItem from '../TouchableItem';
|
import TouchableItem from '../TouchableItem';
|
||||||
|
|
||||||
const defaultBackImage = require('../assets/back-icon.png');
|
|
||||||
|
|
||||||
class HeaderBackButton extends React.PureComponent {
|
class HeaderBackButton extends React.PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
pressColorAndroid: 'rgba(0, 0, 0, .32)',
|
pressColorAndroid: 'rgba(0, 0, 0, .32)',
|
||||||
@@ -19,6 +17,8 @@ class HeaderBackButton extends React.PureComponent {
|
|||||||
ios: '#037aff',
|
ios: '#037aff',
|
||||||
}),
|
}),
|
||||||
truncatedTitle: 'Back',
|
truncatedTitle: 'Back',
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
buttonImage: require('../assets/back-icon.png'),
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {};
|
state = {};
|
||||||
@@ -32,37 +32,9 @@ class HeaderBackButton extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderBackImage() {
|
|
||||||
const { backImage, title, tintColor } = this.props;
|
|
||||||
|
|
||||||
let BackImage;
|
|
||||||
let props;
|
|
||||||
|
|
||||||
if (React.isValidElement(backImage)) {
|
|
||||||
return backImage;
|
|
||||||
} else if (backImage) {
|
|
||||||
BackImage = backImage;
|
|
||||||
props = {
|
|
||||||
tintColor,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
BackImage = Image;
|
|
||||||
props = {
|
|
||||||
style: [
|
|
||||||
styles.icon,
|
|
||||||
!!title && styles.iconWithTitle,
|
|
||||||
!!tintColor && { tintColor },
|
|
||||||
],
|
|
||||||
source: defaultBackImage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BackImage {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
buttonImage,
|
||||||
onPress,
|
onPress,
|
||||||
pressColorAndroid,
|
pressColorAndroid,
|
||||||
width,
|
width,
|
||||||
@@ -92,7 +64,14 @@ class HeaderBackButton extends React.PureComponent {
|
|||||||
borderless
|
borderless
|
||||||
>
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{this._renderBackImage()}
|
<Image
|
||||||
|
style={[
|
||||||
|
styles.icon,
|
||||||
|
!!title && styles.iconWithTitle,
|
||||||
|
!!tintColor && { tintColor },
|
||||||
|
]}
|
||||||
|
source={buttonImage}
|
||||||
|
/>
|
||||||
{Platform.OS === 'ios' &&
|
{Platform.OS === 'ios' &&
|
||||||
typeof backButtonTitle === 'string' && (
|
typeof backButtonTitle === 'string' && (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -1,37 +1,13 @@
|
|||||||
import { Dimensions, I18nManager } from 'react-native';
|
import { Dimensions, I18nManager } from 'react-native';
|
||||||
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
|
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
|
||||||
|
|
||||||
function hasHeader(scene) {
|
const crossFadeInterpolation = (first, index, last) => ({
|
||||||
if (!scene) {
|
inputRange: [first, index - 0.9, index - 0.2, index, last],
|
||||||
return true;
|
outputRange: [0, 0, 0.3, 1, 0],
|
||||||
}
|
|
||||||
const { descriptor } = scene;
|
|
||||||
return descriptor.options.header !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const crossFadeInterpolation = (scenes, first, index, last) => ({
|
|
||||||
inputRange: [
|
|
||||||
first,
|
|
||||||
first + 0.001,
|
|
||||||
index - 0.9,
|
|
||||||
index - 0.2,
|
|
||||||
index,
|
|
||||||
last - 0.001,
|
|
||||||
last,
|
|
||||||
],
|
|
||||||
outputRange: [
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[first]) ? 0.3 : 1,
|
|
||||||
hasHeader(scenes[index]) ? 1 : 0,
|
|
||||||
hasHeader(scenes[last]) ? 0 : 1,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities that build the style for the navigation header.
|
* Utility that builds the style for the navigation header.
|
||||||
*
|
*
|
||||||
* +-------------+-------------+-------------+
|
* +-------------+-------------+-------------+
|
||||||
* | | | |
|
* | | | |
|
||||||
@@ -41,51 +17,6 @@ const crossFadeInterpolation = (scenes, first, index, last) => ({
|
|||||||
* +-------------+-------------+-------------+
|
* +-------------+-------------+-------------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function isGoingBack(scenes) {
|
|
||||||
const lastSceneIndexInScenes = scenes.length - 1;
|
|
||||||
return !scenes[lastSceneIndexInScenes].isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
function forLayout(props) {
|
|
||||||
const { layout, position, scene, scenes, mode } = props;
|
|
||||||
if (mode !== 'float') {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const isBack = isGoingBack(scenes);
|
|
||||||
|
|
||||||
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
|
||||||
if (!interpolate) return {};
|
|
||||||
|
|
||||||
const { first, last } = interpolate;
|
|
||||||
const index = scene.index;
|
|
||||||
const width = layout.initWidth;
|
|
||||||
|
|
||||||
// Make sure the header stays hidden when transitioning between 2 screens
|
|
||||||
// with no header.
|
|
||||||
if (
|
|
||||||
(isBack && !hasHeader(scenes[index]) && !hasHeader(scenes[last])) ||
|
|
||||||
(!isBack && !hasHeader(scenes[first]) && !hasHeader(scenes[index]))
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
transform: [{ translateX: width }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const rtlMult = I18nManager.isRTL ? -1 : 1;
|
|
||||||
const translateX = position.interpolate({
|
|
||||||
inputRange: [first, index, last],
|
|
||||||
outputRange: [
|
|
||||||
rtlMult * (hasHeader(scenes[first]) ? 0 : width),
|
|
||||||
rtlMult * (hasHeader(scenes[index]) ? 0 : isBack ? width : -width),
|
|
||||||
rtlMult * (hasHeader(scenes[last]) ? 0 : -width),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
transform: [{ translateX }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function forLeft(props) {
|
function forLeft(props) {
|
||||||
const { position, scene, scenes } = props;
|
const { position, scene, scenes } = props;
|
||||||
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
@@ -96,14 +27,12 @@ function forLeft(props) {
|
|||||||
const index = scene.index;
|
const index = scene.index;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: position.interpolate(
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
crossFadeInterpolation(scenes, first, index, last)
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function forCenter(props) {
|
function forCenter(props) {
|
||||||
const { position, scene, scenes } = props;
|
const { position, scene } = props;
|
||||||
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
if (!interpolate) return { opacity: 0 };
|
if (!interpolate) return { opacity: 0 };
|
||||||
@@ -112,14 +41,12 @@ function forCenter(props) {
|
|||||||
const index = scene.index;
|
const index = scene.index;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: position.interpolate(
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
crossFadeInterpolation(scenes, first, index, last)
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function forRight(props) {
|
function forRight(props) {
|
||||||
const { position, scene, scenes } = props;
|
const { position, scene } = props;
|
||||||
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
if (!interpolate) return { opacity: 0 };
|
if (!interpolate) return { opacity: 0 };
|
||||||
@@ -127,9 +54,7 @@ function forRight(props) {
|
|||||||
const index = scene.index;
|
const index = scene.index;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: position.interpolate(
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
crossFadeInterpolation(scenes, first, index, last)
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,42 +71,25 @@ function forLeftButton(props) {
|
|||||||
const { first, last } = interpolate;
|
const { first, last } = interpolate;
|
||||||
const index = scene.index;
|
const index = scene.index;
|
||||||
|
|
||||||
// The gist of what we're doing here is animating the left button _normally_ (fast fade)
|
|
||||||
// when both scenes in transition have headers. When the current, next, or previous scene _don't_
|
|
||||||
// have a header, we don't fade the button, and only set it's opacity to 0 at the last moment
|
|
||||||
// of the transition.
|
|
||||||
const inputRange = [
|
|
||||||
first,
|
|
||||||
first + 0.001,
|
|
||||||
first + Math.abs(index - first) / 2,
|
|
||||||
index,
|
|
||||||
last - Math.abs(last - index) / 2,
|
|
||||||
last - 0.001,
|
|
||||||
last,
|
|
||||||
];
|
|
||||||
const outputRange = [
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[first]) ? 0.1 : 1,
|
|
||||||
hasHeader(scenes[index]) ? 1 : 0,
|
|
||||||
hasHeader(scenes[last]) ? 0.1 : 1,
|
|
||||||
hasHeader(scenes[last]) ? 0 : 1,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: position.interpolate({
|
opacity: position.interpolate({
|
||||||
inputRange,
|
inputRange: [
|
||||||
outputRange,
|
first,
|
||||||
|
first + Math.abs(index - first) / 2,
|
||||||
|
index,
|
||||||
|
last - Math.abs(last - index) / 2,
|
||||||
|
last,
|
||||||
|
],
|
||||||
|
outputRange: [0, 0.5, 1, 0.5, 0],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: this offset calculation is an approximation that gives us
|
* NOTE: this offset calculation is a an approximation that gives us
|
||||||
* decent results in many cases, but it is ultimately a poor substitute
|
* decent results in many cases, but it is ultimately a poor substitute
|
||||||
* for text measurement. See the comment on title for more information.
|
* for text measurement. See the comment on title for more information.
|
||||||
*
|
*
|
||||||
* - 70 is the width of the left button area.
|
* - 70 is the width of the left button area.
|
||||||
* - 25 is the width of the left button icon (to account for label offset)
|
* - 25 is the width of the left button icon (to account for label offset)
|
||||||
*/
|
*/
|
||||||
@@ -197,71 +105,41 @@ function forLeftLabel(props) {
|
|||||||
|
|
||||||
const offset = LEFT_LABEL_OFFSET;
|
const offset = LEFT_LABEL_OFFSET;
|
||||||
|
|
||||||
// Similarly to the animation of the left label, when animating to or from a scene without
|
|
||||||
// a header, we keep the label at full opacity and in the same position for as long as possible.
|
|
||||||
return {
|
return {
|
||||||
// For now we fade out the label before fading in the title, so the
|
// For now we fade out the label before fading in the title, so the
|
||||||
// differences between the label and title position can be hopefully not so
|
// differences between the label and title position can be hopefully not so
|
||||||
// noticable to the user
|
// noticable to the user
|
||||||
opacity: position.interpolate({
|
opacity: position.interpolate({
|
||||||
inputRange: [
|
inputRange: [first, index - 0.35, index, index + 0.5, last],
|
||||||
first,
|
outputRange: [0, 0, 1, 0.5, 0],
|
||||||
first + 0.001,
|
|
||||||
index - 0.35,
|
|
||||||
index,
|
|
||||||
index + 0.5,
|
|
||||||
last - 0.001,
|
|
||||||
last,
|
|
||||||
],
|
|
||||||
outputRange: [
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[index]) ? 1 : 0,
|
|
||||||
hasHeader(scenes[last]) ? 0.5 : 1,
|
|
||||||
hasHeader(scenes[last]) ? 0 : 1,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateX: position.interpolate({
|
translateX: position.interpolate({
|
||||||
inputRange: [first, first + 0.001, index, last - 0.001, last],
|
inputRange: [first, index, last],
|
||||||
outputRange: I18nManager.isRTL
|
outputRange: I18nManager.isRTL
|
||||||
? [
|
? [-offset, 0, offset]
|
||||||
-offset * 1.5,
|
: [offset, 0, -offset * 1.5],
|
||||||
hasHeader(scenes[first]) ? -offset * 1.5 : 0,
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[last]) ? offset : 0,
|
|
||||||
offset,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
offset,
|
|
||||||
hasHeader(scenes[first]) ? offset : 0,
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[last]) ? -offset * 1.5 : 0,
|
|
||||||
-offset * 1.5,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: this offset calculation is a an approximation that gives us
|
* NOTE: this offset calculation is a an approximation that gives us
|
||||||
* decent results in many cases, but it is ultimately a poor substitute
|
* decent results in many cases, but it is ultimately a poor substitute
|
||||||
* for text measurement. We want the back button label to transition
|
* for text measurement. We want the back button label to transition
|
||||||
* smoothly into the title text and to do this we need to understand
|
* smoothly into the title text and to do this we need to understand
|
||||||
* where the title is positioned within the title container (since it is
|
* where the title is positioned within the title container (since it is
|
||||||
* centered).
|
* centered).
|
||||||
*
|
*
|
||||||
* - 70 is the width of the left button area.
|
* - 70 is the width of the left button area.
|
||||||
* - 25 is the width of the left button icon (to account for label offset)
|
* - 25 is the width of the left button icon (to account for label offset)
|
||||||
*/
|
*/
|
||||||
const TITLE_OFFSET_IOS = Dimensions.get('window').width / 2 - 70 + 25;
|
const TITLE_OFFSET_IOS = Dimensions.get('window').width / 2 - 70 + 25;
|
||||||
function forCenterFromLeft(props) {
|
function forCenterFromLeft(props) {
|
||||||
const { position, scene, scenes } = props;
|
const { position, scene } = props;
|
||||||
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
if (!interpolate) return { opacity: 0 };
|
if (!interpolate) return { opacity: 0 };
|
||||||
@@ -273,44 +151,16 @@ function forCenterFromLeft(props) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: position.interpolate({
|
opacity: position.interpolate({
|
||||||
inputRange: [
|
inputRange: [first, index - 0.5, index, index + 0.7, last],
|
||||||
first,
|
outputRange: [0, 0, 1, 0, 0],
|
||||||
first + 0.001,
|
|
||||||
index - 0.5,
|
|
||||||
index,
|
|
||||||
index + 0.7,
|
|
||||||
last - 0.001,
|
|
||||||
last,
|
|
||||||
],
|
|
||||||
outputRange: [
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[first]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[index]) ? 1 : 0,
|
|
||||||
hasHeader(scenes[last]) ? 0 : 1,
|
|
||||||
hasHeader(scenes[last]) ? 0 : 1,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateX: position.interpolate({
|
translateX: position.interpolate({
|
||||||
inputRange: [first, first + 0.001, index, last - 0.001, last],
|
inputRange: [first, index, last],
|
||||||
outputRange: I18nManager.isRTL
|
outputRange: I18nManager.isRTL
|
||||||
? [
|
? [-offset, 0, offset]
|
||||||
-offset,
|
: [offset, 0, -offset],
|
||||||
hasHeader(scenes[first]) ? -offset : 0,
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[last]) ? offset : 0,
|
|
||||||
offset,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
offset,
|
|
||||||
hasHeader(scenes[first]) ? offset : 0,
|
|
||||||
0,
|
|
||||||
hasHeader(scenes[last]) ? -offset : 0,
|
|
||||||
-offset,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -318,7 +168,6 @@ function forCenterFromLeft(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
forLayout,
|
|
||||||
forLeft,
|
forLeft,
|
||||||
forLeftButton,
|
forLeftButton,
|
||||||
forLeftLabel,
|
forLeftLabel,
|
||||||
|
|||||||
176
src/views/Header/HeaderStyleInterpolator2.js
Normal file
176
src/views/Header/HeaderStyleInterpolator2.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { Dimensions, I18nManager } from 'react-native';
|
||||||
|
|
||||||
|
const crossFadeInterpolation = (first, index, last) => ({
|
||||||
|
inputRange: [first, index - 0.9, index - 0.2, index, last],
|
||||||
|
outputRange: [0, 0, 0.3, 1, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility that builds the style for the navigation header.
|
||||||
|
*
|
||||||
|
* +-------------+-------------+-------------+
|
||||||
|
* | | | |
|
||||||
|
* | Left | Title | Right |
|
||||||
|
* | Component | Component | Component |
|
||||||
|
* | | | |
|
||||||
|
* +-------------+-------------+-------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
function forLeft(props) {
|
||||||
|
const { position, descriptor, scenes } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function forCenter(props) {
|
||||||
|
const { position, scene } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function forRight(props) {
|
||||||
|
const { position, scene } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity: position.interpolate(crossFadeInterpolation(first, index, last)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iOS UINavigationController style interpolators
|
||||||
|
*/
|
||||||
|
|
||||||
|
function forLeftButton(props) {
|
||||||
|
const { position, scene, scenes } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity: position.interpolate({
|
||||||
|
inputRange: [
|
||||||
|
first,
|
||||||
|
first + Math.abs(index - first) / 2,
|
||||||
|
index,
|
||||||
|
last - Math.abs(last - index) / 2,
|
||||||
|
last,
|
||||||
|
],
|
||||||
|
outputRange: [0, 0.5, 1, 0.5, 0],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: this offset calculation is a an approximation that gives us
|
||||||
|
* decent results in many cases, but it is ultimately a poor substitute
|
||||||
|
* for text measurement. See the comment on title for more information.
|
||||||
|
*
|
||||||
|
* - 70 is the width of the left button area.
|
||||||
|
* - 25 is the width of the left button icon (to account for label offset)
|
||||||
|
*/
|
||||||
|
const LEFT_LABEL_OFFSET = Dimensions.get('window').width / 2 - 70 - 25;
|
||||||
|
function forLeftLabel(props) {
|
||||||
|
const { position, scene, scenes } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
|
||||||
|
const offset = LEFT_LABEL_OFFSET;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// For now we fade out the label before fading in the title, so the
|
||||||
|
// differences between the label and title position can be hopefully not so
|
||||||
|
// noticable to the user
|
||||||
|
opacity: position.interpolate({
|
||||||
|
inputRange: [first, index - 0.35, index, index + 0.5, last],
|
||||||
|
outputRange: [0, 0, 1, 0.5, 0],
|
||||||
|
}),
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: position.interpolate({
|
||||||
|
inputRange: [first, index, last],
|
||||||
|
outputRange: I18nManager.isRTL
|
||||||
|
? [-offset, 0, offset]
|
||||||
|
: [offset, 0, -offset * 1.5],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: this offset calculation is a an approximation that gives us
|
||||||
|
* decent results in many cases, but it is ultimately a poor substitute
|
||||||
|
* for text measurement. We want the back button label to transition
|
||||||
|
* smoothly into the title text and to do this we need to understand
|
||||||
|
* where the title is positioned within the title container (since it is
|
||||||
|
* centered).
|
||||||
|
*
|
||||||
|
* - 70 is the width of the left button area.
|
||||||
|
* - 25 is the width of the left button icon (to account for label offset)
|
||||||
|
*/
|
||||||
|
const TITLE_OFFSET_IOS = Dimensions.get('window').width / 2 - 70 + 25;
|
||||||
|
function forCenterFromLeft(props) {
|
||||||
|
const { position, scene } = props;
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
const inputRange = [first, index - 0.5, index, index + 0.5, last];
|
||||||
|
const offset = TITLE_OFFSET_IOS;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity: position.interpolate({
|
||||||
|
inputRange: [first, index - 0.5, index, index + 0.7, last],
|
||||||
|
outputRange: [0, 0, 1, 0, 0],
|
||||||
|
}),
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: position.interpolate({
|
||||||
|
inputRange: [first, index, last],
|
||||||
|
outputRange: I18nManager.isRTL
|
||||||
|
? [-offset, 0, offset]
|
||||||
|
: [offset, 0, -offset],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
forLeft,
|
||||||
|
forLeftButton,
|
||||||
|
forLeftLabel,
|
||||||
|
forCenterFromLeft,
|
||||||
|
forCenter,
|
||||||
|
forRight,
|
||||||
|
};
|
||||||
@@ -3,12 +3,12 @@ import { I18nManager, Image, Text, View, StyleSheet } from 'react-native';
|
|||||||
|
|
||||||
import TouchableItem from '../TouchableItem';
|
import TouchableItem from '../TouchableItem';
|
||||||
|
|
||||||
const defaultBackImage = require('../assets/back-icon.png');
|
|
||||||
|
|
||||||
class ModularHeaderBackButton extends React.PureComponent {
|
class ModularHeaderBackButton extends React.PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
tintColor: '#037aff',
|
tintColor: '#037aff',
|
||||||
truncatedTitle: 'Back',
|
truncatedTitle: 'Back',
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
buttonImage: require('../assets/back-icon.png'),
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {};
|
state = {};
|
||||||
@@ -22,37 +22,9 @@ class ModularHeaderBackButton extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderBackImage() {
|
|
||||||
const { backImage, title, tintColor } = this.props;
|
|
||||||
|
|
||||||
let BackImage;
|
|
||||||
let props;
|
|
||||||
|
|
||||||
if (React.isValidElement(backImage)) {
|
|
||||||
return backImage;
|
|
||||||
} else if (backImage) {
|
|
||||||
BackImage = backImage;
|
|
||||||
props = {
|
|
||||||
tintColor,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
BackImage = Image;
|
|
||||||
props = {
|
|
||||||
style: [
|
|
||||||
styles.icon,
|
|
||||||
!!title && styles.iconWithTitle,
|
|
||||||
!!tintColor && { tintColor },
|
|
||||||
],
|
|
||||||
source: defaultBackImage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BackImage {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
buttonImage,
|
||||||
onPress,
|
onPress,
|
||||||
width,
|
width,
|
||||||
title,
|
title,
|
||||||
@@ -89,7 +61,14 @@ class ModularHeaderBackButton extends React.PureComponent {
|
|||||||
>
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ButtonContainerComponent>
|
<ButtonContainerComponent>
|
||||||
{this._renderBackImage()}
|
<Image
|
||||||
|
style={[
|
||||||
|
styles.icon,
|
||||||
|
!!title && styles.iconWithTitle,
|
||||||
|
!!tintColor && { tintColor },
|
||||||
|
]}
|
||||||
|
source={buttonImage}
|
||||||
|
/>
|
||||||
</ButtonContainerComponent>
|
</ButtonContainerComponent>
|
||||||
{typeof backButtonTitle === 'string' && (
|
{typeof backButtonTitle === 'string' && (
|
||||||
<LabelContainerComponent>
|
<LabelContainerComponent>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import propTypes from 'prop-types';
|
|
||||||
import createReactContext from 'create-react-context';
|
|
||||||
|
|
||||||
const NavigationContext = createReactContext();
|
|
||||||
|
|
||||||
export const NavigationProvider = NavigationContext.Provider;
|
|
||||||
export const NavigationConsumer = NavigationContext.Consumer;
|
|
||||||
@@ -1,33 +1,40 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
|
||||||
|
|
||||||
import SceneView from './SceneView';
|
import SceneView from './SceneView';
|
||||||
|
|
||||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||||
|
|
||||||
class ResourceSavingSceneView extends React.PureComponent {
|
export default class ResourceSavingSceneView extends React.PureComponent {
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
|
||||||
if (nextProps.isFocused && !prevState.awake) {
|
|
||||||
return { awake: true };
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
const key = props.childNavigation.state.key;
|
||||||
|
const focusedIndex = props.navigation.state.index;
|
||||||
|
const focusedKey = props.navigation.state.routes[focusedIndex].key;
|
||||||
|
const isFocused = key === focusedKey;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
awake: props.lazy ? props.isFocused : true,
|
awake: props.lazy ? isFocused : true,
|
||||||
|
visible: isFocused,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._actionListener = this.props.navigation.addListener(
|
||||||
|
'action',
|
||||||
|
this._onAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._actionListener.remove();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { awake } = this.state;
|
const { awake, visible } = this.state;
|
||||||
const {
|
const {
|
||||||
isFocused,
|
|
||||||
childNavigation,
|
childNavigation,
|
||||||
navigation,
|
navigation,
|
||||||
removeClippedSubviews,
|
removeClippedSubviews,
|
||||||
@@ -42,12 +49,12 @@ class ResourceSavingSceneView extends React.PureComponent {
|
|||||||
removeClippedSubviews={
|
removeClippedSubviews={
|
||||||
Platform.OS === 'android'
|
Platform.OS === 'android'
|
||||||
? removeClippedSubviews
|
? removeClippedSubviews
|
||||||
: !isFocused && removeClippedSubviews
|
: !visible && removeClippedSubviews
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
this._mustAlwaysBeVisible() || isFocused
|
this._mustAlwaysBeVisible() || visible
|
||||||
? styles.innerAttached
|
? styles.innerAttached
|
||||||
: styles.innerDetached
|
: styles.innerDetached
|
||||||
}
|
}
|
||||||
@@ -61,6 +68,33 @@ class ResourceSavingSceneView extends React.PureComponent {
|
|||||||
_mustAlwaysBeVisible = () => {
|
_mustAlwaysBeVisible = () => {
|
||||||
return this.props.animationEnabled || this.props.swipeEnabled;
|
return this.props.animationEnabled || this.props.swipeEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onAction = payload => {
|
||||||
|
// We do not care about transition complete events, they won't actually change the state
|
||||||
|
if (
|
||||||
|
payload.action.type == 'Navigation/COMPLETE_TRANSITION' ||
|
||||||
|
!payload.state
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { routes, index } = payload.state;
|
||||||
|
const key = this.props.childNavigation.state.key;
|
||||||
|
|
||||||
|
if (routes[index].key === key) {
|
||||||
|
if (!this.state.visible) {
|
||||||
|
let nextState = { visible: true };
|
||||||
|
if (!this.state.awake) {
|
||||||
|
nextState.awake = true;
|
||||||
|
}
|
||||||
|
this.setState(nextState);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.state.visible) {
|
||||||
|
this.setState({ visible: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@@ -76,5 +110,3 @@ const styles = StyleSheet.create({
|
|||||||
top: FAR_FAR_AWAY,
|
top: FAR_FAR_AWAY,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default polyfill(ResourceSavingSceneView);
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import propTypes from 'prop-types';
|
import propTypes from 'prop-types';
|
||||||
import { NavigationProvider } from './NavigationContext';
|
|
||||||
|
|
||||||
export default class SceneView extends React.PureComponent {
|
export default class SceneView extends React.PureComponent {
|
||||||
|
static childContextTypes = {
|
||||||
|
navigation: propTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
return {
|
||||||
|
navigation: this.props.navigation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { screenProps, component: Component, navigation } = this.props;
|
const { screenProps, navigation, component: Component } = this.props;
|
||||||
return (
|
return <Component screenProps={screenProps} navigation={navigation} />;
|
||||||
<NavigationProvider value={navigation}>
|
|
||||||
<Component screenProps={screenProps} navigation={navigation} />
|
|
||||||
</NavigationProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { NativeModules } from 'react-native';
|
|||||||
import StackViewLayout from './StackViewLayout';
|
import StackViewLayout from './StackViewLayout';
|
||||||
import Transitioner from '../Transitioner';
|
import Transitioner from '../Transitioner';
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import StackActions from '../../routers/StackActions';
|
|
||||||
import TransitionConfigs from './StackViewTransitionConfigs';
|
import TransitionConfigs from './StackViewTransitionConfigs';
|
||||||
|
|
||||||
const NativeAnimatedModule =
|
const NativeAnimatedModule =
|
||||||
@@ -25,16 +24,14 @@ class StackView extends React.Component {
|
|||||||
navigation={this.props.navigation}
|
navigation={this.props.navigation}
|
||||||
descriptors={this.props.descriptors}
|
descriptors={this.props.descriptors}
|
||||||
onTransitionStart={this.props.onTransitionStart}
|
onTransitionStart={this.props.onTransitionStart}
|
||||||
onTransitionEnd={(transition, lastTransition) => {
|
onTransitionEnd={(lastTransition, transition) => {
|
||||||
const { onTransitionEnd, navigation } = this.props;
|
const { onTransitionEnd, navigation } = this.props;
|
||||||
if (transition.navigation.state.isTransitioning) {
|
navigation.dispatch(
|
||||||
navigation.dispatch(
|
NavigationActions.completeTransition({
|
||||||
StackActions.completeTransition({
|
key: navigation.state.key,
|
||||||
key: navigation.state.key,
|
})
|
||||||
})
|
);
|
||||||
);
|
onTransitionEnd && onTransitionEnd(lastTransition, transition);
|
||||||
}
|
|
||||||
onTransitionEnd && onTransitionEnd(transition, lastTransition);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -57,9 +54,6 @@ class StackView extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<StackViewLayout
|
<StackViewLayout
|
||||||
{...navigationConfig}
|
{...navigationConfig}
|
||||||
onGestureBegin={this.props.onGestureBegin}
|
|
||||||
onGestureCanceled={this.props.onGestureCanceled}
|
|
||||||
onGestureEnd={this.props.onGestureEnd}
|
|
||||||
screenProps={screenProps}
|
screenProps={screenProps}
|
||||||
descriptors={this.props.descriptors}
|
descriptors={this.props.descriptors}
|
||||||
transitionProps={transitionProps}
|
transitionProps={transitionProps}
|
||||||
|
|||||||
546
src/views/StackView/StackView2.js
Normal file
546
src/views/StackView/StackView2.js
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import Transitioner from './Transitioner2';
|
||||||
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
import Transitions from './StackViewTransitions';
|
||||||
|
|
||||||
|
const NativeAnimatedModule =
|
||||||
|
NativeModules && NativeModules.NativeAnimatedModule;
|
||||||
|
|
||||||
|
import clamp from 'clamp';
|
||||||
|
import {
|
||||||
|
Animated,
|
||||||
|
StyleSheet,
|
||||||
|
PanResponder,
|
||||||
|
Platform,
|
||||||
|
View,
|
||||||
|
I18nManager,
|
||||||
|
Easing,
|
||||||
|
NativeModules,
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import Card from './StackViewCard';
|
||||||
|
// import Header from '../Header/Header2'; // WIP.. interpolation reconfiguration, fun!
|
||||||
|
import SceneView from '../SceneView';
|
||||||
|
import invariant from '../../utils/invariant';
|
||||||
|
|
||||||
|
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
||||||
|
|
||||||
|
const emptyFunction = () => {};
|
||||||
|
|
||||||
|
const EaseInOut = Easing.inOut(Easing.ease);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The max duration of the card animation in milliseconds after released gesture.
|
||||||
|
* The actual duration should be always less then that because the rest distance
|
||||||
|
* is always less then the full distance of the layout.
|
||||||
|
*/
|
||||||
|
const ANIMATION_DURATION = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The gesture distance threshold to trigger the back behavior. For instance,
|
||||||
|
* `1/2` means that moving greater than 1/2 of the width of the screen will
|
||||||
|
* trigger a back action
|
||||||
|
*/
|
||||||
|
const POSITION_THRESHOLD = 1 / 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold (in pixels) to start the gesture action.
|
||||||
|
*/
|
||||||
|
const RESPOND_THRESHOLD = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distance of touch start from the edge of the screen where the gesture will be recognized
|
||||||
|
*/
|
||||||
|
const GESTURE_RESPONSE_DISTANCE_HORIZONTAL = 25;
|
||||||
|
const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
|
||||||
|
|
||||||
|
const animatedSubscribeValue = animatedValue => {
|
||||||
|
if (!animatedValue.__isNative) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Object.keys(animatedValue._listeners).length === 0) {
|
||||||
|
animatedValue.addListener(emptyFunction);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class StackViewLayout extends React.Component {
|
||||||
|
/**
|
||||||
|
* Used to identify the starting point of the position when the gesture starts, such that it can
|
||||||
|
* be updated according to its relative position. This means that a card can effectively be
|
||||||
|
* "caught"- If a gesture starts while a card is animating, the card does not jump into a
|
||||||
|
* corresponding location for the touch.
|
||||||
|
*/
|
||||||
|
_gestureStartValue = 0;
|
||||||
|
|
||||||
|
// tracks if a touch is currently happening
|
||||||
|
_isResponding = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* immediateIndex is used to represent the expected index that we will be on after a
|
||||||
|
* transition. To achieve a smooth animation when swiping back, the action to go back
|
||||||
|
* doesn't actually fire until the transition completes. The immediateIndex is used during
|
||||||
|
* the transition so that gestures can be handled correctly. This is a work-around for
|
||||||
|
* cases when the user quickly swipes back several times.
|
||||||
|
*/
|
||||||
|
_immediateIndex = null;
|
||||||
|
|
||||||
|
// _panResponder = PanResponder.create({
|
||||||
|
// onPanResponderTerminate: () => {
|
||||||
|
// this._isResponding = false;
|
||||||
|
// this._reset(index, 0);
|
||||||
|
// },
|
||||||
|
// onPanResponderGrant: () => {
|
||||||
|
// position.stopAnimation((value: number) => {
|
||||||
|
// this._isResponding = true;
|
||||||
|
// this._gestureStartValue = value;
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// onMoveShouldSetPanResponder: (event, gesture) => {
|
||||||
|
// if (index !== scene.index) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// const immediateIndex =
|
||||||
|
// this._immediateIndex == null ? index : this._immediateIndex;
|
||||||
|
// const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
||||||
|
// const currentDragPosition =
|
||||||
|
// event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
|
||||||
|
// const axisLength = isVertical
|
||||||
|
// ? layout.height.__getValue()
|
||||||
|
// : layout.width.__getValue();
|
||||||
|
// const axisHasBeenMeasured = !!axisLength;
|
||||||
|
|
||||||
|
// // Measure the distance from the touch to the edge of the screen
|
||||||
|
// const screenEdgeDistance = gestureDirectionInverted
|
||||||
|
// ? axisLength - (currentDragPosition - currentDragDistance)
|
||||||
|
// : currentDragPosition - currentDragDistance;
|
||||||
|
// // Compare to the gesture distance relavant to card or modal
|
||||||
|
|
||||||
|
// const { options } = scene.descriptor;
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// gestureResponseDistance: userGestureResponseDistance = {},
|
||||||
|
// } = options;
|
||||||
|
// const gestureResponseDistance = isVertical
|
||||||
|
// ? userGestureResponseDistance.vertical ||
|
||||||
|
// GESTURE_RESPONSE_DISTANCE_VERTICAL
|
||||||
|
// : userGestureResponseDistance.horizontal ||
|
||||||
|
// GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
|
||||||
|
// // GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
|
||||||
|
// if (screenEdgeDistance > gestureResponseDistance) {
|
||||||
|
// // Reject touches that started in the middle of the screen
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const hasDraggedEnough =
|
||||||
|
// Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
|
||||||
|
|
||||||
|
// const isOnFirstCard = immediateIndex === 0;
|
||||||
|
// const shouldSetResponder =
|
||||||
|
// hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
|
||||||
|
// return shouldSetResponder;
|
||||||
|
// },
|
||||||
|
// onPanResponderMove: (event, gesture) => {
|
||||||
|
// // Handle the moving touches for our granted responder
|
||||||
|
// const startValue = this._gestureStartValue;
|
||||||
|
// const axis = isVertical ? 'dy' : 'dx';
|
||||||
|
// const axisDistance = isVertical
|
||||||
|
// ? layout.height.__getValue()
|
||||||
|
// : layout.width.__getValue();
|
||||||
|
// const currentValue =
|
||||||
|
// (I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
|
||||||
|
// ? startValue + gesture[axis] / axisDistance
|
||||||
|
// : startValue - gesture[axis] / axisDistance;
|
||||||
|
// const value = clamp(index - 1, currentValue, index);
|
||||||
|
// position.setValue(value);
|
||||||
|
// },
|
||||||
|
// onPanResponderTerminationRequest: () =>
|
||||||
|
// // Returning false will prevent other views from becoming responder while
|
||||||
|
// // the navigation view is the responder (mid-gesture)
|
||||||
|
// false,
|
||||||
|
// onPanResponderRelease: (event, gesture) => {
|
||||||
|
// if (!this._isResponding) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// this._isResponding = false;
|
||||||
|
|
||||||
|
// const immediateIndex =
|
||||||
|
// this._immediateIndex == null ? index : this._immediateIndex;
|
||||||
|
|
||||||
|
// // Calculate animate duration according to gesture speed and moved distance
|
||||||
|
// const axisDistance = isVertical
|
||||||
|
// ? layout.height.__getValue()
|
||||||
|
// : layout.width.__getValue();
|
||||||
|
// const movementDirection = gestureDirectionInverted ? -1 : 1;
|
||||||
|
// const movedDistance =
|
||||||
|
// movementDirection * gesture[isVertical ? 'dy' : 'dx'];
|
||||||
|
// const gestureVelocity =
|
||||||
|
// movementDirection * gesture[isVertical ? 'vy' : 'vx'];
|
||||||
|
// const defaultVelocity = axisDistance / ANIMATION_DURATION;
|
||||||
|
// const velocity = Math.max(Math.abs(gestureVelocity), defaultVelocity);
|
||||||
|
// const resetDuration = gestureDirectionInverted
|
||||||
|
// ? (axisDistance - movedDistance) / velocity
|
||||||
|
// : movedDistance / velocity;
|
||||||
|
// const goBackDuration = gestureDirectionInverted
|
||||||
|
// ? movedDistance / velocity
|
||||||
|
// : (axisDistance - movedDistance) / velocity;
|
||||||
|
|
||||||
|
// // To asyncronously get the current animated value, we need to run stopAnimation:
|
||||||
|
// position.stopAnimation(value => {
|
||||||
|
// // If the speed of the gesture release is significant, use that as the indication
|
||||||
|
// // of intent
|
||||||
|
// if (gestureVelocity < -0.5) {
|
||||||
|
// this._reset(immediateIndex, resetDuration);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (gestureVelocity > 0.5) {
|
||||||
|
// this._goBack(immediateIndex, goBackDuration);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Then filter based on the distance the screen was moved. Over a third of the way swiped,
|
||||||
|
// // and the back will happen.
|
||||||
|
// if (value <= index - POSITION_THRESHOLD) {
|
||||||
|
// this._goBack(immediateIndex, goBackDuration);
|
||||||
|
// } else {
|
||||||
|
// this._reset(immediateIndex, resetDuration);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
_renderHeader(descriptor, headerMode) {
|
||||||
|
const { options } = descriptor;
|
||||||
|
const { header } = options;
|
||||||
|
|
||||||
|
if (typeof header !== 'undefined' && typeof header !== 'function') {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const renderHeader = header || (props => <Header {...props} />);
|
||||||
|
const renderHeader = header || (props => null);
|
||||||
|
const {
|
||||||
|
headerLeftInterpolator,
|
||||||
|
headerTitleInterpolator,
|
||||||
|
headerRightInterpolator,
|
||||||
|
} = this._getTransitionConfig();
|
||||||
|
|
||||||
|
const {
|
||||||
|
mode,
|
||||||
|
transitionProps,
|
||||||
|
prevTransitionProps,
|
||||||
|
...passProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return renderHeader({
|
||||||
|
...passProps,
|
||||||
|
...transitionProps,
|
||||||
|
descriptor,
|
||||||
|
mode: headerMode,
|
||||||
|
transitionPreset: this._getHeaderTransitionPreset(),
|
||||||
|
leftInterpolator: headerLeftInterpolator,
|
||||||
|
titleInterpolator: headerTitleInterpolator,
|
||||||
|
rightInterpolator: headerRightInterpolator,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
_animatedSubscribe(props) {
|
||||||
|
// Hack to make this work with native driven animations. We add a single listener
|
||||||
|
// so the JS value of the following animated values gets updated. We rely on
|
||||||
|
// some Animated private APIs and not doing so would require using a bunch of
|
||||||
|
// value listeners but we'd have to remove them to not leak and I'm not sure
|
||||||
|
// 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.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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _goBack(backFromIndex, duration) {
|
||||||
|
// const { navigation, position, scenes } = this.props.transitionProps;
|
||||||
|
// 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 = () => {
|
||||||
|
// this._immediateIndex = null;
|
||||||
|
// const backFromScene = scenes.find(s => s.index === toValue + 1);
|
||||||
|
// if (!this._isResponding && backFromScene) {
|
||||||
|
// navigation.dispatch(
|
||||||
|
// NavigationActions.back({
|
||||||
|
// key: backFromScene.route.key,
|
||||||
|
// immediate: true,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
transition,
|
||||||
|
descriptor,
|
||||||
|
descriptors,
|
||||||
|
layout,
|
||||||
|
mode,
|
||||||
|
} = this.props;
|
||||||
|
if (headerMode === 'float') {
|
||||||
|
floatingHeader = this._renderHeader(descriptor, headerMode);
|
||||||
|
}
|
||||||
|
const { index, routes } = navigation.state;
|
||||||
|
const isVertical = mode === 'modal';
|
||||||
|
const { options } = descriptor;
|
||||||
|
|
||||||
|
const gestureDirectionInverted = options.gestureDirection === 'inverted';
|
||||||
|
|
||||||
|
const gesturesEnabled =
|
||||||
|
typeof options.gesturesEnabled === 'boolean'
|
||||||
|
? options.gesturesEnabled
|
||||||
|
: Platform.OS === 'ios';
|
||||||
|
|
||||||
|
// const handlers = gesturesEnabled ? this._panResponder.panHandlers : {};
|
||||||
|
const handlers = {};
|
||||||
|
|
||||||
|
const containerStyle = [
|
||||||
|
styles.container,
|
||||||
|
this._getTransitionConfig().containerStyle,
|
||||||
|
];
|
||||||
|
|
||||||
|
let forwardScene = null;
|
||||||
|
let backwardScene = null;
|
||||||
|
|
||||||
|
if (transition) {
|
||||||
|
const { fromDescriptor, toDescriptor } = transition;
|
||||||
|
const fromKey = fromDescriptor.key;
|
||||||
|
const toKey = toDescriptor.key;
|
||||||
|
const toIndex = navigation.state.routes.findIndex(r => r.key === toKey);
|
||||||
|
invariant(
|
||||||
|
toIndex !== -1,
|
||||||
|
`Could not find toIndex in navigation state for ${fromKey}`
|
||||||
|
);
|
||||||
|
const fromIndex = navigation.state.routes.findIndex(
|
||||||
|
r => r.key === fromKey
|
||||||
|
);
|
||||||
|
if (fromIndex == -1) {
|
||||||
|
// we are coming from a screen that is no longer in the stack
|
||||||
|
backwardScene = fromDescriptor;
|
||||||
|
} else if (toIndex > fromIndex) {
|
||||||
|
// presumably we are going doing a push.
|
||||||
|
backwardScene = fromDescriptor;
|
||||||
|
} else {
|
||||||
|
// we are navigating back, and the forward scene is on top
|
||||||
|
forwardScene = fromDescriptor;
|
||||||
|
}
|
||||||
|
} else if (index > 0) {
|
||||||
|
// when we aren't transitioning, render the previous screen in case we swipe back.
|
||||||
|
const previousKey = routes[index - 1].key;
|
||||||
|
const previousDescriptor = descriptors[previousKey];
|
||||||
|
backwardScene = previousDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...handlers} style={containerStyle}>
|
||||||
|
<View style={styles.scenes}>
|
||||||
|
{backwardScene && this._renderScene(backwardScene, index - 1)}
|
||||||
|
{this._renderScene(descriptor, index)}
|
||||||
|
{forwardScene && this._renderScene(forwardScene, index + 1)}
|
||||||
|
</View>
|
||||||
|
{floatingHeader}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getHeaderMode() {
|
||||||
|
if (this.props.headerMode) {
|
||||||
|
return this.props.headerMode;
|
||||||
|
}
|
||||||
|
if (Platform.OS === 'android' || this.props.mode === 'modal') {
|
||||||
|
return 'screen';
|
||||||
|
}
|
||||||
|
return 'float';
|
||||||
|
}
|
||||||
|
|
||||||
|
_getHeaderTransitionPreset() {
|
||||||
|
// On Android or with header mode screen, we always just use in-place,
|
||||||
|
// we ignore the option entirely (at least until we have other presets)
|
||||||
|
if (Platform.OS === 'android' || this._getHeaderMode() === 'screen') {
|
||||||
|
return 'fade-in-place';
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: validations: 'fade-in-place' or 'uikit' are valid
|
||||||
|
if (this.props.headerTransitionPreset) {
|
||||||
|
return this.props.headerTransitionPreset;
|
||||||
|
} else {
|
||||||
|
return 'fade-in-place';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderInnerScene(descriptor) {
|
||||||
|
const { options, navigation, getComponent } = descriptor;
|
||||||
|
const SceneComponent = getComponent();
|
||||||
|
|
||||||
|
const { screenProps } = this.props;
|
||||||
|
const headerMode = this._getHeaderMode();
|
||||||
|
if (headerMode === 'screen') {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<SceneView
|
||||||
|
screenProps={screenProps}
|
||||||
|
navigation={navigation}
|
||||||
|
component={SceneComponent}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{this._renderHeader(descriptor, headerMode)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SceneView
|
||||||
|
screenProps={screenProps}
|
||||||
|
navigation={navigation}
|
||||||
|
component={SceneComponent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTransitionConfig = () => {
|
||||||
|
const isModal = this.props.mode === 'modal';
|
||||||
|
|
||||||
|
return Transitions.getTransitionConfig(
|
||||||
|
this.props.transitionConfig,
|
||||||
|
this.props,
|
||||||
|
isModal
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderScene = (descriptor, index) => {
|
||||||
|
const { screenInterpolator } = this._getTransitionConfig();
|
||||||
|
const style =
|
||||||
|
screenInterpolator &&
|
||||||
|
screenInterpolator({ ...this.props, descriptor, index });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
{...this.props}
|
||||||
|
// providing this descriptor will override this.props.descriptor, to tell the card exactly which scene to render, instead of this.props.descriptor, which defines what scene is active
|
||||||
|
descriptor={descriptor}
|
||||||
|
index={index}
|
||||||
|
key={`card_${descriptor.key}`}
|
||||||
|
style={[style, this.props.cardStyle]}
|
||||||
|
>
|
||||||
|
{this._renderInnerScene(descriptor)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
// Header is physically rendered after scenes so that Header won't be
|
||||||
|
// covered by the shadows of the scenes.
|
||||||
|
// That said, we'd have use `flexDirection: 'column-reverse'` to move
|
||||||
|
// Header above the scenes.
|
||||||
|
flexDirection: 'column-reverse',
|
||||||
|
},
|
||||||
|
scenes: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
class StackView extends React.Component {
|
||||||
|
static defaultProps = {
|
||||||
|
navigationConfig: {
|
||||||
|
mode: 'card',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Transitioner
|
||||||
|
render={this._render}
|
||||||
|
configureTransition={this._configureTransition}
|
||||||
|
navigation={this.props.navigation}
|
||||||
|
descriptors={this.props.descriptors}
|
||||||
|
// onTransitionStart={this.props.onTransitionStart}
|
||||||
|
// onTransitionEnd={(lastTransition, transition) => {
|
||||||
|
// const { onTransitionEnd, navigation } = this.props;
|
||||||
|
// navigation.dispatch(
|
||||||
|
// NavigationActions.completeTransition({
|
||||||
|
// key: navigation.state.key,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// onTransitionEnd && onTransitionEnd(lastTransition, transition);
|
||||||
|
// }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_configureTransition = transitionProps => {
|
||||||
|
return {
|
||||||
|
...Transitions.getTransitionConfig(
|
||||||
|
this.props.navigationConfig.transitionConfig,
|
||||||
|
transitionProps,
|
||||||
|
this.props.navigationConfig.mode === 'modal'
|
||||||
|
).transitionSpec,
|
||||||
|
useNativeDriver: !!NativeAnimatedModule,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_render = transitionProps => {
|
||||||
|
const { screenProps } = this.props;
|
||||||
|
return <StackViewLayout {...transitionProps} screenProps={screenProps} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackView;
|
||||||
@@ -9,29 +9,18 @@ import {
|
|||||||
View,
|
View,
|
||||||
I18nManager,
|
I18nManager,
|
||||||
Easing,
|
Easing,
|
||||||
Dimensions,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import Card from './StackViewCard';
|
import Card from './StackViewCard';
|
||||||
import Header from '../Header/Header';
|
import Header from '../Header/Header';
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import StackActions from '../../routers/StackActions';
|
|
||||||
import SceneView from '../SceneView';
|
import SceneView from '../SceneView';
|
||||||
import withOrientation from '../withOrientation';
|
|
||||||
import { NavigationProvider } from '../NavigationContext';
|
|
||||||
|
|
||||||
import TransitionConfigs from './StackViewTransitionConfigs';
|
import TransitionConfigs from './StackViewTransitionConfigs';
|
||||||
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
||||||
|
|
||||||
const emptyFunction = () => {};
|
const emptyFunction = () => {};
|
||||||
|
|
||||||
const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window');
|
|
||||||
const IS_IPHONE_X =
|
|
||||||
Platform.OS === 'ios' &&
|
|
||||||
!Platform.isPad &&
|
|
||||||
!Platform.isTVOS &&
|
|
||||||
(WINDOW_HEIGHT === 812 || WINDOW_WIDTH === 812);
|
|
||||||
|
|
||||||
const EaseInOut = Easing.inOut(Easing.ease);
|
const EaseInOut = Easing.inOut(Easing.ease);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,18 +82,11 @@ class StackViewLayout extends React.Component {
|
|||||||
const { options } = scene.descriptor;
|
const { options } = scene.descriptor;
|
||||||
const { header } = options;
|
const { header } = options;
|
||||||
|
|
||||||
if (header === null && headerMode === 'screen') {
|
if (typeof header !== 'undefined' && typeof header !== 'function') {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it's a react element
|
|
||||||
if (React.isValidElement(header)) {
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the case where the header option is a function, and provide the default
|
const renderHeader = header || ((props: *) => <Header {...props} />);
|
||||||
const renderHeader = header || (props => <Header {...props} />);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
headerLeftInterpolator,
|
headerLeftInterpolator,
|
||||||
headerTitleInterpolator,
|
headerTitleInterpolator,
|
||||||
@@ -184,7 +166,6 @@ class StackViewLayout extends React.Component {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
navigation.dispatch(StackActions.completeTransition());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,197 +190,13 @@ class StackViewLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_panResponder = PanResponder.create({
|
|
||||||
onPanResponderTerminate: () => {
|
|
||||||
const { navigation } = this.props.transitionProps;
|
|
||||||
const { index } = navigation.state;
|
|
||||||
this._isResponding = false;
|
|
||||||
this._reset(index, 0);
|
|
||||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
|
||||||
},
|
|
||||||
onPanResponderGrant: () => {
|
|
||||||
const {
|
|
||||||
transitionProps: { navigation, position, scene },
|
|
||||||
} = this.props;
|
|
||||||
const { index } = navigation.state;
|
|
||||||
|
|
||||||
if (index !== scene.index) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
position.stopAnimation((value: number) => {
|
|
||||||
this._isResponding = true;
|
|
||||||
this._gestureStartValue = value;
|
|
||||||
});
|
|
||||||
this.props.onGestureBegin && this.props.onGestureBegin();
|
|
||||||
},
|
|
||||||
onMoveShouldSetPanResponder: (event, gesture) => {
|
|
||||||
const {
|
|
||||||
transitionProps: { navigation, position, layout, scene, scenes },
|
|
||||||
mode,
|
|
||||||
} = this.props;
|
|
||||||
const { index } = navigation.state;
|
|
||||||
const isVertical = mode === 'modal';
|
|
||||||
const { options } = scene.descriptor;
|
|
||||||
const gestureDirection = options.gestureDirection;
|
|
||||||
|
|
||||||
const gestureDirectionInverted =
|
|
||||||
typeof gestureDirection === 'string'
|
|
||||||
? gestureDirection === 'inverted'
|
|
||||||
: I18nManager.isRTL;
|
|
||||||
|
|
||||||
if (index !== scene.index) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const immediateIndex =
|
|
||||||
this._immediateIndex == null ? index : this._immediateIndex;
|
|
||||||
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
|
||||||
const currentDragPosition =
|
|
||||||
event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
|
|
||||||
const axisLength = isVertical
|
|
||||||
? layout.height.__getValue()
|
|
||||||
: layout.width.__getValue();
|
|
||||||
const axisHasBeenMeasured = !!axisLength;
|
|
||||||
|
|
||||||
// Measure the distance from the touch to the edge of the screen
|
|
||||||
const screenEdgeDistance = gestureDirectionInverted
|
|
||||||
? axisLength - (currentDragPosition - currentDragDistance)
|
|
||||||
: currentDragPosition - currentDragDistance;
|
|
||||||
// Compare to the gesture distance relavant to card or modal
|
|
||||||
|
|
||||||
const {
|
|
||||||
gestureResponseDistance: userGestureResponseDistance = {},
|
|
||||||
} = options;
|
|
||||||
const gestureResponseDistance = isVertical
|
|
||||||
? userGestureResponseDistance.vertical ||
|
|
||||||
GESTURE_RESPONSE_DISTANCE_VERTICAL
|
|
||||||
: userGestureResponseDistance.horizontal ||
|
|
||||||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
|
|
||||||
// GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
|
|
||||||
if (screenEdgeDistance > gestureResponseDistance) {
|
|
||||||
// Reject touches that started in the middle of the screen
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasDraggedEnough =
|
|
||||||
Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
|
|
||||||
|
|
||||||
const isOnFirstCard = immediateIndex === 0;
|
|
||||||
const shouldSetResponder =
|
|
||||||
hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
|
|
||||||
return shouldSetResponder;
|
|
||||||
},
|
|
||||||
onPanResponderMove: (event, gesture) => {
|
|
||||||
const {
|
|
||||||
transitionProps: { navigation, position, layout, scene },
|
|
||||||
mode,
|
|
||||||
} = this.props;
|
|
||||||
const { index } = navigation.state;
|
|
||||||
const isVertical = mode === 'modal';
|
|
||||||
const { options } = scene.descriptor;
|
|
||||||
const gestureDirection = options.gestureDirection;
|
|
||||||
|
|
||||||
const gestureDirectionInverted =
|
|
||||||
typeof gestureDirection === 'string'
|
|
||||||
? gestureDirection === 'inverted'
|
|
||||||
: I18nManager.isRTL;
|
|
||||||
|
|
||||||
// Handle the moving touches for our granted responder
|
|
||||||
const startValue = this._gestureStartValue;
|
|
||||||
const axis = isVertical ? 'dy' : 'dx';
|
|
||||||
const axisDistance = isVertical
|
|
||||||
? layout.height.__getValue()
|
|
||||||
: layout.width.__getValue();
|
|
||||||
const currentValue =
|
|
||||||
axis === 'dx' && gestureDirectionInverted
|
|
||||||
? startValue + gesture[axis] / axisDistance
|
|
||||||
: startValue - gesture[axis] / axisDistance;
|
|
||||||
const value = clamp(index - 1, currentValue, index);
|
|
||||||
position.setValue(value);
|
|
||||||
},
|
|
||||||
onPanResponderTerminationRequest: () =>
|
|
||||||
// Returning false will prevent other views from becoming responder while
|
|
||||||
// the navigation view is the responder (mid-gesture)
|
|
||||||
false,
|
|
||||||
onPanResponderRelease: (event, gesture) => {
|
|
||||||
const {
|
|
||||||
transitionProps: { navigation, position, layout, scene },
|
|
||||||
mode,
|
|
||||||
} = this.props;
|
|
||||||
const { index } = navigation.state;
|
|
||||||
const isVertical = mode === 'modal';
|
|
||||||
const { options } = scene.descriptor;
|
|
||||||
const gestureDirection = options.gestureDirection;
|
|
||||||
|
|
||||||
const gestureDirectionInverted =
|
|
||||||
typeof gestureDirection === 'string'
|
|
||||||
? gestureDirection === 'inverted'
|
|
||||||
: I18nManager.isRTL;
|
|
||||||
|
|
||||||
if (!this._isResponding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._isResponding = false;
|
|
||||||
|
|
||||||
const immediateIndex =
|
|
||||||
this._immediateIndex == null ? index : this._immediateIndex;
|
|
||||||
|
|
||||||
// Calculate animate duration according to gesture speed and moved distance
|
|
||||||
const axisDistance = isVertical
|
|
||||||
? layout.height.__getValue()
|
|
||||||
: layout.width.__getValue();
|
|
||||||
const movementDirection = gestureDirectionInverted ? -1 : 1;
|
|
||||||
const movedDistance =
|
|
||||||
movementDirection * gesture[isVertical ? 'dy' : 'dx'];
|
|
||||||
const gestureVelocity =
|
|
||||||
movementDirection * gesture[isVertical ? 'vy' : 'vx'];
|
|
||||||
const defaultVelocity = axisDistance / ANIMATION_DURATION;
|
|
||||||
const velocity = Math.max(Math.abs(gestureVelocity), defaultVelocity);
|
|
||||||
const resetDuration = gestureDirectionInverted
|
|
||||||
? (axisDistance - movedDistance) / velocity
|
|
||||||
: movedDistance / velocity;
|
|
||||||
const goBackDuration = gestureDirectionInverted
|
|
||||||
? movedDistance / velocity
|
|
||||||
: (axisDistance - movedDistance) / velocity;
|
|
||||||
|
|
||||||
// To asyncronously get the current animated value, we need to run stopAnimation:
|
|
||||||
position.stopAnimation(value => {
|
|
||||||
// If the speed of the gesture release is significant, use that as the indication
|
|
||||||
// of intent
|
|
||||||
if (gestureVelocity < -0.5) {
|
|
||||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
|
||||||
this._reset(immediateIndex, resetDuration);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gestureVelocity > 0.5) {
|
|
||||||
this.props.onGestureFinish && this.props.onGestureFinish();
|
|
||||||
this._goBack(immediateIndex, goBackDuration);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
|
|
||||||
// and the back will happen.
|
|
||||||
if (value <= index - POSITION_THRESHOLD) {
|
|
||||||
this.props.onGestureFinish && this.props.onGestureFinish();
|
|
||||||
this._goBack(immediateIndex, goBackDuration);
|
|
||||||
} else {
|
|
||||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
|
||||||
this._reset(immediateIndex, resetDuration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let floatingHeader = null;
|
let floatingHeader = null;
|
||||||
const headerMode = this._getHeaderMode();
|
const headerMode = this._getHeaderMode();
|
||||||
if (headerMode === 'float') {
|
if (headerMode === 'float') {
|
||||||
const { scene } = this.props.transitionProps;
|
floatingHeader = this._renderHeader(
|
||||||
floatingHeader = (
|
this.props.transitionProps.scene,
|
||||||
<NavigationProvider value={scene.descriptor.navigation}>
|
headerMode
|
||||||
{this._renderHeader(scene, headerMode)}
|
|
||||||
</NavigationProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@@ -409,19 +206,142 @@ class StackViewLayout extends React.Component {
|
|||||||
const { index } = navigation.state;
|
const { index } = navigation.state;
|
||||||
const isVertical = mode === 'modal';
|
const isVertical = mode === 'modal';
|
||||||
const { options } = scene.descriptor;
|
const { options } = scene.descriptor;
|
||||||
const gestureDirection = options.gestureDirection;
|
|
||||||
|
|
||||||
const gestureDirectionInverted =
|
const gestureDirectionInverted = options.gestureDirection === 'inverted';
|
||||||
typeof gestureDirection === 'string'
|
|
||||||
? gestureDirection === 'inverted'
|
|
||||||
: I18nManager.isRTL;
|
|
||||||
|
|
||||||
const gesturesEnabled =
|
const gesturesEnabled =
|
||||||
typeof options.gesturesEnabled === 'boolean'
|
typeof options.gesturesEnabled === 'boolean'
|
||||||
? options.gesturesEnabled
|
? options.gesturesEnabled
|
||||||
: Platform.OS === 'ios';
|
: Platform.OS === 'ios';
|
||||||
|
|
||||||
const responder = !gesturesEnabled ? null : this._panResponder;
|
const responder = !gesturesEnabled
|
||||||
|
? null
|
||||||
|
: PanResponder.create({
|
||||||
|
onPanResponderTerminate: () => {
|
||||||
|
this._isResponding = false;
|
||||||
|
this._reset(index, 0);
|
||||||
|
},
|
||||||
|
onPanResponderGrant: () => {
|
||||||
|
position.stopAnimation((value: number) => {
|
||||||
|
this._isResponding = true;
|
||||||
|
this._gestureStartValue = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onMoveShouldSetPanResponder: (event, gesture) => {
|
||||||
|
if (index !== scene.index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const immediateIndex =
|
||||||
|
this._immediateIndex == null ? index : this._immediateIndex;
|
||||||
|
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
||||||
|
const currentDragPosition =
|
||||||
|
event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
|
||||||
|
const axisLength = isVertical
|
||||||
|
? layout.height.__getValue()
|
||||||
|
: layout.width.__getValue();
|
||||||
|
const axisHasBeenMeasured = !!axisLength;
|
||||||
|
|
||||||
|
// Measure the distance from the touch to the edge of the screen
|
||||||
|
const screenEdgeDistance = gestureDirectionInverted
|
||||||
|
? axisLength - (currentDragPosition - currentDragDistance)
|
||||||
|
: currentDragPosition - currentDragDistance;
|
||||||
|
// Compare to the gesture distance relavant to card or modal
|
||||||
|
|
||||||
|
const { options } = scene.descriptor;
|
||||||
|
|
||||||
|
const {
|
||||||
|
gestureResponseDistance: userGestureResponseDistance = {},
|
||||||
|
} = options;
|
||||||
|
const gestureResponseDistance = isVertical
|
||||||
|
? userGestureResponseDistance.vertical ||
|
||||||
|
GESTURE_RESPONSE_DISTANCE_VERTICAL
|
||||||
|
: userGestureResponseDistance.horizontal ||
|
||||||
|
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
|
||||||
|
// GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
|
||||||
|
if (screenEdgeDistance > gestureResponseDistance) {
|
||||||
|
// Reject touches that started in the middle of the screen
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDraggedEnough =
|
||||||
|
Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
|
||||||
|
|
||||||
|
const isOnFirstCard = immediateIndex === 0;
|
||||||
|
const shouldSetResponder =
|
||||||
|
hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
|
||||||
|
return shouldSetResponder;
|
||||||
|
},
|
||||||
|
onPanResponderMove: (event, gesture) => {
|
||||||
|
// Handle the moving touches for our granted responder
|
||||||
|
const startValue = this._gestureStartValue;
|
||||||
|
const axis = isVertical ? 'dy' : 'dx';
|
||||||
|
const axisDistance = isVertical
|
||||||
|
? layout.height.__getValue()
|
||||||
|
: layout.width.__getValue();
|
||||||
|
const currentValue =
|
||||||
|
(I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
|
||||||
|
? startValue + gesture[axis] / axisDistance
|
||||||
|
: startValue - gesture[axis] / axisDistance;
|
||||||
|
const value = clamp(index - 1, currentValue, index);
|
||||||
|
position.setValue(value);
|
||||||
|
},
|
||||||
|
onPanResponderTerminationRequest: () =>
|
||||||
|
// Returning false will prevent other views from becoming responder while
|
||||||
|
// the navigation view is the responder (mid-gesture)
|
||||||
|
false,
|
||||||
|
onPanResponderRelease: (event, gesture) => {
|
||||||
|
if (!this._isResponding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._isResponding = false;
|
||||||
|
|
||||||
|
const immediateIndex =
|
||||||
|
this._immediateIndex == null ? index : this._immediateIndex;
|
||||||
|
|
||||||
|
// Calculate animate duration according to gesture speed and moved distance
|
||||||
|
const axisDistance = isVertical
|
||||||
|
? layout.height.__getValue()
|
||||||
|
: layout.width.__getValue();
|
||||||
|
const movementDirection = gestureDirectionInverted ? -1 : 1;
|
||||||
|
const movedDistance =
|
||||||
|
movementDirection * gesture[isVertical ? 'dy' : 'dx'];
|
||||||
|
const gestureVelocity =
|
||||||
|
movementDirection * gesture[isVertical ? 'vy' : 'vx'];
|
||||||
|
const defaultVelocity = axisDistance / ANIMATION_DURATION;
|
||||||
|
const velocity = Math.max(
|
||||||
|
Math.abs(gestureVelocity),
|
||||||
|
defaultVelocity
|
||||||
|
);
|
||||||
|
const resetDuration = gestureDirectionInverted
|
||||||
|
? (axisDistance - movedDistance) / velocity
|
||||||
|
: movedDistance / velocity;
|
||||||
|
const goBackDuration = gestureDirectionInverted
|
||||||
|
? movedDistance / velocity
|
||||||
|
: (axisDistance - movedDistance) / velocity;
|
||||||
|
|
||||||
|
// To asyncronously get the current animated value, we need to run stopAnimation:
|
||||||
|
position.stopAnimation(value => {
|
||||||
|
// If the speed of the gesture release is significant, use that as the indication
|
||||||
|
// of intent
|
||||||
|
if (gestureVelocity < -0.5) {
|
||||||
|
this._reset(immediateIndex, resetDuration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gestureVelocity > 0.5) {
|
||||||
|
this._goBack(immediateIndex, goBackDuration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
|
||||||
|
// and the back will happen.
|
||||||
|
if (value <= index - POSITION_THRESHOLD) {
|
||||||
|
this._goBack(immediateIndex, goBackDuration);
|
||||||
|
} else {
|
||||||
|
this._reset(immediateIndex, resetDuration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handlers = gesturesEnabled ? responder.panHandlers : {};
|
const handlers = gesturesEnabled ? responder.panHandlers : {};
|
||||||
const containerStyle = [
|
const containerStyle = [
|
||||||
@@ -432,7 +352,7 @@ class StackViewLayout extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<View {...handlers} style={containerStyle}>
|
<View {...handlers} style={containerStyle}>
|
||||||
<View style={styles.scenes}>
|
<View style={styles.scenes}>
|
||||||
{scenes.map(s => this._renderCard(s))}
|
{scenes.map((s: *) => this._renderCard(s))}
|
||||||
</View>
|
</View>
|
||||||
{floatingHeader}
|
{floatingHeader}
|
||||||
</View>
|
</View>
|
||||||
@@ -473,7 +393,7 @@ class StackViewLayout extends React.Component {
|
|||||||
if (headerMode === 'screen') {
|
if (headerMode === 'screen') {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.scenes}>
|
<View style={{ flex: 1 }}>
|
||||||
<SceneView
|
<SceneView
|
||||||
screenProps={screenProps}
|
screenProps={screenProps}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
@@ -510,36 +430,11 @@ class StackViewLayout extends React.Component {
|
|||||||
screenInterpolator &&
|
screenInterpolator &&
|
||||||
screenInterpolator({ ...this.props.transitionProps, scene });
|
screenInterpolator({ ...this.props.transitionProps, scene });
|
||||||
|
|
||||||
// If this screen has "header" set to `null` in it's navigation options, but
|
|
||||||
// it exists in a stack with headerMode float, add a negative margin to
|
|
||||||
// compensate for the hidden header
|
|
||||||
const { options } = scene.descriptor;
|
|
||||||
const hasHeader = options.header !== null;
|
|
||||||
const headerMode = this._getHeaderMode();
|
|
||||||
let marginTop = 0;
|
|
||||||
if (!hasHeader && headerMode === 'float') {
|
|
||||||
const { isLandscape } = this.props;
|
|
||||||
let headerHeight;
|
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
if (isLandscape && !Platform.isPad) {
|
|
||||||
headerHeight = 52;
|
|
||||||
} else if (IS_IPHONE_X) {
|
|
||||||
headerHeight = 88;
|
|
||||||
} else {
|
|
||||||
headerHeight = 64;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
headerHeight = 56;
|
|
||||||
// TODO (Android only): Need to handle translucent status bar.
|
|
||||||
}
|
|
||||||
marginTop = -headerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
{...this.props.transitionProps}
|
{...this.props.transitionProps}
|
||||||
key={`card_${scene.key}`}
|
key={`card_${scene.key}`}
|
||||||
style={[style, { marginTop }, this.props.cardStyle]}
|
style={[style, this.props.cardStyle]}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
>
|
>
|
||||||
{this._renderInnerScene(scene)}
|
{this._renderInnerScene(scene)}
|
||||||
@@ -562,4 +457,4 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withOrientation(StackViewLayout);
|
export default StackViewLayout;
|
||||||
|
|||||||
@@ -60,21 +60,19 @@ const FadeOutToBottomAndroid = {
|
|||||||
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
|
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
|
||||||
};
|
};
|
||||||
|
|
||||||
function defaultTransitionConfig(
|
function defaultTransitionConfig(transitionProps, isModal) {
|
||||||
transitionProps,
|
|
||||||
prevTransitionProps,
|
|
||||||
isModal
|
|
||||||
) {
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
// Use the default Android animation no matter if the screen is a modal.
|
// todo, uncomment and fix, stop using prevTransitionProps
|
||||||
// Android doesn't have full-screen modals like iOS does, it has dialogs.
|
|
||||||
if (
|
// // Use the default Android animation no matter if the screen is a modal.
|
||||||
prevTransitionProps &&
|
// // Android doesn't have full-screen modals like iOS does, it has dialogs.
|
||||||
transitionProps.index < prevTransitionProps.index
|
// if (
|
||||||
) {
|
// prevTransitionProps &&
|
||||||
// Navigating back to the previous screen
|
// transitionProps.index < prevTransitionProps.index
|
||||||
return FadeOutToBottomAndroid;
|
// ) {
|
||||||
}
|
// // Navigating back to the previous screen
|
||||||
|
// return FadeOutToBottomAndroid;
|
||||||
|
// }
|
||||||
return FadeInFromBottomAndroid;
|
return FadeInFromBottomAndroid;
|
||||||
}
|
}
|
||||||
// iOS and other platforms
|
// iOS and other platforms
|
||||||
@@ -84,21 +82,12 @@ function defaultTransitionConfig(
|
|||||||
return SlideFromRightIOS;
|
return SlideFromRightIOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTransitionConfig(
|
function getTransitionConfig(transitionConfigurer, transitionProps, isModal) {
|
||||||
transitionConfigurer,
|
const defaultConfig = defaultTransitionConfig(transitionProps, isModal);
|
||||||
transitionProps,
|
|
||||||
prevTransitionProps,
|
|
||||||
isModal
|
|
||||||
) {
|
|
||||||
const defaultConfig = defaultTransitionConfig(
|
|
||||||
transitionProps,
|
|
||||||
prevTransitionProps,
|
|
||||||
isModal
|
|
||||||
);
|
|
||||||
if (transitionConfigurer) {
|
if (transitionConfigurer) {
|
||||||
return {
|
return {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...transitionConfigurer(transitionProps, prevTransitionProps, isModal),
|
...transitionConfigurer(transitionProps, isModal),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
|
|||||||
268
src/views/StackView/StackViewTransitions.js
Normal file
268
src/views/StackView/StackViewTransitions.js
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import { Animated, Easing, Platform } from 'react-native';
|
||||||
|
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
||||||
|
|
||||||
|
import { I18nManager } from 'react-native';
|
||||||
|
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility that builds the style for the card in the cards stack.
|
||||||
|
*
|
||||||
|
* +------------+
|
||||||
|
* +-+ |
|
||||||
|
* +-+ | |
|
||||||
|
* | | | |
|
||||||
|
* | | | Focused |
|
||||||
|
* | | | Card |
|
||||||
|
* | | | |
|
||||||
|
* +-+ | |
|
||||||
|
* +-+ |
|
||||||
|
* +------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the initial style when the initial layout isn't measured yet.
|
||||||
|
*/
|
||||||
|
function forInitial(props) {
|
||||||
|
const { navigation, descriptor } = props;
|
||||||
|
const { state } = navigation;
|
||||||
|
const activeKey = state.routes[state.index].key;
|
||||||
|
|
||||||
|
const focused = descriptor.key === activeKey;
|
||||||
|
const opacity = focused ? 1 : 0;
|
||||||
|
// If not focused, move the card far away.
|
||||||
|
const translate = focused ? 0 : 1000000;
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
transform: [{ translateX: translate }, { translateY: translate }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard iOS-style slide in from the right.
|
||||||
|
*/
|
||||||
|
function forHorizontal(props) {
|
||||||
|
const { layout, transition, navigation, index } = props;
|
||||||
|
const { state } = navigation;
|
||||||
|
if (!layout.isMeasured) {
|
||||||
|
return forInitial(props);
|
||||||
|
}
|
||||||
|
const first = index - 1;
|
||||||
|
const last = index + 1;
|
||||||
|
const opacity = transition
|
||||||
|
? transition.progress.interpolate({
|
||||||
|
inputRange: [first, first + 0.01, index, last - 0.01, last],
|
||||||
|
outputRange: [0, 1, 1, 0.85, 0],
|
||||||
|
})
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const width = layout.initWidth;
|
||||||
|
const translateX = transition
|
||||||
|
? transition.progress.interpolate({
|
||||||
|
inputRange: [first, index, last],
|
||||||
|
outputRange: I18nManager.isRTL
|
||||||
|
? [-width, 0, width * 0.3]
|
||||||
|
: [width, 0, width * -0.3],
|
||||||
|
})
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
transform: [{ translateX }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard iOS-style slide in from the bottom (used for modals).
|
||||||
|
*/
|
||||||
|
function forVertical(props) {
|
||||||
|
const { layout, transition, descriptor } = props;
|
||||||
|
|
||||||
|
if (!layout.isMeasured) {
|
||||||
|
return forInitial(props);
|
||||||
|
}
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
const opacity = transition.progress.interpolate({
|
||||||
|
inputRange: [first, first + 0.01, index, last - 0.01, last],
|
||||||
|
outputRange: [0, 1, 1, 0.85, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
const height = layout.initHeight;
|
||||||
|
const translateY = transition.progress.interpolate({
|
||||||
|
inputRange: [first, index, last],
|
||||||
|
outputRange: [height, 0, 0],
|
||||||
|
});
|
||||||
|
const translateX = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
transform: [{ translateX }, { translateY }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard Android-style fade in from the bottom.
|
||||||
|
*/
|
||||||
|
function forFadeFromBottomAndroid(props) {
|
||||||
|
const { layout, position, scene } = props;
|
||||||
|
|
||||||
|
if (!layout.isMeasured) {
|
||||||
|
return forInitial(props);
|
||||||
|
}
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
const inputRange = [first, index, last - 0.01, last];
|
||||||
|
|
||||||
|
const opacity = position.interpolate({
|
||||||
|
inputRange,
|
||||||
|
outputRange: [0, 1, 1, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
const translateY = position.interpolate({
|
||||||
|
inputRange,
|
||||||
|
outputRange: [50, 0, 0, 0],
|
||||||
|
});
|
||||||
|
const translateX = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
transform: [{ translateX }, { translateY }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fadeIn and fadeOut
|
||||||
|
*/
|
||||||
|
function forFade(props) {
|
||||||
|
const { layout, position, scene } = props;
|
||||||
|
|
||||||
|
if (!layout.isMeasured) {
|
||||||
|
return forInitial(props);
|
||||||
|
}
|
||||||
|
const interpolate = getSceneIndicesForInterpolationInputRange(props);
|
||||||
|
|
||||||
|
if (!interpolate) return { opacity: 0 };
|
||||||
|
|
||||||
|
const { first, last } = interpolate;
|
||||||
|
const index = scene.index;
|
||||||
|
const opacity = position.interpolate({
|
||||||
|
inputRange: [first, index, last],
|
||||||
|
outputRange: [0, 1, 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyleInterpolator = {
|
||||||
|
forHorizontal,
|
||||||
|
forVertical,
|
||||||
|
forFadeFromBottomAndroid,
|
||||||
|
forFade,
|
||||||
|
};
|
||||||
|
|
||||||
|
let IOSTransitionSpec;
|
||||||
|
if (ReactNativeFeatures.supportsImprovedSpringAnimation()) {
|
||||||
|
// These are the exact values from UINavigationController's animation configuration
|
||||||
|
IOSTransitionSpec = {
|
||||||
|
timing: Animated.spring,
|
||||||
|
stiffness: 1000,
|
||||||
|
damping: 500,
|
||||||
|
mass: 3,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// This is an approximation of the IOS spring animation using a derived bezier curve
|
||||||
|
IOSTransitionSpec = {
|
||||||
|
duration: 500,
|
||||||
|
easing: Easing.bezier(0.2833, 0.99, 0.31833, 0.99),
|
||||||
|
timing: Animated.timing,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard iOS navigation transition
|
||||||
|
const SlideFromRightIOS = {
|
||||||
|
transitionSpec: IOSTransitionSpec,
|
||||||
|
screenInterpolator: StyleInterpolator.forHorizontal,
|
||||||
|
containerStyle: {
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard iOS navigation transition for modals
|
||||||
|
const ModalSlideFromBottomIOS = {
|
||||||
|
transitionSpec: IOSTransitionSpec,
|
||||||
|
screenInterpolator: StyleInterpolator.forVertical,
|
||||||
|
containerStyle: {
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard Android navigation transition when opening an Activity
|
||||||
|
const FadeInFromBottomAndroid = {
|
||||||
|
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
|
||||||
|
transitionSpec: {
|
||||||
|
duration: 350,
|
||||||
|
easing: Easing.out(Easing.poly(5)), // decelerate
|
||||||
|
timing: Animated.timing,
|
||||||
|
},
|
||||||
|
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard Android navigation transition when closing an Activity
|
||||||
|
const FadeOutToBottomAndroid = {
|
||||||
|
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
|
||||||
|
transitionSpec: {
|
||||||
|
duration: 230,
|
||||||
|
easing: Easing.in(Easing.poly(4)), // accelerate
|
||||||
|
timing: Animated.timing,
|
||||||
|
},
|
||||||
|
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
|
||||||
|
};
|
||||||
|
|
||||||
|
function defaultTransitionConfig(transitionProps, isModal) {
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
// todo, uncomment and fix, stop using prevTransitionProps
|
||||||
|
|
||||||
|
// // Use the default Android animation no matter if the screen is a modal.
|
||||||
|
// // Android doesn't have full-screen modals like iOS does, it has dialogs.
|
||||||
|
// if (
|
||||||
|
// prevTransitionProps &&
|
||||||
|
// transitionProps.index < prevTransitionProps.index
|
||||||
|
// ) {
|
||||||
|
// // Navigating back to the previous screen
|
||||||
|
// return FadeOutToBottomAndroid;
|
||||||
|
// }
|
||||||
|
return FadeInFromBottomAndroid;
|
||||||
|
}
|
||||||
|
// iOS and other platforms
|
||||||
|
if (isModal) {
|
||||||
|
return ModalSlideFromBottomIOS;
|
||||||
|
}
|
||||||
|
return SlideFromRightIOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransitionConfig(transitionConfigurer, transitionProps, isModal) {
|
||||||
|
const defaultConfig = defaultTransitionConfig(transitionProps, isModal);
|
||||||
|
if (transitionConfigurer) {
|
||||||
|
return {
|
||||||
|
...defaultConfig,
|
||||||
|
...transitionConfigurer(transitionProps, isModal),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
defaultTransitionConfig,
|
||||||
|
getTransitionConfig,
|
||||||
|
StyleInterpolator,
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user