mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-15 09:17:26 +08:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37ca6a92ca | ||
|
|
980e0409dc | ||
|
|
a00ba5918a | ||
|
|
ad6b25cff9 | ||
|
|
a69b67d6d2 | ||
|
|
dc436e4d01 | ||
|
|
fe95bdeee6 | ||
|
|
525528e38f | ||
|
|
9f5f3d994c | ||
|
|
e8c1833053 | ||
|
|
0921889f7a | ||
|
|
1951a3ac46 | ||
|
|
4e384f8057 | ||
|
|
3d06d19d6a | ||
|
|
30ef5ef72b | ||
|
|
c7fff52408 | ||
|
|
bc01a4cd57 | ||
|
|
cad3d70aed | ||
|
|
bb5719f438 | ||
|
|
3dd3f5b804 | ||
|
|
3d8d5a0634 | ||
|
|
54448ed070 | ||
|
|
369ac2b568 | ||
|
|
3dc592f679 | ||
|
|
4f93200c91 | ||
|
|
665736d754 | ||
|
|
5598c3e28f | ||
|
|
cde6e845cd | ||
|
|
fb8c712ad8 | ||
|
|
350b7e0aed | ||
|
|
de112565d3 | ||
|
|
acdd515c13 | ||
|
|
452a6d2004 | ||
|
|
08c8031a71 | ||
|
|
608365266a | ||
|
|
247fba56e6 | ||
|
|
060f5dcecf | ||
|
|
fdec05c87a | ||
|
|
76da804574 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -25,7 +25,7 @@ Bugs with react-navigation must be reproducible *without any external libraries
|
|||||||
|
|
||||||
### How to reproduce
|
### How to reproduce
|
||||||
|
|
||||||
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repoistory as that is outside of the scope of Rect Navigation.
|
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repoistory as that is outside of the scope of React Navigation.
|
||||||
- Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug.
|
- Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug.
|
||||||
- Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app.
|
- Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app.
|
||||||
|
|
||||||
|
|||||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,17 +1,21 @@
|
|||||||
Please provide enough information so that others can review your pull request:
|
Please provide enough information so that others can review your pull request:
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
Explain the **motivation** for making this change. What existing problem does the pull request solve?
|
Explain the **motivation** for making this change. What existing problem does the pull request solve?
|
||||||
|
|
||||||
Prefer **small pull requests**. These are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise split it.
|
## Test plan
|
||||||
|
|
||||||
**Test plan (required)**
|
Demonstrate the code is solid. Example: the exact commands you ran and their output, screenshots / videos if the pull request changes UI.
|
||||||
|
|
||||||
Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
|
|
||||||
|
|
||||||
Make sure you test on both platforms if your change affects both platforms.
|
Make sure you test on both platforms if your change affects both platforms.
|
||||||
|
|
||||||
The code must pass tests.
|
The code must pass tests.
|
||||||
|
|
||||||
**Code formatting**
|
## Code formatting
|
||||||
|
|
||||||
Look around. Match the style of the rest of the codebase.
|
Look around. Match the style of the rest of the codebase. Run `yarn format` before committing.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
Add an entry under the "Unreleased" heading in [CHANGELOG.md](https://github.com/react-navigation/react-navigation/blob/master/CHANGELOG.md#unreleased) which explains your change.
|
||||||
|
|||||||
82
CHANGELOG.md
Normal file
82
CHANGELOG.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.6.2] - [2018-07-05)(https://github.com/react-navigation/react-navigation/releases/tag/2.6.2)
|
||||||
|
### Changed
|
||||||
|
- Relax vertical padding warnings on header.
|
||||||
|
|
||||||
|
## [2.6.1] - [2018-07-05)(https://github.com/react-navigation/react-navigation/releases/tag/2.6.1)
|
||||||
|
### Added
|
||||||
|
- Warn for more invalid headerStyle properties (padding, top/right/bottom/left, position).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed missing header shadow on Android.
|
||||||
|
|
||||||
|
## [2.6.0] - [2018-07-04](https://github.com/react-navigation/react-navigation/releases/tag/2.6.0)
|
||||||
|
### Added
|
||||||
|
- [NavigationEvents](https://github.com/react-navigation/react-navigation/pull/4188) component as a declarative interface for subscribing to navigation focus events.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix stack router child router delegation priority (https://github.com/react-navigation/react-navigation/commit/e8c1833053e37d28f0ce505ff323565abf23b6a2)
|
||||||
|
- Avoid crash when calling isFocused on old route (https://github.com/react-navigation/react-navigation/commit/0921889f7a3acfc6d6bcc4909d209eeeee985ba7)
|
||||||
|
- Stack router no longer attempts to parse query params within path handling
|
||||||
|
- Switch router now has exact same param treatment for URLs as stack router does
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Internally we no longer need to special case PlatformHelpers by platform as react-native-web handles the APIs we mocked out with it now.
|
||||||
|
|
||||||
|
## [2.5.5] - [2018-06-27](https://github.com/react-navigation/react-navigation/releases/tag/2.5.5)
|
||||||
|
### Added
|
||||||
|
- Throw error in development mode when header navigation option is set to a string - a common mistake that would otherwise result in a cryptic error message.
|
||||||
|
- Throw error in development mode when title is not a string.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Delegate to child routers for more than just the top screen in the stack.
|
||||||
|
- Update react-navigation-drawer to 0.4.3 to fix `initialRouteParams` option
|
||||||
|
|
||||||
|
## [2.5.4] - [2018-06-27](https://github.com/react-navigation/react-navigation/releases/tag/2.5.4)
|
||||||
|
### Fixed
|
||||||
|
- Header no longer sometimes flashes for 1 frame when using `header: null` on initial route of stack with floating header.
|
||||||
|
- Export `createSwitchNavigator` in react-navigation.web.js
|
||||||
|
|
||||||
|
## [2.5.3] - [2018-06-23](https://github.com/react-navigation/react-navigation/releases/tag/2.5.3)
|
||||||
|
### Fixed
|
||||||
|
- `setParams` applies to the navigation object it is called on even if that is the navigation object for a navigation view (more details in https://github.com/react-navigation/react-navigation/issues/4497)
|
||||||
|
|
||||||
|
## [2.5.2] - [2018-06-23](https://github.com/react-navigation/react-navigation/releases/tag/2.5.2)
|
||||||
|
### Fixed
|
||||||
|
- Update react-navigation-drawer to fix regression in toggleDrawer
|
||||||
|
|
||||||
|
## [2.5.1] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.1)
|
||||||
|
### Fixed
|
||||||
|
- `transitionConfig` in stack navigator no longer passes incorrect `fromTransitionProps` when navigating back
|
||||||
|
|
||||||
|
## [2.5.0] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.0)
|
||||||
|
### Changed
|
||||||
|
- Refactor internals to make it play more nicely with web
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `const defaultGetStateForAction = SwitchBasedNavigator.router.getStateForAction` no longer throws error.
|
||||||
|
- Updated react-navigation-drawer to 0.4.1 which should fix issues related to automatically closing drawer when changing routes.
|
||||||
|
|
||||||
|
## [2.4.1] - [2018-06-21](https://github.com/react-navigation/react-navigation/releases/tag/2.4.1)
|
||||||
|
### Changed
|
||||||
|
- Improved examples
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/2.6.2...HEAD
|
||||||
|
[2.6.2]: https://github.com/react-navigation/react-navigation/compare/2.6.1...2.6.2
|
||||||
|
[2.6.1]: https://github.com/react-navigation/react-navigation/compare/2.6.0...2.6.1
|
||||||
|
[2.6.0]: https://github.com/react-navigation/react-navigation/compare/2.5.5...2.6.0
|
||||||
|
[2.5.5]: https://github.com/react-navigation/react-navigation/compare/2.5.4...2.5.5
|
||||||
|
[2.5.4]: https://github.com/react-navigation/react-navigation/compare/2.5.3...2.5.4
|
||||||
|
[2.5.3]: https://github.com/react-navigation/react-navigation/compare/2.5.2...2.5.3
|
||||||
|
[2.5.2]: https://github.com/react-navigation/react-navigation/compare/2.5.1...2.5.2
|
||||||
|
[2.5.1]: https://github.com/react-navigation/react-navigation/compare/2.5.0...2.5.1
|
||||||
|
[2.5.0]: https://github.com/react-navigation/react-navigation/compare/2.4.1...2.5.0
|
||||||
|
[2.4.1]: https://github.com/react-navigation/react-navigation/compare/2.4.0...2.4.1
|
||||||
@@ -13,14 +13,15 @@
|
|||||||
},
|
},
|
||||||
"sdkVersion": "27.0.0",
|
"sdkVersion": "27.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": {
|
"assetBundlePatterns": [
|
||||||
"assetExts": [
|
"**/*"
|
||||||
"ttf",
|
],
|
||||||
"mp4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ios": {
|
"ios": {
|
||||||
|
"bundleIdentifier": "com.reactnavigation.example",
|
||||||
"supportsTablet": true
|
"supportsTablet": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"package": "com.reactnavigation.example"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
|||||||
import SimpleTabs from './SimpleTabs';
|
import SimpleTabs from './SimpleTabs';
|
||||||
import SwitchWithStacks from './SwitchWithStacks';
|
import SwitchWithStacks from './SwitchWithStacks';
|
||||||
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
||||||
|
import TabsWithNavigationEvents from './TabsWithNavigationEvents';
|
||||||
import KeyboardHandlingExample from './KeyboardHandlingExample';
|
import KeyboardHandlingExample from './KeyboardHandlingExample';
|
||||||
|
|
||||||
const ExampleInfo = {
|
const ExampleInfo = {
|
||||||
@@ -126,6 +127,11 @@ const ExampleInfo = {
|
|||||||
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',
|
||||||
},
|
},
|
||||||
|
TabsWithNavigationEvents: {
|
||||||
|
name: 'NavigationEvents',
|
||||||
|
description:
|
||||||
|
'Declarative NavigationEvents component to subscribe to navigation events',
|
||||||
|
},
|
||||||
KeyboardHandlingExample: {
|
KeyboardHandlingExample: {
|
||||||
name: 'Keyboard Handling Example',
|
name: 'Keyboard Handling Example',
|
||||||
description:
|
description:
|
||||||
@@ -166,6 +172,7 @@ const ExampleRoutes = {
|
|||||||
path: 'settings',
|
path: 'settings',
|
||||||
},
|
},
|
||||||
TabsWithNavigationFocus,
|
TabsWithNavigationFocus,
|
||||||
|
TabsWithNavigationEvents,
|
||||||
KeyboardHandlingExample,
|
KeyboardHandlingExample,
|
||||||
// This is commented out because it's rarely useful
|
// This is commented out because it's rarely useful
|
||||||
// InactiveStack,
|
// InactiveStack,
|
||||||
|
|||||||
127
examples/NavigationPlayground/js/TabsWithNavigationEvents.js
Normal file
127
examples/NavigationPlayground/js/TabsWithNavigationEvents.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { FlatList, SafeAreaView, StatusBar, Text, View } from 'react-native';
|
||||||
|
import { NavigationEvents } from 'react-navigation';
|
||||||
|
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
|
||||||
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
|
const Event = ({ event }) => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
borderColor: 'grey',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: 5,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>{event.type}</Text>
|
||||||
|
<Text>
|
||||||
|
{event.action.type.replace('Navigation/', '')}
|
||||||
|
{event.action.routeName ? '=>' + event.action.routeName : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const createTabScreen = (name, icon, focusedIcon) => {
|
||||||
|
class TabScreen extends React.Component<any, any> {
|
||||||
|
static navigationOptions = {
|
||||||
|
tabBarLabel: name,
|
||||||
|
tabBarIcon: ({ tintColor, focused }) => (
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name={focused ? focusedIcon : icon}
|
||||||
|
size={26}
|
||||||
|
style={{ color: focused ? tintColor : '#ccc' }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
state = { eventLog: [] };
|
||||||
|
|
||||||
|
append = navigationEvent => {
|
||||||
|
this.setState(({ eventLog }) => ({
|
||||||
|
eventLog: eventLog.concat(navigationEvent),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SafeAreaView
|
||||||
|
forceInset={{ horizontal: 'always', top: 'always' }}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
margin: 10,
|
||||||
|
marginTop: 30,
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Events for tab {name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={{ flex: 1, width: '100%', marginTop: 10 }}>
|
||||||
|
<FlatList
|
||||||
|
data={this.state.eventLog}
|
||||||
|
keyExtractor={item => `${this.state.eventLog.indexOf(item)}`}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginVertical: 5,
|
||||||
|
marginHorizontal: 10,
|
||||||
|
backgroundColor: '#e4e4e4',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Event event={item} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<NavigationEvents
|
||||||
|
onWillFocus={this.append}
|
||||||
|
onDidFocus={this.append}
|
||||||
|
onWillBlur={this.append}
|
||||||
|
onDidBlur={this.append}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatusBar barStyle="default" />
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TabScreen;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TabsWithNavigationEvents = createMaterialBottomTabNavigator(
|
||||||
|
{
|
||||||
|
One: {
|
||||||
|
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
|
||||||
|
},
|
||||||
|
Two: {
|
||||||
|
screen: createTabScreen('Two', 'numeric-2-box-outline', 'numeric-2-box'),
|
||||||
|
},
|
||||||
|
Three: {
|
||||||
|
screen: createTabScreen(
|
||||||
|
'Three',
|
||||||
|
'numeric-3-box-outline',
|
||||||
|
'numeric-3-box'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shifting: false,
|
||||||
|
activeTintColor: '#F44336',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TabsWithNavigationEvents;
|
||||||
15
flow/react-navigation.js
vendored
15
flow/react-navigation.js
vendored
@@ -557,6 +557,21 @@ declare module 'react-navigation' {
|
|||||||
navigationOptions?: O,
|
navigationOptions?: O,
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NavigationEvents component
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare type _NavigationEventsProps = {
|
||||||
|
navigation?: NavigationScreenProp<NavigationState>,
|
||||||
|
onWillFocus?: NavigationEventCallback,
|
||||||
|
onDidFocus?: NavigationEventCallback,
|
||||||
|
onWillBlur?: NavigationEventCallback,
|
||||||
|
onDidBlur?: NavigationEventCallback,
|
||||||
|
};
|
||||||
|
declare export var NavigationEvents: React$ComponentType<
|
||||||
|
_NavigationEventsProps
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigation container
|
* Navigation container
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-navigation",
|
"name": "react-navigation",
|
||||||
"version": "2.4.1",
|
"version": "2.6.2",
|
||||||
"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": {
|
||||||
@@ -33,11 +33,11 @@
|
|||||||
"create-react-context": "^0.2.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",
|
"query-string": "^6.1.0",
|
||||||
"react-lifecycles-compat": "^3",
|
"react-lifecycles-compat": "^3",
|
||||||
"react-native-safe-area-view": "^0.8.0",
|
"react-native-safe-area-view": "^0.8.0",
|
||||||
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
||||||
"react-navigation-drawer": "0.3.2",
|
"react-navigation-drawer": "0.4.3",
|
||||||
"react-navigation-tabs": "0.5.1"
|
"react-navigation-tabs": "0.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import {
|
|
||||||
BackAndroid as DeprecatedBackAndroid,
|
|
||||||
BackHandler as ModernBackHandler,
|
|
||||||
MaskedViewIOS,
|
|
||||||
} from 'react-native';
|
|
||||||
|
|
||||||
const BackHandler = ModernBackHandler || DeprecatedBackAndroid;
|
|
||||||
|
|
||||||
export { BackHandler, MaskedViewIOS };
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { BackHandler, View } from 'react-native';
|
|
||||||
|
|
||||||
const MaskedViewIOS = () => <View>{this.props.children}</View>;
|
|
||||||
|
|
||||||
export { BackHandler, MaskedViewIOS };
|
|
||||||
@@ -133,10 +133,15 @@ 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 position to where the new route in the
|
||||||
* stack is at. Does not prune the routes.
|
* stack is at. Does not prune the routes.
|
||||||
|
* If preserveIndex is true then replacing the route does not cause the index
|
||||||
|
* to change to the index of that route.
|
||||||
*/
|
*/
|
||||||
replaceAt(state, key, route) {
|
replaceAt(state, key, route, preserveIndex = false) {
|
||||||
const index = StateUtils.indexOf(state, key);
|
const index = StateUtils.indexOf(state, key);
|
||||||
return StateUtils.replaceAtIndex(state, index, route);
|
const nextIndex = preserveIndex ? state.index : index;
|
||||||
|
let nextState = StateUtils.replaceAtIndex(state, index, route);
|
||||||
|
nextState.index = nextIndex;
|
||||||
|
return nextState;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import renderer from 'react-test-renderer';
|
|||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import createStackNavigator from '../navigators/createStackNavigator';
|
import createStackNavigator from '../navigators/createStackNavigator';
|
||||||
import { _TESTING_ONLY_reset_container_count } from '../createNavigationContainer';
|
import createNavigationContainer, {
|
||||||
|
_TESTING_ONLY_reset_container_count,
|
||||||
|
} from '../createNavigationContainer';
|
||||||
|
|
||||||
describe('NavigationContainer', () => {
|
describe('NavigationContainer', () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@@ -19,7 +21,7 @@ describe('NavigationContainer', () => {
|
|||||||
const CarScreen = () => <div />;
|
const CarScreen = () => <div />;
|
||||||
const DogScreen = () => <div />;
|
const DogScreen = () => <div />;
|
||||||
const ElkScreen = () => <div />;
|
const ElkScreen = () => <div />;
|
||||||
const NavigationContainer = createStackNavigator(
|
const Stack = createStackNavigator(
|
||||||
{
|
{
|
||||||
foo: {
|
foo: {
|
||||||
screen: FooScreen,
|
screen: FooScreen,
|
||||||
@@ -44,6 +46,7 @@ describe('NavigationContainer', () => {
|
|||||||
initialRouteName: 'foo',
|
initialRouteName: 'foo',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const NavigationContainer = createNavigationContainer(Stack);
|
||||||
|
|
||||||
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", () => {
|
||||||
@@ -225,7 +228,7 @@ describe('NavigationContainer', () => {
|
|||||||
|
|
||||||
let spy = spyConsole();
|
let spy = spyConsole();
|
||||||
|
|
||||||
it('warns when you render more than one navigator explicitly', () => {
|
it('warns when you render more than one container explicitly', () => {
|
||||||
class BlankScreen extends React.Component {
|
class BlankScreen extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <View />;
|
return <View />;
|
||||||
@@ -242,13 +245,17 @@ describe('NavigationContainer', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChildNavigator = createStackNavigator({
|
const ChildNavigator = createNavigationContainer(
|
||||||
Child: BlankScreen,
|
createStackNavigator({
|
||||||
});
|
Child: BlankScreen,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const RootStack = createStackNavigator({
|
const RootStack = createNavigationContainer(
|
||||||
Root: RootScreen,
|
createStackNavigator({
|
||||||
});
|
Root: RootScreen,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
renderer.create(<RootStack />).toJSON();
|
renderer.create(<RootStack />).toJSON();
|
||||||
expect(spy).toMatchSnapshot();
|
expect(spy).toMatchSnapshot();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`NavigationContainer warnings detached navigators warns when you render more than one navigator explicitly 1`] = `
|
exports[`NavigationContainer warnings detached navigators warns when you render more than one container explicitly 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"console": [MockFunction] {
|
"console": [MockFunction] {
|
||||||
"calls": Array [
|
"calls": Array [
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AsyncStorage, Linking, Platform } from 'react-native';
|
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
import { polyfill } from 'react-lifecycles-compat';
|
||||||
|
|
||||||
import { BackHandler } from './PlatformHelpers';
|
|
||||||
import NavigationActions from './NavigationActions';
|
import NavigationActions from './NavigationActions';
|
||||||
import getNavigation from './getNavigation';
|
import getNavigation from './getNavigation';
|
||||||
import invariant from './utils/invariant';
|
import invariant from './utils/invariant';
|
||||||
import docsUrl from './utils/docsUrl';
|
import docsUrl from './utils/docsUrl';
|
||||||
|
import { urlToPathAndParams } from './routers/pathUtils';
|
||||||
|
|
||||||
function isStateful(props) {
|
function isStateful(props) {
|
||||||
return !props.navigation;
|
return !props.navigation;
|
||||||
@@ -129,23 +129,8 @@ export default function createNavigationContainer(Component) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_urlToPathAndParams(url) {
|
|
||||||
const params = {};
|
|
||||||
const delimiter = this.props.uriPrefix || '://';
|
|
||||||
let path = url.split(delimiter)[1];
|
|
||||||
if (typeof path === 'undefined') {
|
|
||||||
path = url;
|
|
||||||
} else if (path === '') {
|
|
||||||
path = '/';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleOpenURL = ({ url }) => {
|
_handleOpenURL = ({ url }) => {
|
||||||
const parsedUrl = this._urlToPathAndParams(url);
|
const parsedUrl = urlToPathAndParams(url, this.props.uriPrefix);
|
||||||
if (parsedUrl) {
|
if (parsedUrl) {
|
||||||
const { path, params } = parsedUrl;
|
const { path, params } = parsedUrl;
|
||||||
const action = Component.router.getActionForPathAndParams(path, params);
|
const action = Component.router.getActionForPathAndParams(path, params);
|
||||||
@@ -214,11 +199,11 @@ export default function createNavigationContainer(Component) {
|
|||||||
Linking.addEventListener('url', this._handleOpenURL);
|
Linking.addEventListener('url', this._handleOpenURL);
|
||||||
|
|
||||||
// Pull out anything that can impact state
|
// Pull out anything that can impact state
|
||||||
const { persistenceKey } = this.props;
|
const { persistenceKey, uriPrefix } = this.props;
|
||||||
const startupStateJSON =
|
const startupStateJSON =
|
||||||
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
||||||
const url = await Linking.getInitialURL();
|
const url = await Linking.getInitialURL();
|
||||||
const parsedUrl = url && this._urlToPathAndParams(url);
|
const parsedUrl = url && urlToPathAndParams(url, uriPrefix);
|
||||||
|
|
||||||
// Initialize state. This must be done *after* any async code
|
// Initialize state. This must be done *after* any async code
|
||||||
// so we don't end up with a different value for this.state.nav
|
// so we don't end up with a different value for this.state.nav
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import getChildEventSubscriber from './getChildEventSubscriber';
|
import getChildEventSubscriber from './getChildEventSubscriber';
|
||||||
import getChildRouter from './getChildRouter';
|
import getChildRouter from './getChildRouter';
|
||||||
|
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
||||||
import invariant from './utils/invariant';
|
import invariant from './utils/invariant';
|
||||||
|
|
||||||
const createParamGetter = route => (paramName, defaultValue) => {
|
const createParamGetter = route => (paramName, defaultValue) => {
|
||||||
@@ -18,6 +19,10 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
|||||||
|
|
||||||
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
||||||
|
|
||||||
|
if (!childRoute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (children[childKey] && children[childKey].state === childRoute) {
|
if (children[childKey] && children[childKey].state === childRoute) {
|
||||||
return children[childKey];
|
return children[childKey];
|
||||||
}
|
}
|
||||||
@@ -40,7 +45,9 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
|||||||
...(childRouter
|
...(childRouter
|
||||||
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
|
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
|
||||||
: {}),
|
: {}),
|
||||||
|
...getNavigationActionCreators(childRoute),
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionHelpers = {};
|
const actionHelpers = {};
|
||||||
Object.keys(actionCreators).forEach(actionName => {
|
Object.keys(actionCreators).forEach(actionName => {
|
||||||
actionHelpers[actionName] = (...args) => {
|
actionHelpers[actionName] = (...args) => {
|
||||||
@@ -76,12 +83,16 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
|||||||
getParam: createParamGetter(childRoute),
|
getParam: createParamGetter(childRoute),
|
||||||
|
|
||||||
getChildNavigation: grandChildKey =>
|
getChildNavigation: grandChildKey =>
|
||||||
getChildNavigation(children[childKey], grandChildKey, () =>
|
getChildNavigation(children[childKey], grandChildKey, () => {
|
||||||
getCurrentParentNavigation().getChildNavigation(childKey)
|
const nav = getCurrentParentNavigation();
|
||||||
),
|
return nav && nav.getChildNavigation(childKey);
|
||||||
|
}),
|
||||||
|
|
||||||
isFocused: () => {
|
isFocused: () => {
|
||||||
const currentNavigation = getCurrentParentNavigation();
|
const currentNavigation = getCurrentParentNavigation();
|
||||||
|
if (!currentNavigation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const { routes, index } = currentNavigation.state;
|
const { routes, index } = currentNavigation.state;
|
||||||
if (!currentNavigation.isFocused()) {
|
if (!currentNavigation.isFocused()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import StackNavigator from '../createStackNavigator';
|
import StackNavigator from '../createContainedStackNavigator';
|
||||||
|
|
||||||
const SubNavigator = StackNavigator({
|
const SubNavigator = StackNavigator({
|
||||||
Home: {
|
Home: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import { StyleSheet, View } from 'react-native';
|
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 '../createContainedStackNavigator';
|
||||||
import withNavigation from '../../views/withNavigation';
|
import withNavigation from '../../views/withNavigation';
|
||||||
import { _TESTING_ONLY_reset_container_count } from '../../createNavigationContainer';
|
import { _TESTING_ONLY_reset_container_count } from '../../createNavigationContainer';
|
||||||
|
|
||||||
|
|||||||
@@ -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 SwitchNavigator from '../createSwitchNavigator';
|
import SwitchNavigator from '../createContainedSwitchNavigator';
|
||||||
|
|
||||||
const A = () => <View />;
|
const A = () => <View />;
|
||||||
const B = () => <View />;
|
const B = () => <View />;
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
|||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"backgroundColor": "#F7F7F7",
|
||||||
"transform": Array [
|
"transform": Array [
|
||||||
Object {
|
Object {
|
||||||
"translateX": 0,
|
"translateX": 0,
|
||||||
@@ -265,6 +266,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
|||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"backgroundColor": "#F7F7F7",
|
||||||
"transform": Array [
|
"transform": Array [
|
||||||
Object {
|
Object {
|
||||||
"translateX": 0,
|
"translateX": 0,
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"backgroundColor": "#F7F7F7",
|
||||||
"transform": Array [
|
"transform": Array [
|
||||||
Object {
|
Object {
|
||||||
"translateX": 0,
|
"translateX": 0,
|
||||||
@@ -288,6 +289,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"backgroundColor": "#F7F7F7",
|
||||||
"transform": Array [
|
"transform": Array [
|
||||||
Object {
|
Object {
|
||||||
"translateX": 0,
|
"translateX": 0,
|
||||||
|
|||||||
9
src/navigators/createContainedStackNavigator.js
Normal file
9
src/navigators/createContainedStackNavigator.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
|
import createStackNavigator from './createStackNavigator';
|
||||||
|
|
||||||
|
const StackNavigator = (routeConfigs, config = {}) => {
|
||||||
|
const navigator = createStackNavigator(routeConfigs, config);
|
||||||
|
return createNavigationContainer(navigator);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StackNavigator;
|
||||||
9
src/navigators/createContainedSwitchNavigator.js
Normal file
9
src/navigators/createContainedSwitchNavigator.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
|
import createSwitchNavigator from './createSwitchNavigator';
|
||||||
|
|
||||||
|
const SwitchNavigator = (routeConfigs, config = {}) => {
|
||||||
|
const navigator = createSwitchNavigator(routeConfigs, config);
|
||||||
|
return createNavigationContainer(navigator);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwitchNavigator;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
|
||||||
import createKeyboardAwareNavigator from './createKeyboardAwareNavigator';
|
import createKeyboardAwareNavigator from './createKeyboardAwareNavigator';
|
||||||
import createNavigator from './createNavigator';
|
import createNavigator from './createNavigator';
|
||||||
import StackView from '../views/StackView/StackView';
|
import StackView from '../views/StackView/StackView';
|
||||||
@@ -33,8 +31,7 @@ function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
|||||||
Navigator = createKeyboardAwareNavigator(Navigator);
|
Navigator = createKeyboardAwareNavigator(Navigator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
|
return Navigator;
|
||||||
return createNavigationContainer(Navigator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createStackNavigator;
|
export default createStackNavigator;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
|
||||||
import createNavigator from '../navigators/createNavigator';
|
import createNavigator from '../navigators/createNavigator';
|
||||||
import SwitchRouter from '../routers/SwitchRouter';
|
import SwitchRouter from '../routers/SwitchRouter';
|
||||||
import SwitchView from '../views/SwitchView/SwitchView';
|
import SwitchView from '../views/SwitchView/SwitchView';
|
||||||
@@ -7,7 +6,7 @@ import SwitchView from '../views/SwitchView/SwitchView';
|
|||||||
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
||||||
const router = SwitchRouter(routeConfigMap, switchConfig);
|
const router = SwitchRouter(routeConfigMap, switchConfig);
|
||||||
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
||||||
return createNavigationContainer(Navigator);
|
return Navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createSwitchNavigator;
|
export default createSwitchNavigator;
|
||||||
|
|||||||
13
src/react-navigation.js
vendored
13
src/react-navigation.js
vendored
@@ -17,22 +17,22 @@ module.exports = {
|
|||||||
return require('./navigators/createNavigator').default;
|
return require('./navigators/createNavigator').default;
|
||||||
},
|
},
|
||||||
get createStackNavigator() {
|
get createStackNavigator() {
|
||||||
return require('./navigators/createStackNavigator').default;
|
return require('./navigators/createContainedStackNavigator').default;
|
||||||
},
|
},
|
||||||
get StackNavigator() {
|
get StackNavigator() {
|
||||||
console.warn(
|
console.warn(
|
||||||
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
|
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
|
||||||
);
|
);
|
||||||
return require('./navigators/createStackNavigator').default;
|
return require('./navigators/createContainedStackNavigator').default;
|
||||||
},
|
},
|
||||||
get createSwitchNavigator() {
|
get createSwitchNavigator() {
|
||||||
return require('./navigators/createSwitchNavigator').default;
|
return require('./navigators/createContainedSwitchNavigator').default;
|
||||||
},
|
},
|
||||||
get SwitchNavigator() {
|
get SwitchNavigator() {
|
||||||
console.warn(
|
console.warn(
|
||||||
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
|
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
|
||||||
);
|
);
|
||||||
return require('./navigators/createSwitchNavigator').default;
|
return require('./navigators/createContainedSwitchNavigator').default;
|
||||||
},
|
},
|
||||||
get createDrawerNavigator() {
|
get createDrawerNavigator() {
|
||||||
return require('react-navigation-drawer').createDrawerNavigator;
|
return require('react-navigation-drawer').createDrawerNavigator;
|
||||||
@@ -156,6 +156,11 @@ module.exports = {
|
|||||||
return require('./views/SwitchView/SwitchView').default;
|
return require('./views/SwitchView/SwitchView').default;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// NavigationEvents
|
||||||
|
get NavigationEvents() {
|
||||||
|
return require('./views/NavigationEvents').default;
|
||||||
|
},
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
get withNavigation() {
|
get withNavigation() {
|
||||||
return require('./views/withNavigation').default;
|
return require('./views/withNavigation').default;
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ module.exports = {
|
|||||||
get StateUtils() {
|
get StateUtils() {
|
||||||
return require('./StateUtils').default;
|
return require('./StateUtils').default;
|
||||||
},
|
},
|
||||||
|
get getNavigation() {
|
||||||
|
return require('./getNavigation').default;
|
||||||
|
},
|
||||||
|
|
||||||
// Navigators
|
// Navigators
|
||||||
get createNavigator() {
|
get createNavigator() {
|
||||||
return require('./navigators/createNavigator').default;
|
return require('./navigators/createNavigator').default;
|
||||||
},
|
},
|
||||||
|
get createSwitchNavigator() {
|
||||||
|
return require('./navigators/createSwitchNavigator').default;
|
||||||
|
},
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
get NavigationActions() {
|
get NavigationActions() {
|
||||||
@@ -36,6 +42,11 @@ module.exports = {
|
|||||||
return require('./routers/SwitchRouter').default;
|
return require('./routers/SwitchRouter').default;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// NavigationEvents
|
||||||
|
get NavigationEvents() {
|
||||||
|
return require('./views/NavigationEvents').default;
|
||||||
|
},
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
get withNavigation() {
|
get withNavigation() {
|
||||||
return require('./views/withNavigation').default;
|
return require('./views/withNavigation').default;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import pathToRegexp from 'path-to-regexp';
|
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import StackActions from './StackActions';
|
import StackActions from './StackActions';
|
||||||
import createConfigGetter from './createConfigGetter';
|
import createConfigGetter from './createConfigGetter';
|
||||||
@@ -8,15 +6,7 @@ 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';
|
import { createPathParser } from './pathUtils';
|
||||||
|
|
||||||
function isEmpty(obj) {
|
|
||||||
if (!obj) return true;
|
|
||||||
for (let key in obj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function behavesLikePushAction(action) {
|
function behavesLikePushAction(action) {
|
||||||
return (
|
return (
|
||||||
@@ -57,8 +47,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
||||||
|
|
||||||
const initialChildRouter = childRouters[initialRouteName];
|
const initialChildRouter = childRouters[initialRouteName];
|
||||||
const pathsByRouteNames = { ...stackConfig.paths } || {};
|
|
||||||
let paths = [];
|
|
||||||
|
|
||||||
function getInitialState(action) {
|
function getInitialState(action) {
|
||||||
let route = {};
|
let route = {};
|
||||||
@@ -116,37 +104,16 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build paths for each route
|
const {
|
||||||
routeNames.forEach(routeName => {
|
getPathAndParamsForRoute,
|
||||||
let pathPattern =
|
getActionForPathAndParams,
|
||||||
pathsByRouteNames[routeName] || routeConfigs[routeName].path;
|
} = createPathParser(
|
||||||
let matchExact = !!pathPattern && !childRouters[routeName];
|
childRouters,
|
||||||
if (pathPattern === undefined) {
|
routeConfigs,
|
||||||
pathPattern = routeName;
|
stackConfig.paths,
|
||||||
}
|
initialRouteName,
|
||||||
const keys = [];
|
initialRouteParams
|
||||||
let re, toPath, priority;
|
);
|
||||||
if (typeof pathPattern === 'string') {
|
|
||||||
// pathPattern may be either a string or a regexp object according to path-to-regexp docs.
|
|
||||||
re = pathToRegexp(pathPattern, keys);
|
|
||||||
toPath = pathToRegexp.compile(pathPattern);
|
|
||||||
priority = 0;
|
|
||||||
} else {
|
|
||||||
// for wildcard match
|
|
||||||
re = pathToRegexp('*', keys);
|
|
||||||
toPath = () => '';
|
|
||||||
matchExact = true;
|
|
||||||
priority = -1;
|
|
||||||
}
|
|
||||||
if (!matchExact) {
|
|
||||||
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
|
|
||||||
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
|
|
||||||
}
|
|
||||||
pathsByRouteNames[routeName] = { re, keys, toPath, priority };
|
|
||||||
});
|
|
||||||
|
|
||||||
paths = Object.entries(pathsByRouteNames);
|
|
||||||
paths.sort((a, b) => b[1].priority - a[1].priority);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
childRouters,
|
childRouters,
|
||||||
@@ -166,7 +133,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
|
|
||||||
getActionCreators(route, navStateKey) {
|
getActionCreators(route, navStateKey) {
|
||||||
return {
|
return {
|
||||||
...getNavigationActionCreators(route),
|
|
||||||
...getCustomActionCreators(route, navStateKey),
|
...getCustomActionCreators(route, navStateKey),
|
||||||
pop: (n, params) =>
|
pop: (n, params) =>
|
||||||
StackActions.pop({
|
StackActions.pop({
|
||||||
@@ -227,29 +193,27 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
return getInitialState(action);
|
return getInitialState(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the focused child scene wants to handle the action, as long as
|
const activeChildRoute = state.routes[state.index];
|
||||||
// it is not a reset to the root stack
|
|
||||||
if (
|
if (
|
||||||
!isResetToRootStack(action) &&
|
!isResetToRootStack(action) &&
|
||||||
action.type !== NavigationActions.NAVIGATE
|
action.type !== NavigationActions.NAVIGATE
|
||||||
) {
|
) {
|
||||||
const keyIndex = action.key
|
// Let the active child router handle the action
|
||||||
? StateUtils.indexOf(state, action.key)
|
const activeChildRouter = childRouters[activeChildRoute.routeName];
|
||||||
: -1;
|
if (activeChildRouter) {
|
||||||
const childIndex = keyIndex >= 0 ? keyIndex : state.index;
|
const route = activeChildRouter.getStateForAction(
|
||||||
const childRoute = state.routes[childIndex];
|
action,
|
||||||
invariant(
|
activeChildRoute
|
||||||
childRoute,
|
);
|
||||||
`StateUtils erroneously thought index ${childIndex} exists`
|
if (route !== null && route !== activeChildRoute) {
|
||||||
);
|
return StateUtils.replaceAt(
|
||||||
const childRouter = childRouters[childRoute.routeName];
|
state,
|
||||||
if (childRouter) {
|
activeChildRoute.key,
|
||||||
const route = childRouter.getStateForAction(action, childRoute);
|
route,
|
||||||
if (route === null) {
|
// the following tells replaceAt to NOT change the index to this route for the setParam action, because people don't expect param-setting actions to switch the active route
|
||||||
return state;
|
action.type === NavigationActions.SET_PARAMS
|
||||||
}
|
);
|
||||||
if (route && route !== childRoute) {
|
|
||||||
return StateUtils.replaceAt(state, childRoute.key, route);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (action.type === NavigationActions.NAVIGATE) {
|
} else if (action.type === NavigationActions.NAVIGATE) {
|
||||||
@@ -548,126 +512,52 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By this point in the router's state handling logic, we have handled the behavior of the active route, and handled any stack actions.
|
||||||
|
// If we haven't returned by now, we should allow non-active child routers to handle this action, and switch to that index if the child state (route) does change..
|
||||||
|
|
||||||
|
const keyIndex = action.key ? StateUtils.indexOf(state, action.key) : -1;
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
if (childRoute.key === activeChildRoute.key) {
|
||||||
|
// skip over the active child because we let it attempt to handle the action earlier
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If a key is provided and in routes state then let's use that
|
||||||
|
// knowledge to skip extra getStateForAction calls on other child
|
||||||
|
// routers
|
||||||
|
if (keyIndex >= 0 && childRoute.key !== action.key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let childRouter = childRouters[childRoute.routeName];
|
||||||
|
if (childRouter) {
|
||||||
|
const route = childRouter.getStateForAction(action, childRoute);
|
||||||
|
|
||||||
|
if (route === null) {
|
||||||
|
return state;
|
||||||
|
} else if (route && route !== childRoute) {
|
||||||
|
return StateUtils.replaceAt(
|
||||||
|
state,
|
||||||
|
childRoute.key,
|
||||||
|
route,
|
||||||
|
// the following tells replaceAt to NOT change the index to this route for the setParam action, because people don't expect param-setting actions to switch the active route
|
||||||
|
action.type === NavigationActions.SET_PARAMS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPathAndParamsForState(state) {
|
getPathAndParamsForState(state) {
|
||||||
const route = state.routes[state.index];
|
const route = state.routes[state.index];
|
||||||
const routeName = route.routeName;
|
return getPathAndParamsForRoute(route);
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
const subPath = pathsByRouteNames[routeName].toPath(route.params);
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getActionForPathAndParams(pathToResolve, inputParams) {
|
getActionForPathAndParams(path, params) {
|
||||||
// If the path is empty (null or empty string)
|
return getActionForPathAndParams(path, params);
|
||||||
// just return the initial route action
|
|
||||||
if (!pathToResolve) {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
params: inputParams,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [pathNameToResolve, queryString] = pathToResolve.split('?');
|
|
||||||
|
|
||||||
// Attempt to match `pathNameToResolve` with a route in this router's
|
|
||||||
// routeConfigs
|
|
||||||
let matchedRouteName;
|
|
||||||
let pathMatch;
|
|
||||||
let pathMatchKeys;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const [routeName, path] of paths) {
|
|
||||||
const { re, keys } = path;
|
|
||||||
pathMatch = re.exec(pathNameToResolve);
|
|
||||||
if (pathMatch && pathMatch.length) {
|
|
||||||
pathMatchKeys = keys;
|
|
||||||
matchedRouteName = routeName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't match -- return null
|
|
||||||
if (!matchedRouteName) {
|
|
||||||
// If the path is empty (null or empty string)
|
|
||||||
// just return the initial route action
|
|
||||||
if (!pathToResolve) {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine nested actions:
|
|
||||||
// If our matched route for this router is a child router,
|
|
||||||
// get the action for the path AFTER the matched path for this
|
|
||||||
// router
|
|
||||||
let nestedAction;
|
|
||||||
let nestedQueryString = queryString ? '?' + queryString : '';
|
|
||||||
if (childRouters[matchedRouteName]) {
|
|
||||||
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
|
|
||||||
pathMatch.slice(pathMatchKeys.length).join('/') + nestedQueryString
|
|
||||||
);
|
|
||||||
if (!nestedAction) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduce the items of the query string. any query params may
|
|
||||||
// be overridden by path params
|
|
||||||
const queryParams = !isEmpty(inputParams)
|
|
||||||
? inputParams
|
|
||||||
: (queryString || '').split('&').reduce((result, item) => {
|
|
||||||
if (item !== '') {
|
|
||||||
const nextResult = result || {};
|
|
||||||
const [key, value] = item.split('=');
|
|
||||||
nextResult[key] = value;
|
|
||||||
return nextResult;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
// reduce the matched pieces of the path into the params
|
|
||||||
// of the route. `params` is null if there are no params.
|
|
||||||
const params = pathMatch.slice(1).reduce((result, matchResult, i) => {
|
|
||||||
const key = pathMatchKeys[i];
|
|
||||||
if (key.asterisk || !key) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const nextResult = result || inputParams || {};
|
|
||||||
const paramName = key.name;
|
|
||||||
|
|
||||||
let decodedMatchResult;
|
|
||||||
try {
|
|
||||||
decodedMatchResult = decodeURIComponent(matchResult);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore `URIError: malformed URI`
|
|
||||||
}
|
|
||||||
|
|
||||||
nextResult[paramName] = decodedMatchResult || matchResult;
|
|
||||||
return nextResult;
|
|
||||||
}, queryParams);
|
|
||||||
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: matchedRouteName,
|
|
||||||
...(params ? { params } : {}),
|
|
||||||
...(nestedAction ? { action: nestedAction } : {}),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getScreenOptions: createConfigGetter(
|
getScreenOptions: createConfigGetter(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import createConfigGetter from './createConfigGetter';
|
|||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import StackActions from './StackActions';
|
import StackActions from './StackActions';
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
import getNavigationActionCreators from './getNavigationActionCreators';
|
import { createPathParser } from './pathUtils';
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
const defaultActionCreators = (route, navStateKey) => ({});
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
validateRouteConfigMap(routeConfigs);
|
validateRouteConfigMap(routeConfigs);
|
||||||
|
|
||||||
const order = config.order || Object.keys(routeConfigs);
|
const order = config.order || Object.keys(routeConfigs);
|
||||||
const paths = config.paths || {};
|
|
||||||
const getCustomActionCreators =
|
const getCustomActionCreators =
|
||||||
config.getCustomActionCreators || defaultActionCreators;
|
config.getCustomActionCreators || defaultActionCreators;
|
||||||
|
|
||||||
@@ -37,16 +37,24 @@ export default (routeConfigs, config = {}) => {
|
|||||||
const childRouters = {};
|
const childRouters = {};
|
||||||
order.forEach(routeName => {
|
order.forEach(routeName => {
|
||||||
const routeConfig = routeConfigs[routeName];
|
const routeConfig = routeConfigs[routeName];
|
||||||
if (!paths[routeName]) {
|
|
||||||
paths[routeName] =
|
|
||||||
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
|
||||||
}
|
|
||||||
childRouters[routeName] = null;
|
childRouters[routeName] = null;
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||||
if (screen.router) {
|
if (screen.router) {
|
||||||
childRouters[routeName] = screen.router;
|
childRouters[routeName] = screen.router;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
getPathAndParamsForRoute,
|
||||||
|
getActionForPathAndParams,
|
||||||
|
} = createPathParser(
|
||||||
|
childRouters,
|
||||||
|
routeConfigs,
|
||||||
|
config.paths,
|
||||||
|
initialRouteName,
|
||||||
|
initialRouteParams
|
||||||
|
);
|
||||||
|
|
||||||
if (initialRouteIndex === -1) {
|
if (initialRouteIndex === -1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||||
@@ -74,50 +82,47 @@ export default (routeConfigs, config = {}) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialState() {
|
||||||
|
const routes = order.map(resetChildRoute);
|
||||||
|
return {
|
||||||
|
routes,
|
||||||
|
index: initialRouteIndex,
|
||||||
|
isTransitioning: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
childRouters,
|
childRouters,
|
||||||
|
|
||||||
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) {
|
getActionCreators(route, stateKey) {
|
||||||
return {
|
return getCustomActionCreators(route, stateKey);
|
||||||
...getNavigationActionCreators(route),
|
|
||||||
...getCustomActionCreators(route, stateKey),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getStateForAction(action, inputState) {
|
getStateForAction(action, inputState) {
|
||||||
let prevState = inputState ? { ...inputState } : inputState;
|
let prevState = inputState ? { ...inputState } : inputState;
|
||||||
let state = inputState || this.getInitialState();
|
let state = inputState || getInitialState();
|
||||||
let activeChildIndex = state.index;
|
let activeChildIndex = state.index;
|
||||||
|
|
||||||
if (action.type === NavigationActions.INIT) {
|
if (action.type === NavigationActions.INIT) {
|
||||||
@@ -154,7 +159,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
if (activeChildState && activeChildState !== activeChildLastState) {
|
if (activeChildState && activeChildState !== activeChildLastState) {
|
||||||
const routes = [...state.routes];
|
const routes = [...state.routes];
|
||||||
routes[state.index] = activeChildState;
|
routes[state.index] = activeChildState;
|
||||||
return this.getNextState(prevState, {
|
return getNextState(prevState, {
|
||||||
...state,
|
...state,
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
@@ -191,7 +196,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
newChildState = childRouter
|
newChildState = childRouter
|
||||||
? childRouter.getStateForAction(action.action, childState)
|
? childRouter.getStateForAction(action.action, childState)
|
||||||
: null;
|
: null;
|
||||||
} else if (!action.action && !childRouter && action.params) {
|
} else if (!action.action && action.params) {
|
||||||
newChildState = {
|
newChildState = {
|
||||||
...childState,
|
...childState,
|
||||||
params: {
|
params: {
|
||||||
@@ -204,7 +209,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
if (newChildState && newChildState !== childState) {
|
if (newChildState && newChildState !== childState) {
|
||||||
const routes = [...state.routes];
|
const routes = [...state.routes];
|
||||||
routes[activeChildIndex] = newChildState;
|
routes[activeChildIndex] = newChildState;
|
||||||
return this.getNextState(prevState, {
|
return getNextState(prevState, {
|
||||||
...state,
|
...state,
|
||||||
routes,
|
routes,
|
||||||
index: activeChildIndex,
|
index: activeChildIndex,
|
||||||
@@ -232,7 +237,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
...lastRoute,
|
...lastRoute,
|
||||||
params,
|
params,
|
||||||
};
|
};
|
||||||
return this.getNextState(prevState, {
|
return getNextState(prevState, {
|
||||||
...state,
|
...state,
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
@@ -240,7 +245,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activeChildIndex !== state.index) {
|
if (activeChildIndex !== state.index) {
|
||||||
return this.getNextState(prevState, {
|
return getNextState(prevState, {
|
||||||
...state,
|
...state,
|
||||||
index: activeChildIndex,
|
index: activeChildIndex,
|
||||||
});
|
});
|
||||||
@@ -284,7 +289,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index !== state.index || routes !== state.routes) {
|
if (index !== state.index || routes !== state.routes) {
|
||||||
return this.getNextState(prevState, {
|
return getNextState(prevState, {
|
||||||
...state,
|
...state,
|
||||||
index,
|
index,
|
||||||
routes,
|
routes,
|
||||||
@@ -313,73 +318,11 @@ export default (routeConfigs, config = {}) => {
|
|||||||
|
|
||||||
getPathAndParamsForState(state) {
|
getPathAndParamsForState(state) {
|
||||||
const route = state.routes[state.index];
|
const route = state.routes[state.index];
|
||||||
const routeName = order[state.index];
|
return getPathAndParamsForRoute(route);
|
||||||
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) {
|
getActionForPathAndParams(path, params) {
|
||||||
if (!path) {
|
return getActionForPathAndParams(path, params);
|
||||||
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(
|
getScreenOptions: createConfigGetter(
|
||||||
|
|||||||
299
src/routers/__tests__/PathHandling-test.js
Normal file
299
src/routers/__tests__/PathHandling-test.js
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
/* eslint no-shadow:0, react/no-multi-comp:0, react/display-name:0 */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import SwitchRouter from '../SwitchRouter';
|
||||||
|
import StackRouter from '../StackRouter';
|
||||||
|
import StackActions from '../StackActions';
|
||||||
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
import { urlToPathAndParams } from '../pathUtils';
|
||||||
|
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_TESTING_ONLY_normalize_keys();
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListScreen = () => <div />;
|
||||||
|
|
||||||
|
const ProfileNavigator = () => <div />;
|
||||||
|
ProfileNavigator.router = StackRouter({
|
||||||
|
list: {
|
||||||
|
path: 'list/:id',
|
||||||
|
screen: ListScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const MainNavigator = () => <div />;
|
||||||
|
MainNavigator.router = StackRouter({
|
||||||
|
profile: {
|
||||||
|
path: 'p/:id',
|
||||||
|
screen: ProfileNavigator,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoginScreen = () => <div />;
|
||||||
|
|
||||||
|
const AuthNavigator = () => <div />;
|
||||||
|
AuthNavigator.router = StackRouter({
|
||||||
|
login: {
|
||||||
|
screen: LoginScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const BarScreen = () => <div />;
|
||||||
|
|
||||||
|
class FooNavigator extends React.Component {
|
||||||
|
static router = StackRouter({
|
||||||
|
bar: {
|
||||||
|
path: 'b/:barThing',
|
||||||
|
screen: BarScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render() {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PersonScreen = () => <div />;
|
||||||
|
|
||||||
|
const performRouterTest = createTestRouter => {
|
||||||
|
const testRouter = createTestRouter({
|
||||||
|
main: {
|
||||||
|
screen: MainNavigator,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
path: null,
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
screen: AuthNavigator,
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
path: 'people/:id',
|
||||||
|
screen: PersonScreen,
|
||||||
|
},
|
||||||
|
foo: {
|
||||||
|
path: 'fo/:fooThing',
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Handles empty URIs', () => {
|
||||||
|
const router = createTestRouter(
|
||||||
|
{
|
||||||
|
Foo: {
|
||||||
|
screen: () => <div />,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: () => <div />,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ initialRouteName: 'Bar', initialRouteParams: { foo: 42 } }
|
||||||
|
);
|
||||||
|
const action = router.getActionForPathAndParams('');
|
||||||
|
expect(action).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: 42 },
|
||||||
|
});
|
||||||
|
const state = router.getStateForAction(action);
|
||||||
|
expect(state.routes[state.index]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: 42 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gets deep path with pure wildcard match', () => {
|
||||||
|
const ScreenA = () => <div />;
|
||||||
|
const ScreenB = () => <div />;
|
||||||
|
const ScreenC = () => <div />;
|
||||||
|
ScreenA.router = createTestRouter({
|
||||||
|
Boo: { path: 'boo', screen: ScreenC },
|
||||||
|
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
||||||
|
});
|
||||||
|
ScreenC.router = createTestRouter({
|
||||||
|
Boo2: { path: '', screen: ScreenB },
|
||||||
|
});
|
||||||
|
const router = createTestRouter({
|
||||||
|
Foo: {
|
||||||
|
path: null,
|
||||||
|
screen: ScreenA,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: ScreenB,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Boo',
|
||||||
|
routeName: 'Boo',
|
||||||
|
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
||||||
|
},
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('baz/321');
|
||||||
|
expect(params.id).toEqual('123');
|
||||||
|
expect(params.bazId).toEqual('321');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Boo',
|
||||||
|
routeName: 'Boo',
|
||||||
|
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
||||||
|
},
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('boo');
|
||||||
|
expect(params).toEqual({ id: '123' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('URI encoded string get passed to deep link', () => {
|
||||||
|
const uri = 'people/2018%2F02%2F07';
|
||||||
|
const action = testRouter.getActionForPathAndParams(uri);
|
||||||
|
expect(action).toEqual({
|
||||||
|
routeName: 'person',
|
||||||
|
params: {
|
||||||
|
id: '2018/02/07',
|
||||||
|
},
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const malformedUri = 'people/%E0%A4%A';
|
||||||
|
const action2 = testRouter.getActionForPathAndParams(malformedUri);
|
||||||
|
expect(action2).toEqual({
|
||||||
|
routeName: 'person',
|
||||||
|
params: {
|
||||||
|
id: '%E0%A4%A',
|
||||||
|
},
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Querystring params get passed to nested deep link', () => {
|
||||||
|
const action = testRouter.getActionForPathAndParams(
|
||||||
|
'main/p/4/list/10259959195',
|
||||||
|
{ code: 'test', foo: 'bar' }
|
||||||
|
);
|
||||||
|
expect(action).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'main',
|
||||||
|
params: {
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'profile',
|
||||||
|
params: {
|
||||||
|
id: '4',
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'list',
|
||||||
|
params: {
|
||||||
|
id: '10259959195',
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const action2 = testRouter.getActionForPathAndParams(
|
||||||
|
'main/p/4/list/10259959195',
|
||||||
|
{ code: '', foo: 'bar' }
|
||||||
|
);
|
||||||
|
expect(action2).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'main',
|
||||||
|
params: {
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'profile',
|
||||||
|
params: {
|
||||||
|
id: '4',
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'list',
|
||||||
|
params: {
|
||||||
|
id: '10259959195',
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('paths option on router overrides path from route config', () => {
|
||||||
|
const router = createTestRouter(
|
||||||
|
{
|
||||||
|
main: {
|
||||||
|
screen: MainNavigator,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
path: null,
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ paths: { baz: 'overridden' } }
|
||||||
|
);
|
||||||
|
const action = router.getActionForPathAndParams('overridden', {});
|
||||||
|
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
||||||
|
expect(action.routeName).toEqual('baz');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Path handling for stack router', () => {
|
||||||
|
performRouterTest(StackRouter);
|
||||||
|
});
|
||||||
|
describe('Path handling for switch router', () => {
|
||||||
|
performRouterTest(SwitchRouter);
|
||||||
|
});
|
||||||
@@ -208,6 +208,7 @@ describe('StackRouter', () => {
|
|||||||
expect(AuthNavigator.router.getActionForPathAndParams('login')).toEqual({
|
expect(AuthNavigator.router.getActionForPathAndParams('login')).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -223,7 +224,10 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
test('Parses paths with a query', () => {
|
test('Parses paths with a query', () => {
|
||||||
expect(
|
expect(
|
||||||
TestStackRouter.getActionForPathAndParams('people/foo?code=test&foo=bar')
|
TestStackRouter.getActionForPathAndParams('people/foo', {
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'person',
|
routeName: 'person',
|
||||||
@@ -237,7 +241,10 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
test('Parses paths with an empty query value', () => {
|
test('Parses paths with an empty query value', () => {
|
||||||
expect(
|
expect(
|
||||||
TestStackRouter.getActionForPathAndParams('people/foo?code=&foo=bar')
|
TestStackRouter.getActionForPathAndParams('people/foo', {
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'person',
|
routeName: 'person',
|
||||||
@@ -255,9 +262,11 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'auth',
|
routeName: 'auth',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -268,6 +277,7 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'main',
|
routeName: 'main',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'profile',
|
routeName: 'profile',
|
||||||
@@ -291,6 +301,7 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'baz',
|
routeName: 'baz',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'bar',
|
routeName: 'bar',
|
||||||
@@ -313,9 +324,11 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'auth',
|
routeName: 'auth',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -989,6 +1002,43 @@ describe('StackRouter', () => {
|
|||||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
expect(state3 && state3.isTransitioning).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Back action parent is prioritized over inactive child routers', () => {
|
||||||
|
const Bar = () => <div />;
|
||||||
|
Bar.router = StackRouter({
|
||||||
|
baz: { screen: () => <div /> },
|
||||||
|
qux: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
const TestRouter = StackRouter({
|
||||||
|
foo: { screen: () => <div /> },
|
||||||
|
bar: { screen: Bar },
|
||||||
|
boo: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
const state = {
|
||||||
|
key: 'top',
|
||||||
|
index: 3,
|
||||||
|
routes: [
|
||||||
|
{ routeName: 'foo', key: 'f' },
|
||||||
|
{
|
||||||
|
routeName: 'bar',
|
||||||
|
key: 'b',
|
||||||
|
index: 1,
|
||||||
|
routes: [
|
||||||
|
{ routeName: 'baz', key: 'bz' },
|
||||||
|
{ routeName: 'qux', key: 'bx' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ routeName: 'foo', key: 'f1' },
|
||||||
|
{ routeName: 'boo', key: 'z' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const testState = TestRouter.getStateForAction(
|
||||||
|
{ type: NavigationActions.BACK },
|
||||||
|
state
|
||||||
|
);
|
||||||
|
expect(testState.index).toBe(2);
|
||||||
|
expect(testState.routes[1].index).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Handle basic stack logic for components with router', () => {
|
test('Handle basic stack logic for components with router', () => {
|
||||||
const FooScreen = () => <div />;
|
const FooScreen = () => <div />;
|
||||||
const BarScreen = () => <div />;
|
const BarScreen = () => <div />;
|
||||||
@@ -1047,6 +1097,48 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Gets deep path (stack behavior)', () => {
|
||||||
|
const ScreenA = () => <div />;
|
||||||
|
const ScreenB = () => <div />;
|
||||||
|
ScreenA.router = StackRouter({
|
||||||
|
Boo: { path: 'boo', screen: ScreenB },
|
||||||
|
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
||||||
|
});
|
||||||
|
const router = StackRouter({
|
||||||
|
Foo: {
|
||||||
|
path: 'f/:id',
|
||||||
|
screen: ScreenA,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: ScreenB,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
isTransitioning: false,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{ key: 'Boo', routeName: 'Boo' },
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('f/123/baz/321');
|
||||||
|
expect(params.id).toEqual('123');
|
||||||
|
expect(params.bazId).toEqual('321');
|
||||||
|
});
|
||||||
|
|
||||||
test('Handle goBack identified by key', () => {
|
test('Handle goBack identified by key', () => {
|
||||||
const FooScreen = () => <div />;
|
const FooScreen = () => <div />;
|
||||||
const BarScreen = () => <div />;
|
const BarScreen = () => <div />;
|
||||||
@@ -1634,400 +1726,164 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles empty URIs', () => {
|
test('Handles deep navigate completion action', () => {
|
||||||
const router = StackRouter(
|
const LeafScreen = () => <div />;
|
||||||
{
|
const FooScreen = () => <div />;
|
||||||
Foo: {
|
FooScreen.router = StackRouter({
|
||||||
screen: () => <div />,
|
Boo: { path: 'boo', screen: LeafScreen },
|
||||||
},
|
Baz: { path: 'baz/:bazId', screen: LeafScreen },
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'Bar' }
|
|
||||||
);
|
|
||||||
const action = router.getActionForPathAndParams('');
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
});
|
});
|
||||||
let state = null;
|
const router = StackRouter({
|
||||||
if (action) {
|
Foo: {
|
||||||
state = router.getStateForAction(action);
|
screen: FooScreen,
|
||||||
}
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: LeafScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state && state.index).toEqual(0);
|
expect(state && state.index).toEqual(0);
|
||||||
expect(state && state.routes[0]).toEqual(
|
expect(state && state.routes[0].routeName).toEqual('Foo');
|
||||||
expect.objectContaining({
|
const key = state && state.routes[0].key;
|
||||||
routeName: 'Bar',
|
const state2 = router.getStateForAction(
|
||||||
})
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Baz',
|
||||||
|
},
|
||||||
|
state
|
||||||
);
|
);
|
||||||
|
expect(state2 && state2.index).toEqual(0);
|
||||||
|
expect(state2 && state2.isTransitioning).toEqual(false);
|
||||||
|
expect(state2 && state2.routes[0].index).toEqual(1);
|
||||||
|
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
||||||
|
expect(!!key).toEqual(true);
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{
|
||||||
|
type: StackActions.COMPLETE_TRANSITION,
|
||||||
|
},
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
expect(state3 && state3.index).toEqual(0);
|
||||||
|
expect(state3 && state3.isTransitioning).toEqual(false);
|
||||||
|
expect(state3 && state3.routes[0].index).toEqual(1);
|
||||||
|
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Gets deep path', () => {
|
test('order of handling navigate action is correct for nested stackrouters', () => {
|
||||||
const ScreenA = () => <div />;
|
const Screen = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const NestedStack = () => <div />;
|
||||||
ScreenA.router = StackRouter({
|
let nestedRouter = StackRouter({
|
||||||
Boo: { path: 'boo', screen: ScreenB },
|
Foo: Screen,
|
||||||
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
path: 'f/:id',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('f/123/baz/321');
|
|
||||||
expect(params.id).toEqual('123');
|
|
||||||
expect(params.bazId).toEqual('321');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Gets deep path with pure wildcard match', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenA.router = StackRouter({
|
|
||||||
Boo: { path: 'boo', screen: ScreenC },
|
|
||||||
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
|
||||||
});
|
|
||||||
ScreenC.router = StackRouter({
|
|
||||||
Boo2: { path: '', screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('baz/321');
|
|
||||||
expect(params.id).toEqual('123');
|
|
||||||
expect(params.bazId).toEqual('321');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('boo/');
|
|
||||||
expect(params).toEqual({ id: '123' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('URI encoded string get passed to deep link', () => {
|
|
||||||
const uri = 'people/2018%2F02%2F07';
|
|
||||||
const action = TestStackRouter.getActionForPathAndParams(uri);
|
|
||||||
expect(action).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '2018/02/07',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const malformedUri = 'people/%E0%A4%A';
|
|
||||||
const action2 = TestStackRouter.getActionForPathAndParams(malformedUri);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '%E0%A4%A',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Querystring params get passed to nested deep link', () => {
|
|
||||||
// uri with two non-empty query param values
|
|
||||||
const uri = 'main/p/4/list/10259959195?code=test&foo=bar';
|
|
||||||
const action = TestStackRouter.getActionForPathAndParams(uri);
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// uri with one empty and one non-empty query param value
|
|
||||||
const uri2 = 'main/p/4/list/10259959195?code=&foo=bar';
|
|
||||||
const action2 = TestStackRouter.getActionForPathAndParams(uri2);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles deep navigate completion action', () => {
|
|
||||||
const LeafScreen = () => <div />;
|
|
||||||
const FooScreen = () => <div />;
|
|
||||||
FooScreen.router = StackRouter({
|
|
||||||
Boo: { path: 'boo', screen: LeafScreen },
|
|
||||||
Baz: { path: 'baz/:bazId', screen: LeafScreen },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: FooScreen,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: LeafScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state && state.index).toEqual(0);
|
|
||||||
expect(state && state.routes[0].routeName).toEqual('Foo');
|
|
||||||
const key = state && state.routes[0].key;
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.index).toEqual(0);
|
|
||||||
expect(state2 && state2.isTransitioning).toEqual(false);
|
|
||||||
expect(state2 && state2.routes[0].index).toEqual(1);
|
|
||||||
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
|
||||||
expect(!!key).toEqual(true);
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: StackActions.COMPLETE_TRANSITION,
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3 && state3.index).toEqual(0);
|
|
||||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
|
||||||
expect(state3 && state3.routes[0].index).toEqual(1);
|
|
||||||
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
Bar: Screen,
|
||||||
Baz: Screen,
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Baz',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
NestedStack.router = nestedRouter;
|
||||||
expect(state.routes[state.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
let router = StackRouter(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
NestedStack,
|
||||||
routeName: 'Bar',
|
Bar: Screen,
|
||||||
},
|
Baz: Screen,
|
||||||
state
|
},
|
||||||
);
|
{
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
initialRouteName: 'Baz',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
{
|
expect(state.routes[state.index].routeName).toEqual('Baz');
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.routes[state3.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state4 = router.getStateForAction(
|
const state2 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Foo',
|
routeName: 'Bar',
|
||||||
},
|
},
|
||||||
state3
|
state
|
||||||
);
|
);
|
||||||
let activeState4 = state4.routes[state4.index];
|
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
|
|
||||||
|
|
||||||
const state5 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Bar',
|
routeName: 'Baz',
|
||||||
},
|
},
|
||||||
state4
|
state2
|
||||||
);
|
);
|
||||||
let activeState5 = state5.routes[state5.index];
|
expect(state3.routes[state3.index].routeName).toEqual('Baz');
|
||||||
expect(activeState5.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
|
const state4 = router.getStateForAction(
|
||||||
});
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
routeName: 'Foo',
|
||||||
const Screen = () => <div />;
|
},
|
||||||
const NestedStack = () => <div />;
|
state3
|
||||||
const OtherNestedStack = () => <div />;
|
);
|
||||||
|
let activeState4 = state4.routes[state4.index];
|
||||||
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
|
expect(activeState4.routeName).toEqual('NestedStack');
|
||||||
let otherNestedRouter = StackRouter({ Foo: Screen });
|
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
|
||||||
NestedStack.router = nestedRouter;
|
|
||||||
OtherNestedStack.router = otherNestedRouter;
|
const state5 = router.getStateForAction(
|
||||||
|
{
|
||||||
let router = StackRouter(
|
type: NavigationActions.NAVIGATE,
|
||||||
{
|
routeName: 'Bar',
|
||||||
NestedStack,
|
},
|
||||||
OtherNestedStack,
|
state4
|
||||||
Bar: Screen,
|
);
|
||||||
},
|
let activeState5 = state5.routes[state5.index];
|
||||||
{
|
expect(activeState5.routeName).toEqual('NestedStack');
|
||||||
initialRouteName: 'OtherNestedStack',
|
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
test('order of handling navigate action is correct for nested stackrouters', () => {
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const Screen = () => <div />;
|
||||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
|
const NestedStack = () => <div />;
|
||||||
|
const OtherNestedStack = () => <div />;
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
|
||||||
type: NavigationActions.NAVIGATE,
|
let otherNestedRouter = StackRouter({ Foo: Screen });
|
||||||
routeName: 'Bar',
|
NestedStack.router = nestedRouter;
|
||||||
},
|
OtherNestedStack.router = otherNestedRouter;
|
||||||
state
|
|
||||||
);
|
let router = StackRouter(
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
{
|
||||||
|
NestedStack,
|
||||||
const state3 = router.getStateForAction(
|
OtherNestedStack,
|
||||||
{
|
Bar: Screen,
|
||||||
type: NavigationActions.NAVIGATE,
|
},
|
||||||
routeName: 'NestedStack',
|
{
|
||||||
},
|
initialRouteName: 'OtherNestedStack',
|
||||||
state2
|
}
|
||||||
);
|
);
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
type: NavigationActions.NAVIGATE,
|
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
const state2 = router.getStateForAction(
|
||||||
state3
|
{
|
||||||
);
|
type: NavigationActions.NAVIGATE,
|
||||||
let activeState4 = state4.routes[state4.index];
|
routeName: 'Bar',
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
},
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,56 +78,6 @@ describe('SwitchRouter', () => {
|
|||||||
expect(state3.index).toEqual(0);
|
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', () => {
|
test('order of handling navigate action is correct for nested switchrouters', () => {
|
||||||
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
||||||
// if we are focused on Other and navigate to Bar, what should happen?
|
// if we are focused on Other and navigate to Bar, what should happen?
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ describe('TabRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles path configuration', () => {
|
test.only('Handles path configuration', () => {
|
||||||
const ScreenA = () => <div />;
|
const ScreenA = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const ScreenB = () => <div />;
|
||||||
const router = TabRouter({
|
const router = TabRouter({
|
||||||
@@ -537,14 +537,17 @@ describe('TabRouter', () => {
|
|||||||
screen: ScreenA,
|
screen: ScreenA,
|
||||||
},
|
},
|
||||||
Bar: {
|
Bar: {
|
||||||
path: 'b',
|
path: 'b/:great',
|
||||||
screen: ScreenB,
|
screen: ScreenB,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const params = { foo: '42' };
|
const params = { foo: '42' };
|
||||||
const action = router.getActionForPathAndParams('b/anything', params);
|
const action = router.getActionForPathAndParams('b/anything', params);
|
||||||
const expectedAction = {
|
const expectedAction = {
|
||||||
params,
|
params: {
|
||||||
|
foo: '42',
|
||||||
|
great: 'anything',
|
||||||
|
},
|
||||||
routeName: 'Bar',
|
routeName: 'Bar',
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
};
|
};
|
||||||
@@ -565,15 +568,21 @@ describe('TabRouter', () => {
|
|||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||||
{ key: 'Bar', routeName: 'Bar', params },
|
{
|
||||||
|
key: 'Bar',
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: '42', great: 'anything' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(state2).toEqual(expectedState2);
|
expect(state2).toEqual(expectedState2);
|
||||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||||
expect(router.getPathAndParamsForState(expectedState).path).toEqual('f');
|
expect(router.getPathAndParamsForState(expectedState).path).toEqual('f');
|
||||||
expect(router.getPathAndParamsForState(expectedState2).path).toEqual('b');
|
expect(router.getPathAndParamsForState(expectedState2).path).toEqual(
|
||||||
|
'b/anything'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles default configuration', () => {
|
test('Handles default configuration', () => {
|
||||||
|
|||||||
34
src/routers/__tests__/pathUtils-test.js
Normal file
34
src/routers/__tests__/pathUtils-test.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { urlToPathAndParams } from '../pathUtils';
|
||||||
|
|
||||||
|
test('urlToPathAndParams empty', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://');
|
||||||
|
expect(path).toBe('');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams empty params', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar/b');
|
||||||
|
expect(path).toBe('foo/bar/b');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams trailing slash', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar/');
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams with params', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar?asdf=1&dude=foo');
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({ asdf: '1', dude: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams with custom delimeter', () => {
|
||||||
|
const { path, params } = urlToPathAndParams(
|
||||||
|
'https://example.com/foo/bar?asdf=1',
|
||||||
|
'https://example.com/'
|
||||||
|
);
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({ asdf: '1' });
|
||||||
|
});
|
||||||
172
src/routers/pathUtils.js
Normal file
172
src/routers/pathUtils.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import pathToRegexp from 'path-to-regexp';
|
||||||
|
import NavigationActions from '../NavigationActions';
|
||||||
|
const queryString = require('query-string');
|
||||||
|
|
||||||
|
function isEmpty(obj) {
|
||||||
|
if (!obj) return true;
|
||||||
|
for (let key in obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const urlToPathAndParams = (url, uriPrefix) => {
|
||||||
|
const searchMatch = url.match(/^(.*)\?(.*)$/);
|
||||||
|
const params = searchMatch ? queryString.parse(searchMatch[2]) : {};
|
||||||
|
const urlWithoutSearch = searchMatch ? searchMatch[1] : url;
|
||||||
|
const delimiter = uriPrefix || '://';
|
||||||
|
let path = urlWithoutSearch.split(delimiter)[1];
|
||||||
|
if (path === undefined) {
|
||||||
|
path = urlWithoutSearch;
|
||||||
|
}
|
||||||
|
if (path === '/') {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
|
if (path[path.length - 1] === '/') {
|
||||||
|
path = path.slice(0, -1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createPathParser = (
|
||||||
|
childRouters,
|
||||||
|
routeConfigs,
|
||||||
|
pathConfigs = {},
|
||||||
|
initialRouteName,
|
||||||
|
initialRouteParams
|
||||||
|
) => {
|
||||||
|
const pathsByRouteNames = {};
|
||||||
|
let paths = [];
|
||||||
|
|
||||||
|
// Build paths for each route
|
||||||
|
Object.keys(childRouters).forEach(routeName => {
|
||||||
|
let pathPattern = pathConfigs[routeName] || routeConfigs[routeName].path;
|
||||||
|
let matchExact = !!pathPattern && !childRouters[routeName];
|
||||||
|
if (pathPattern === undefined) {
|
||||||
|
pathPattern = routeName;
|
||||||
|
}
|
||||||
|
const keys = [];
|
||||||
|
let re, toPath, priority;
|
||||||
|
if (typeof pathPattern === 'string') {
|
||||||
|
// pathPattern may be either a string or a regexp object according to path-to-regexp docs.
|
||||||
|
re = pathToRegexp(pathPattern, keys);
|
||||||
|
toPath = pathToRegexp.compile(pathPattern);
|
||||||
|
priority = 0;
|
||||||
|
} else if (pathPattern === null) {
|
||||||
|
// for wildcard match
|
||||||
|
re = pathToRegexp('*', keys);
|
||||||
|
toPath = () => '';
|
||||||
|
matchExact = true;
|
||||||
|
priority = -1;
|
||||||
|
}
|
||||||
|
if (!matchExact) {
|
||||||
|
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
|
||||||
|
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
|
||||||
|
}
|
||||||
|
pathsByRouteNames[routeName] = { re, keys, toPath, priority, pathPattern };
|
||||||
|
});
|
||||||
|
|
||||||
|
paths = Object.entries(pathsByRouteNames);
|
||||||
|
paths.sort((a, b) => b[1].priority - a[1].priority);
|
||||||
|
|
||||||
|
const getActionForPathAndParams = (pathToResolve, inputParams = {}) => {
|
||||||
|
// If the path is empty (null or empty string)
|
||||||
|
// just return the initial route action
|
||||||
|
if (!pathToResolve) {
|
||||||
|
return NavigationActions.navigate({
|
||||||
|
routeName: initialRouteName,
|
||||||
|
params: { ...inputParams, ...initialRouteParams },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to match `pathToResolve` with a route in this router's
|
||||||
|
// routeConfigs
|
||||||
|
let matchedRouteName;
|
||||||
|
let pathMatch;
|
||||||
|
let pathMatchKeys;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const [routeName, path] of paths) {
|
||||||
|
const { re, keys } = path;
|
||||||
|
pathMatch = re.exec(pathToResolve);
|
||||||
|
if (pathMatch && pathMatch.length) {
|
||||||
|
pathMatchKeys = keys;
|
||||||
|
matchedRouteName = routeName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't match -- return null to signify no action available
|
||||||
|
if (!matchedRouteName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine nested actions:
|
||||||
|
// If our matched route for this router is a child router,
|
||||||
|
// get the action for the path AFTER the matched path for this
|
||||||
|
// router
|
||||||
|
let nestedAction;
|
||||||
|
if (childRouters[matchedRouteName]) {
|
||||||
|
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
|
||||||
|
pathMatch.slice(pathMatchKeys.length).join('/'),
|
||||||
|
inputParams
|
||||||
|
);
|
||||||
|
if (!nestedAction) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = pathMatch.slice(1).reduce(
|
||||||
|
// iterate over matched path params
|
||||||
|
(paramsOut, matchResult, i) => {
|
||||||
|
const key = pathMatchKeys[i];
|
||||||
|
if (!key || key.asterisk) {
|
||||||
|
return paramsOut;
|
||||||
|
}
|
||||||
|
const paramName = key.name;
|
||||||
|
|
||||||
|
let decodedMatchResult;
|
||||||
|
try {
|
||||||
|
decodedMatchResult = decodeURIComponent(matchResult);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore `URIError: malformed URI`
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsOut[paramName] = decodedMatchResult || matchResult;
|
||||||
|
return paramsOut;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// start with the input(query string) params, which will get overridden by path params
|
||||||
|
...inputParams,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return NavigationActions.navigate({
|
||||||
|
routeName: matchedRouteName,
|
||||||
|
...(params ? { params } : {}),
|
||||||
|
...(nestedAction ? { action: nestedAction } : {}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getPathAndParamsForRoute = route => {
|
||||||
|
const { routeName, params } = route;
|
||||||
|
const childRouter = childRouters[routeName];
|
||||||
|
const subPath = pathsByRouteNames[routeName].toPath(params);
|
||||||
|
if (childRouter) {
|
||||||
|
// If it has a router it's a navigator.
|
||||||
|
// If it doesn't have router it's an ordinary React component.
|
||||||
|
const child = childRouter.getPathAndParamsForState(route);
|
||||||
|
return {
|
||||||
|
path: subPath ? `${subPath}/${child.path}` : child.path,
|
||||||
|
params: child.params ? { ...params, ...child.params } : params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path: subPath,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return { getActionForPathAndParams, getPathAndParamsForRoute };
|
||||||
|
};
|
||||||
@@ -21,25 +21,24 @@ function validateRouteConfigMap(routeConfigs) {
|
|||||||
typeof screenComponent !== 'string' &&
|
typeof screenComponent !== 'string' &&
|
||||||
!routeConfig.getScreen)
|
!routeConfig.getScreen)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(`The component for route '${routeName}' must be a React component. For example:
|
||||||
`The component for route '${routeName}' must be a ` +
|
|
||||||
'React component. For example:\n\n' +
|
import MyScreen from './MyScreen';
|
||||||
"import MyScreen from './MyScreen';\n" +
|
...
|
||||||
'...\n' +
|
${routeName}: MyScreen,
|
||||||
`${routeName}: MyScreen,\n` +
|
}
|
||||||
'}\n\n' +
|
|
||||||
'You can also use a navigator:\n\n' +
|
You can also use a navigator:
|
||||||
"import MyNavigator from './MyNavigator';\n" +
|
|
||||||
'...\n' +
|
import MyNavigator from './MyNavigator';
|
||||||
`${routeName}: MyNavigator,\n` +
|
...
|
||||||
'}'
|
${routeName}: MyNavigator,
|
||||||
);
|
}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeConfig.screen && routeConfig.getScreen) {
|
if (routeConfig.screen && routeConfig.getScreen) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Route '${routeName}' should declare a screen or ` +
|
`Route '${routeName}' should declare a screen or a getScreen, not both.`
|
||||||
'a getScreen, not both.'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2013-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use invariant() to assert state which your program assumes to be true.
|
* Use invariant() to assert state which your program assumes to be true.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2013-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*
|
|
||||||
* @typechecks
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*eslint-disable no-self-compare */
|
/*eslint-disable no-self-compare */
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,4 +57,4 @@ function shallowEqual(objA, objB) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = shallowEqual;
|
export default shallowEqual;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
View,
|
View,
|
||||||
I18nManager,
|
I18nManager,
|
||||||
ViewPropTypes,
|
ViewPropTypes,
|
||||||
|
MaskedViewIOS,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { MaskedViewIOS } from '../../PlatformHelpers';
|
|
||||||
import SafeAreaView from 'react-native-safe-area-view';
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import HeaderTitle from './HeaderTitle';
|
import HeaderTitle from './HeaderTitle';
|
||||||
@@ -55,6 +55,15 @@ class Header extends React.PureComponent {
|
|||||||
if (typeof options.headerTitle === 'string') {
|
if (typeof options.headerTitle === 'string') {
|
||||||
return options.headerTitle;
|
return options.headerTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.title && typeof options.title !== 'string' && __DEV__) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid title for route "${
|
||||||
|
scene.route.routeName
|
||||||
|
}" - title must be string or null, instead it was of type ${typeof options.title}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return options.title;
|
return options.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +473,18 @@ class Header extends React.PureComponent {
|
|||||||
flexShrink,
|
flexShrink,
|
||||||
flexBasis,
|
flexBasis,
|
||||||
flexWrap,
|
flexWrap,
|
||||||
|
position,
|
||||||
|
padding,
|
||||||
|
paddingHorizontal,
|
||||||
|
paddingRight,
|
||||||
|
paddingLeft,
|
||||||
|
// paddingVertical,
|
||||||
|
// paddingTop,
|
||||||
|
// paddingBottom,
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
left,
|
||||||
...safeHeaderStyle
|
...safeHeaderStyle
|
||||||
} = headerStyleObj;
|
} = headerStyleObj;
|
||||||
|
|
||||||
@@ -476,6 +497,18 @@ class Header extends React.PureComponent {
|
|||||||
warnIfHeaderStyleDefined(flexShrink, 'flexShrink');
|
warnIfHeaderStyleDefined(flexShrink, 'flexShrink');
|
||||||
warnIfHeaderStyleDefined(flexBasis, 'flexBasis');
|
warnIfHeaderStyleDefined(flexBasis, 'flexBasis');
|
||||||
warnIfHeaderStyleDefined(flexWrap, 'flexWrap');
|
warnIfHeaderStyleDefined(flexWrap, 'flexWrap');
|
||||||
|
warnIfHeaderStyleDefined(padding, 'padding');
|
||||||
|
warnIfHeaderStyleDefined(position, 'position');
|
||||||
|
warnIfHeaderStyleDefined(paddingHorizontal, 'paddingHorizontal');
|
||||||
|
warnIfHeaderStyleDefined(paddingRight, 'paddingRight');
|
||||||
|
warnIfHeaderStyleDefined(paddingLeft, 'paddingLeft');
|
||||||
|
// warnIfHeaderStyleDefined(paddingVertical, 'paddingVertical');
|
||||||
|
// warnIfHeaderStyleDefined(paddingTop, 'paddingTop');
|
||||||
|
// warnIfHeaderStyleDefined(paddingBottom, 'paddingBottom');
|
||||||
|
warnIfHeaderStyleDefined(top, 'top');
|
||||||
|
warnIfHeaderStyleDefined(right, 'right');
|
||||||
|
warnIfHeaderStyleDefined(bottom, 'bottom');
|
||||||
|
warnIfHeaderStyleDefined(left, 'left');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: warn if any unsafe styles are provided
|
// TODO: warn if any unsafe styles are provided
|
||||||
@@ -491,7 +524,14 @@ 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)}>
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
this.props.layoutInterpolator(this.props),
|
||||||
|
Platform.OS === 'ios'
|
||||||
|
? { backgroundColor: DEFAULT_BACKGROUND_COLOR }
|
||||||
|
: null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
<SafeAreaView forceInset={forceInset} style={containerStyles}>
|
||||||
<View style={StyleSheet.absoluteFill}>
|
<View style={StyleSheet.absoluteFill}>
|
||||||
{options.headerBackground}
|
{options.headerBackground}
|
||||||
@@ -504,7 +544,11 @@ class Header extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function warnIfHeaderStyleDefined(value, styleProp) {
|
function warnIfHeaderStyleDefined(value, styleProp) {
|
||||||
if (value !== undefined) {
|
if (styleProp === 'position' && value === 'absolute') {
|
||||||
|
console.warn(
|
||||||
|
"position: 'absolute' is not supported on headerStyle. If you would like to render content under the header, use the headerTransparent navigationOption."
|
||||||
|
);
|
||||||
|
} else if (value !== undefined) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`${styleProp} was given a value of ${value}, this has no effect on headerStyle.`
|
`${styleProp} was given a value of ${value}, this has no effect on headerStyle.`
|
||||||
);
|
);
|
||||||
@@ -529,9 +573,11 @@ if (Platform.OS === 'ios') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_BACKGROUND_COLOR = Platform.OS === 'ios' ? '#F7F7F7' : '#FFF';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
backgroundColor: DEFAULT_BACKGROUND_COLOR,
|
||||||
...platformContainerStyles,
|
...platformContainerStyles,
|
||||||
},
|
},
|
||||||
transparentContainer: {
|
transparentContainer: {
|
||||||
@@ -540,6 +586,7 @@ const styles = StyleSheet.create({
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
...platformContainerStyles,
|
...platformContainerStyles,
|
||||||
|
elevation: 0,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
|
|||||||
@@ -58,7 +58,15 @@ function forLayout(props) {
|
|||||||
|
|
||||||
const { first, last } = interpolate;
|
const { first, last } = interpolate;
|
||||||
const index = scene.index;
|
const index = scene.index;
|
||||||
const width = layout.initWidth;
|
|
||||||
|
// We really shouldn't render the scene at all until we know the width of the
|
||||||
|
// stack. That said, in every case that I have ever seen, this has just been
|
||||||
|
// the full width of the window. This won't continue to be true if we support
|
||||||
|
// layouts like iPad master-detail. For now, in order to solve
|
||||||
|
// https://github.com/react-navigation/react-navigation/issues/4264, I have
|
||||||
|
// opted for the heuristic that we will use the window width until we have
|
||||||
|
// measured (and they will usually be the same).
|
||||||
|
const width = layout.initWidth || Dimensions.get('window').width;
|
||||||
|
|
||||||
// Make sure the header stays hidden when transitioning between 2 screens
|
// Make sure the header stays hidden when transitioning between 2 screens
|
||||||
// with no header.
|
// with no header.
|
||||||
|
|||||||
3
src/views/NavigationConsumer.js
Normal file
3
src/views/NavigationConsumer.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { NavigationConsumer } from './NavigationContext';
|
||||||
|
|
||||||
|
export default NavigationConsumer;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import propTypes from 'prop-types';
|
|
||||||
import createReactContext from 'create-react-context';
|
import createReactContext from 'create-react-context';
|
||||||
|
|
||||||
const NavigationContext = createReactContext();
|
const NavigationContext = createReactContext();
|
||||||
|
|||||||
57
src/views/NavigationEvents.js
Normal file
57
src/views/NavigationEvents.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import withNavigation from './withNavigation';
|
||||||
|
|
||||||
|
const EventNameToPropName = {
|
||||||
|
willFocus: 'onWillFocus',
|
||||||
|
didFocus: 'onDidFocus',
|
||||||
|
willBlur: 'onWillBlur',
|
||||||
|
didBlur: 'onDidBlur',
|
||||||
|
};
|
||||||
|
|
||||||
|
const EventNames = Object.keys(EventNameToPropName);
|
||||||
|
|
||||||
|
class NavigationEvents extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.subscriptions = {};
|
||||||
|
EventNames.forEach(this.addListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
EventNames.forEach(eventName => {
|
||||||
|
const listenerHasChanged =
|
||||||
|
this.props[EventNameToPropName[eventName]] !==
|
||||||
|
prevProps[EventNameToPropName[eventName]];
|
||||||
|
if (listenerHasChanged) {
|
||||||
|
this.removeListener(eventName);
|
||||||
|
this.addListener(eventName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
EventNames.forEach(this.removeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener = eventName => {
|
||||||
|
const listener = this.props[EventNameToPropName[eventName]];
|
||||||
|
if (listener) {
|
||||||
|
this.subscriptions[eventName] = this.props.navigation.addListener(
|
||||||
|
eventName,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeListener = eventName => {
|
||||||
|
if (this.subscriptions[eventName]) {
|
||||||
|
this.subscriptions[eventName].remove();
|
||||||
|
this.subscriptions[eventName] = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withNavigation(NavigationEvents);
|
||||||
3
src/views/NavigationProvider.js
Normal file
3
src/views/NavigationProvider.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { NavigationProvider } from './NavigationContext';
|
||||||
|
|
||||||
|
export default NavigationProvider;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import propTypes from 'prop-types';
|
|
||||||
import { NavigationProvider } from './NavigationContext';
|
import { NavigationProvider } from './NavigationContext';
|
||||||
|
|
||||||
export default class SceneView extends React.PureComponent {
|
export default class SceneView extends React.PureComponent {
|
||||||
|
|||||||
@@ -3,7 +3,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 StackActions from '../../routers/StackActions';
|
import StackActions from '../../routers/StackActions';
|
||||||
import TransitionConfigs from './StackViewTransitionConfigs';
|
import TransitionConfigs from './StackViewTransitionConfigs';
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import withOrientation from '../withOrientation';
|
|||||||
import { NavigationProvider } from '../NavigationContext';
|
import { NavigationProvider } from '../NavigationContext';
|
||||||
|
|
||||||
import TransitionConfigs from './StackViewTransitionConfigs';
|
import TransitionConfigs from './StackViewTransitionConfigs';
|
||||||
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
import { supportsImprovedSpringAnimation } from '../../utils/ReactNativeFeatures';
|
||||||
|
|
||||||
const emptyFunction = () => {};
|
const emptyFunction = () => {};
|
||||||
|
|
||||||
@@ -68,6 +68,20 @@ const animatedSubscribeValue = animatedValue => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultHeaderHeight = isLandscape => {
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
if (isLandscape && !Platform.isPad) {
|
||||||
|
return 32;
|
||||||
|
} else if (IS_IPHONE_X) {
|
||||||
|
return 88;
|
||||||
|
} else {
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 56;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StackViewLayout extends React.Component {
|
class StackViewLayout extends React.Component {
|
||||||
/**
|
/**
|
||||||
* Used to identify the starting point of the position when the gesture starts, such that it can
|
* Used to identify the starting point of the position when the gesture starts, such that it can
|
||||||
@@ -89,15 +103,29 @@ class StackViewLayout extends React.Component {
|
|||||||
*/
|
*/
|
||||||
_immediateIndex = null;
|
_immediateIndex = null;
|
||||||
|
|
||||||
state = {
|
constructor(props) {
|
||||||
// Used when card's header is null and mode is float to make switch animation work correctly
|
super(props);
|
||||||
floatingHeaderHeight: 0,
|
|
||||||
};
|
this.state = {
|
||||||
|
// Used when card's header is null and mode is float to make transition
|
||||||
|
// between screens with headers and those without headers smooth.
|
||||||
|
// This is not a great heuristic here. We don't know synchronously
|
||||||
|
// on mount what the header height is so we have just used the most
|
||||||
|
// common cases here.
|
||||||
|
floatingHeaderHeight: getDefaultHeaderHeight(props.isLandscape),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_renderHeader(scene, headerMode) {
|
_renderHeader(scene, headerMode) {
|
||||||
const { options } = scene.descriptor;
|
const { options } = scene.descriptor;
|
||||||
const { header } = options;
|
const { header } = options;
|
||||||
|
|
||||||
|
if (__DEV__ && typeof header === 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid header value: "${header}". The header option must be a valid React component or null, not a string.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (header === null && headerMode === 'screen') {
|
if (header === null && headerMode === 'screen') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -119,7 +147,7 @@ class StackViewLayout extends React.Component {
|
|||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
transitionProps,
|
transitionProps,
|
||||||
prevTransitionProps,
|
lastTransitionProps,
|
||||||
...passProps
|
...passProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -154,10 +182,7 @@ class StackViewLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_reset(resetToIndex, duration) {
|
_reset(resetToIndex, duration) {
|
||||||
if (
|
if (Platform.OS === 'ios' && supportsImprovedSpringAnimation()) {
|
||||||
Platform.OS === 'ios' &&
|
|
||||||
ReactNativeFeatures.supportsImprovedSpringAnimation()
|
|
||||||
) {
|
|
||||||
Animated.spring(this.props.transitionProps.position, {
|
Animated.spring(this.props.transitionProps.position, {
|
||||||
toValue: resetToIndex,
|
toValue: resetToIndex,
|
||||||
stiffness: 5000,
|
stiffness: 5000,
|
||||||
@@ -197,10 +222,7 @@ class StackViewLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (Platform.OS === 'ios' && supportsImprovedSpringAnimation()) {
|
||||||
Platform.OS === 'ios' &&
|
|
||||||
ReactNativeFeatures.supportsImprovedSpringAnimation()
|
|
||||||
) {
|
|
||||||
Animated.spring(position, {
|
Animated.spring(position, {
|
||||||
toValue,
|
toValue,
|
||||||
stiffness: 5000,
|
stiffness: 5000,
|
||||||
@@ -236,7 +258,7 @@ class StackViewLayout extends React.Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
position.stopAnimation((value: number) => {
|
position.stopAnimation(value => {
|
||||||
this._isResponding = true;
|
this._isResponding = true;
|
||||||
this._gestureStartValue = value;
|
this._gestureStartValue = value;
|
||||||
});
|
});
|
||||||
@@ -244,7 +266,7 @@ class StackViewLayout extends React.Component {
|
|||||||
},
|
},
|
||||||
onMoveShouldSetPanResponder: (event, gesture) => {
|
onMoveShouldSetPanResponder: (event, gesture) => {
|
||||||
const {
|
const {
|
||||||
transitionProps: { navigation, position, layout, scene, scenes },
|
transitionProps: { navigation, layout, scene },
|
||||||
mode,
|
mode,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { index } = navigation.state;
|
const { index } = navigation.state;
|
||||||
@@ -407,6 +429,7 @@ class StackViewLayout extends React.Component {
|
|||||||
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;
|
const { scene } = this.props.transitionProps;
|
||||||
floatingHeader = (
|
floatingHeader = (
|
||||||
@@ -416,18 +439,10 @@ class StackViewLayout extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
transitionProps: { navigation, position, layout, scene, scenes },
|
transitionProps: { scene, scenes },
|
||||||
mode,
|
mode,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { index } = navigation.state;
|
|
||||||
const isVertical = mode === 'modal';
|
|
||||||
const { options } = scene.descriptor;
|
const { options } = scene.descriptor;
|
||||||
const gestureDirection = options.gestureDirection;
|
|
||||||
|
|
||||||
const gestureDirectionInverted =
|
|
||||||
typeof gestureDirection === 'string'
|
|
||||||
? gestureDirection === 'inverted'
|
|
||||||
: I18nManager.isRTL;
|
|
||||||
|
|
||||||
const gesturesEnabled =
|
const gesturesEnabled =
|
||||||
typeof options.gesturesEnabled === 'boolean'
|
typeof options.gesturesEnabled === 'boolean'
|
||||||
@@ -512,13 +527,14 @@ class StackViewLayout extends React.Component {
|
|||||||
return TransitionConfigs.getTransitionConfig(
|
return TransitionConfigs.getTransitionConfig(
|
||||||
this.props.transitionConfig,
|
this.props.transitionConfig,
|
||||||
this.props.transitionProps,
|
this.props.transitionProps,
|
||||||
this.props.prevTransitionProps,
|
this.props.lastTransitionProps,
|
||||||
isModal
|
isModal
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderCard = scene => {
|
_renderCard = scene => {
|
||||||
const { screenInterpolator } = this._getTransitionConfig();
|
const { screenInterpolator } = this._getTransitionConfig();
|
||||||
|
|
||||||
const style =
|
const style =
|
||||||
screenInterpolator &&
|
screenInterpolator &&
|
||||||
screenInterpolator({ ...this.props.transitionProps, scene });
|
screenInterpolator({ ...this.props.transitionProps, scene });
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Animated, Easing, Platform } from 'react-native';
|
import { Animated, Easing, Platform } from 'react-native';
|
||||||
import StyleInterpolator from './StackViewStyleInterpolator';
|
import StyleInterpolator from './StackViewStyleInterpolator';
|
||||||
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
|
import { supportsImprovedSpringAnimation } from '../../utils/ReactNativeFeatures';
|
||||||
|
|
||||||
let IOSTransitionSpec;
|
let IOSTransitionSpec;
|
||||||
if (ReactNativeFeatures.supportsImprovedSpringAnimation()) {
|
if (supportsImprovedSpringAnimation()) {
|
||||||
// These are the exact values from UINavigationController's animation configuration
|
// These are the exact values from UINavigationController's animation configuration
|
||||||
IOSTransitionSpec = {
|
IOSTransitionSpec = {
|
||||||
timing: Animated.spring,
|
timing: Animated.spring,
|
||||||
|
|||||||
241
src/views/__tests__/NavigationEvents-test.js
Normal file
241
src/views/__tests__/NavigationEvents-test.js
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import NavigationEvents from '../NavigationEvents';
|
||||||
|
import { NavigationProvider } from '../NavigationContext';
|
||||||
|
|
||||||
|
const createListener = () => payload => {};
|
||||||
|
|
||||||
|
// An easy way to create the 4 listeners prop
|
||||||
|
const createEventListenersProp = () => ({
|
||||||
|
onWillFocus: createListener(),
|
||||||
|
onDidFocus: createListener(),
|
||||||
|
onWillBlur: createListener(),
|
||||||
|
onDidBlur: createListener(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createNavigationAndHelpers = () => {
|
||||||
|
// A little API to spy on subscription remove calls that are performed during the tests
|
||||||
|
const removeCallsAPI = (() => {
|
||||||
|
let removeCalls = [];
|
||||||
|
return {
|
||||||
|
reset: () => {
|
||||||
|
removeCalls = [];
|
||||||
|
},
|
||||||
|
add: (name, handler) => {
|
||||||
|
removeCalls.push({ name, handler });
|
||||||
|
},
|
||||||
|
checkRemoveCalled: count => {
|
||||||
|
expect(removeCalls.length).toBe(count);
|
||||||
|
},
|
||||||
|
checkRemoveCalledWith: (name, handler) => {
|
||||||
|
expect(removeCalls).toContainEqual({ name, handler });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const navigation = {
|
||||||
|
addListener: jest.fn((name, handler) => {
|
||||||
|
return {
|
||||||
|
remove: () => removeCallsAPI.add(name, handler),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAddListenerCalled = count => {
|
||||||
|
expect(navigation.addListener).toHaveBeenCalledTimes(count);
|
||||||
|
};
|
||||||
|
const checkAddListenerCalledWith = (eventName, handler) => {
|
||||||
|
expect(navigation.addListener).toHaveBeenCalledWith(eventName, handler);
|
||||||
|
};
|
||||||
|
const checkRemoveCalled = count => {
|
||||||
|
removeCallsAPI.checkRemoveCalled(count);
|
||||||
|
};
|
||||||
|
const checkRemoveCalledWith = (eventName, handler) => {
|
||||||
|
removeCallsAPI.checkRemoveCalledWith(eventName, handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigation,
|
||||||
|
removeCallsAPI,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
checkRemoveCalled,
|
||||||
|
checkRemoveCalledWith,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// We test 2 distinct ways to provide the navigation to the NavigationEvents (prop/context)
|
||||||
|
const NavigationEventsTestComp = ({
|
||||||
|
withContext = true,
|
||||||
|
navigation,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
if (withContext) {
|
||||||
|
return (
|
||||||
|
<NavigationProvider value={navigation}>
|
||||||
|
<NavigationEvents {...props} />
|
||||||
|
</NavigationProvider>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <NavigationEvents navigation={navigation} {...props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('NavigationEvents', () => {
|
||||||
|
it('add all listeners with navigation prop', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const eventListenerProps = createEventListenersProp();
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
withContext={false}
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkAddListenerCalled(4);
|
||||||
|
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
||||||
|
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add all listeners with navigation context', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const eventListenerProps = createEventListenersProp();
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
withContext={true}
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkAddListenerCalled(4);
|
||||||
|
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
||||||
|
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remove all listeners on unmount', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkRemoveCalled,
|
||||||
|
checkRemoveCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const eventListenerProps = createEventListenersProp();
|
||||||
|
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkRemoveCalled(0);
|
||||||
|
component.unmount();
|
||||||
|
checkRemoveCalled(4);
|
||||||
|
checkRemoveCalledWith('willBlur', eventListenerProps.onWillBlur);
|
||||||
|
checkRemoveCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkRemoveCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkRemoveCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add a single listener', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const listener = createListener();
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp navigation={navigation} onDidFocus={listener} />
|
||||||
|
);
|
||||||
|
checkAddListenerCalled(1);
|
||||||
|
checkAddListenerCalledWith('didFocus', listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do not attempt to add/remove stable listeners on update', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const eventListenerProps = createEventListenersProp();
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
component.update(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
component.update(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkAddListenerCalled(4);
|
||||||
|
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
||||||
|
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add, remove and replace (remove+add) listeners on complex updates', () => {
|
||||||
|
const {
|
||||||
|
navigation,
|
||||||
|
checkAddListenerCalled,
|
||||||
|
checkAddListenerCalledWith,
|
||||||
|
checkRemoveCalled,
|
||||||
|
checkRemoveCalledWith,
|
||||||
|
} = createNavigationAndHelpers();
|
||||||
|
const eventListenerProps = createEventListenersProp();
|
||||||
|
|
||||||
|
const component = renderer.create(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
{...eventListenerProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
checkAddListenerCalled(4);
|
||||||
|
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
||||||
|
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
checkRemoveCalled(0);
|
||||||
|
|
||||||
|
const onWillFocus2 = createListener();
|
||||||
|
const onDidFocus2 = createListener();
|
||||||
|
|
||||||
|
component.update(
|
||||||
|
<NavigationEventsTestComp
|
||||||
|
navigation={navigation}
|
||||||
|
onWillBlur={eventListenerProps.onWillBlur}
|
||||||
|
onDidBlur={undefined}
|
||||||
|
onWillFocus={onWillFocus2}
|
||||||
|
onDidFocus={onDidFocus2}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkAddListenerCalled(6);
|
||||||
|
checkAddListenerCalledWith('willFocus', onWillFocus2);
|
||||||
|
checkAddListenerCalledWith('didFocus', onDidFocus2);
|
||||||
|
checkRemoveCalled(3);
|
||||||
|
checkRemoveCalledWith('didBlur', eventListenerProps.onDidBlur);
|
||||||
|
checkRemoveCalledWith('willFocus', eventListenerProps.onWillFocus);
|
||||||
|
checkRemoveCalledWith('didFocus', eventListenerProps.onDidFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
17
yarn.lock
17
yarn.lock
@@ -4659,6 +4659,13 @@ qs@~6.5.1:
|
|||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
query-string@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.1.0.tgz#01e7d69f6a0940dac67a937d6c6325647aa4532a"
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component "^0.2.0"
|
||||||
|
strict-uri-encode "^2.0.0"
|
||||||
|
|
||||||
random-bytes@~1.0.0:
|
random-bytes@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||||
@@ -4828,9 +4835,9 @@ react-navigation-deprecated-tab-navigator@1.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-native-tab-view "^0.0.77"
|
react-native-tab-view "^0.0.77"
|
||||||
|
|
||||||
react-navigation-drawer@0.3.2:
|
react-navigation-drawer@0.4.3:
|
||||||
version "0.3.2"
|
version "0.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-navigation-drawer/-/react-navigation-drawer-0.3.2.tgz#f9b0bd29c6859500201ec4742e81db992074aec2"
|
resolved "https://registry.yarnpkg.com/react-navigation-drawer/-/react-navigation-drawer-0.4.3.tgz#c04c94e2429b7e724801af05bd0a93a79cb27f71"
|
||||||
dependencies:
|
dependencies:
|
||||||
react-native-drawer-layout-polyfill "^1.3.2"
|
react-native-drawer-layout-polyfill "^1.3.2"
|
||||||
|
|
||||||
@@ -5565,6 +5572,10 @@ stream-to-observable@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
|
resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
|
||||||
|
|
||||||
|
strict-uri-encode@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||||
|
|
||||||
string-length@^2.0.0:
|
string-length@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||||
|
|||||||
Reference in New Issue
Block a user