mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-19 18:38:16 +08:00
Compare commits
322 Commits
@ericvicen
...
2.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
460754fde1 | ||
|
|
ffd1865485 | ||
|
|
50320bf0d9 | ||
|
|
74a04c3ce5 | ||
|
|
54d0d5180d | ||
|
|
14eb5a1e75 | ||
|
|
222c77a360 | ||
|
|
39316fc339 | ||
|
|
27eb73cc14 | ||
|
|
f01b4896e6 | ||
|
|
556c31626e | ||
|
|
b6bca3ed2e | ||
|
|
0c56b21b46 | ||
|
|
912c7ca076 | ||
|
|
73c76f1e4b | ||
|
|
d746a587b0 | ||
|
|
dee03c839a | ||
|
|
2104bf1a04 | ||
|
|
4e2a409dca | ||
|
|
51bfe8dd19 | ||
|
|
04a4512c1b | ||
|
|
4a5da86ce0 | ||
|
|
a118122aed | ||
|
|
a94f89ffe1 | ||
|
|
9d77fd6d54 | ||
|
|
13cf4497ee | ||
|
|
9175118383 | ||
|
|
6fc21250ec | ||
|
|
714d5eab6b | ||
|
|
67233dc9ef | ||
|
|
b0443c1861 | ||
|
|
c0b637df52 | ||
|
|
9a82706fba | ||
|
|
d973a26edb | ||
|
|
852e7e1974 | ||
|
|
cd3707d64b | ||
|
|
3c36db455f | ||
|
|
ec52c884c5 | ||
|
|
c4b3f25a0f | ||
|
|
93642e16e7 | ||
|
|
1a76556290 | ||
|
|
12b21f052e | ||
|
|
c1f07dc167 | ||
|
|
bc04b31d01 | ||
|
|
35307c70be | ||
|
|
7e3f4f3bec | ||
|
|
cbd0958e6f | ||
|
|
cab4d71a5e | ||
|
|
108ac0e2a9 | ||
|
|
fa4fdb9c57 | ||
|
|
ebdd2da79f | ||
|
|
1fe11c100e | ||
|
|
c4b84f1d66 | ||
|
|
69f394be5b | ||
|
|
316e4991ac | ||
|
|
805064cb5e | ||
|
|
8f199980cb | ||
|
|
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 | ||
|
|
dde091848a | ||
|
|
824fa32416 | ||
|
|
c518e7f36c | ||
|
|
1cfe01dbdb | ||
|
|
e62a9050fd | ||
|
|
310b909ba8 | ||
|
|
aebe8a5c23 | ||
|
|
e1df2c6c4a | ||
|
|
fa86718a24 | ||
|
|
c8e5673183 | ||
|
|
b312a5e307 | ||
|
|
ee6a6c53b1 | ||
|
|
8edec88341 | ||
|
|
b8d6d4253d | ||
|
|
0adb1ba9f1 | ||
|
|
d3ef3d1271 | ||
|
|
89a24bdc12 | ||
|
|
128a95b496 | ||
|
|
470eaf3b08 | ||
|
|
c91e8206a5 | ||
|
|
da283915f8 | ||
|
|
3031e7bd80 | ||
|
|
98a4f26f26 | ||
|
|
b7f5435c93 | ||
|
|
21ef4fcb82 | ||
|
|
5f64a2a9cb | ||
|
|
992d0fb267 | ||
|
|
6f41379ed1 | ||
|
|
267af01e72 | ||
|
|
b68c3a755d | ||
|
|
7345634493 | ||
|
|
6517169119 | ||
|
|
ea5d14a720 | ||
|
|
313d0726a8 | ||
|
|
b52f153747 | ||
|
|
44621005ff | ||
|
|
bf58364c3d | ||
|
|
b55053cde6 | ||
|
|
9abb2644a9 | ||
|
|
6a946d6ab7 | ||
|
|
395abe5200 | ||
|
|
ba62509ff4 | ||
|
|
45391db7d9 | ||
|
|
7f86362e86 | ||
|
|
99605737e9 | ||
|
|
842f5eb7b2 | ||
|
|
183ea82416 | ||
|
|
108a6504a7 | ||
|
|
f92d671746 | ||
|
|
e0c4a8f7d3 | ||
|
|
bc881c8aa1 | ||
|
|
118c19dcce | ||
|
|
01b43974e6 | ||
|
|
2f90899620 | ||
|
|
6cc86f66e1 | ||
|
|
4be99b6645 | ||
|
|
80016b7218 | ||
|
|
f555a9ec9a | ||
|
|
05cbd85d5c | ||
|
|
51965eac38 | ||
|
|
a3956bf3ce | ||
|
|
ce24c66b5a | ||
|
|
5467f0e22d | ||
|
|
1e7d8d55c3 | ||
|
|
1d2ce862c2 | ||
|
|
d778479e4a | ||
|
|
352dae50e1 | ||
|
|
61385cae59 | ||
|
|
aa3c13891e | ||
|
|
9696d7220d | ||
|
|
2b83b44816 | ||
|
|
ec749023ed | ||
|
|
adc9389eb3 | ||
|
|
54d143fee2 | ||
|
|
d50e74d0c7 | ||
|
|
22926c5230 | ||
|
|
f6c47a6c66 | ||
|
|
046a9f8930 | ||
|
|
72f17538c2 | ||
|
|
550001b053 | ||
|
|
d168ab26f9 | ||
|
|
99916328a1 | ||
|
|
08e1b53f2e | ||
|
|
2243528e97 | ||
|
|
931271198b | ||
|
|
1876706bad | ||
|
|
e97d6b26a8 | ||
|
|
ec1694f909 | ||
|
|
93db7d0c95 | ||
|
|
589b80b2fa | ||
|
|
364144d639 | ||
|
|
2fd7284fe2 | ||
|
|
6499f02157 | ||
|
|
39d5714ac0 | ||
|
|
1ceda5f04d | ||
|
|
4533883ba7 | ||
|
|
8ec2466fef | ||
|
|
1bd6593ede | ||
|
|
4e8d8ce12f | ||
|
|
9f95a7f10b | ||
|
|
f6bd3e4306 | ||
|
|
c1a94895f5 | ||
|
|
ad7cde9eb9 | ||
|
|
2643f690a9 | ||
|
|
8e52995ef3 | ||
|
|
8ed3817c90 | ||
|
|
eda9bfd567 | ||
|
|
723c5f2149 | ||
|
|
ab5481a290 | ||
|
|
df281cfed0 | ||
|
|
e0df3cf74a | ||
|
|
2440af66e4 | ||
|
|
47fe858d4e | ||
|
|
c641bee11b | ||
|
|
4b39e2db3c | ||
|
|
f7533a790f | ||
|
|
c56122466f | ||
|
|
7fc992dc58 | ||
|
|
32922cdd7d | ||
|
|
eda51b3b79 | ||
|
|
921ee09587 | ||
|
|
7ae4c60eb8 | ||
|
|
5fff7ef5c6 | ||
|
|
42bb1cc317 | ||
|
|
337fd89ad5 | ||
|
|
acf9b92ff7 | ||
|
|
5072130d6f | ||
|
|
20bbbd62ff | ||
|
|
0890896824 | ||
|
|
0cf14f8e1e | ||
|
|
e5e434c9e2 | ||
|
|
e5d8d2c216 | ||
|
|
abd5200739 | ||
|
|
202609d9cf | ||
|
|
7b4dd98255 | ||
|
|
70c644f522 | ||
|
|
5274d16e3b | ||
|
|
e5e2cbb86d | ||
|
|
a8caa0d93c | ||
|
|
f70a25a6a8 | ||
|
|
6cde6e2558 | ||
|
|
0794c0faaa | ||
|
|
ea28e84e5a | ||
|
|
419ee5318d | ||
|
|
fbbf00875b | ||
|
|
22e09f7186 | ||
|
|
ece6297e8e | ||
|
|
ad52caf57b | ||
|
|
11f5e6e8e5 | ||
|
|
1764b21f34 | ||
|
|
bbacabeba3 | ||
|
|
b140b70555 | ||
|
|
356646cbfa | ||
|
|
6234b5661e | ||
|
|
270c3f0754 | ||
|
|
6f04bdffa6 | ||
|
|
8415378784 | ||
|
|
9e47092d56 | ||
|
|
029d6ac4d2 | ||
|
|
d57d118fda | ||
|
|
2232e394bb | ||
|
|
670d48366b | ||
|
|
99ac5b6c08 | ||
|
|
68a2a106f3 | ||
|
|
b7c6d072a5 | ||
|
|
ecd9fd71e9 | ||
|
|
cfc9690326 | ||
|
|
828e7f2d43 | ||
|
|
9c3fffa47f | ||
|
|
be524e4224 | ||
|
|
095814230b | ||
|
|
9cf557bba0 | ||
|
|
5e4512f3eb | ||
|
|
ee1b5972ce | ||
|
|
2233d0e1d8 | ||
|
|
577d99c165 | ||
|
|
aa362ea776 | ||
|
|
864908a49c | ||
|
|
5cab55b8c9 | ||
|
|
9b9db86bde | ||
|
|
4def39c0f7 | ||
|
|
e6559f5878 | ||
|
|
a9d8f2e03e | ||
|
|
84a070b9d5 | ||
|
|
ee984943c7 | ||
|
|
9fdfec18f6 | ||
|
|
aee16b91a4 | ||
|
|
191439f79a | ||
|
|
b1ac152fec | ||
|
|
c588ab9f9d | ||
|
|
ae8cd41396 | ||
|
|
5038ee2360 | ||
|
|
5bf071e3ee | ||
|
|
fcbf78e658 | ||
|
|
fd75e9c14c | ||
|
|
7d36a3b137 | ||
|
|
175387b22f | ||
|
|
0dd7daecc0 | ||
|
|
42230634fd | ||
|
|
a9943e9b2e | ||
|
|
6475e32dba | ||
|
|
f67872d8f1 | ||
|
|
2c7187b22a | ||
|
|
160d44f58e | ||
|
|
d017ed01b3 | ||
|
|
c2e197f8d3 | ||
|
|
6b3968b601 | ||
|
|
b575200879 | ||
|
|
cd5bd8882e | ||
|
|
3f837c895e | ||
|
|
bc5d35aba3 | ||
|
|
9a6e0bbd98 | ||
|
|
052d22804c | ||
|
|
7a978b1087 | ||
|
|
b06fb7e432 | ||
|
|
a92ed83302 | ||
|
|
0c31bc44ea | ||
|
|
8e5ee4d312 | ||
|
|
4bb8987ab7 | ||
|
|
81a86fa091 | ||
|
|
47f357f332 | ||
|
|
bdda6fa5be | ||
|
|
b097136f74 | ||
|
|
c9b0219cec | ||
|
|
ac83cf804c | ||
|
|
cf63521516 |
26
.eslintrc
26
.eslintrc
@@ -7,18 +7,18 @@
|
||||
"prettier/react"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["react", "prettier"],
|
||||
"env": {
|
||||
"jasmine": true
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": ["error", {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
}],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
}
|
||||
],
|
||||
|
||||
"no-underscore-dangle": "off",
|
||||
"no-use-before-define": "off",
|
||||
@@ -27,24 +27,20 @@
|
||||
"no-plusplus": "off",
|
||||
"no-class-assign": "off",
|
||||
"no-duplicate-imports": "off",
|
||||
|
||||
"import/extensions": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/no-unresolved": "off",
|
||||
|
||||
"react/jsx-filename-extension": [
|
||||
"off", { "extensions": [".js", ".jsx"] }
|
||||
],
|
||||
"react/jsx-filename-extension": ["off", { "extensions": [".js", ".jsx"] }],
|
||||
|
||||
"react/sort-comp": "off",
|
||||
"react/prefer-stateless-function": "off",
|
||||
"react/no-deprecated": "off",
|
||||
|
||||
"react/forbid-prop-types": "warn",
|
||||
"react/prop-types": "off",
|
||||
"react/require-default-props": "off",
|
||||
"react/no-unused-prop-types": "off",
|
||||
},
|
||||
"settings": {
|
||||
"react/no-unused-prop-types": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
|
||||
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
|
||||
|
||||
- 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 repository 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.
|
||||
- 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:
|
||||
|
||||
## Motivation
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
197
CHANGELOG.md
Normal file
197
CHANGELOG.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# 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.13.0] - [2018-09-06](https://github.com/react-navigation/react-navigation/releases/tag/2.13.0)
|
||||
|
||||
### Added
|
||||
|
||||
- When `tabBarIcon` is a function it is now provided with a `horizontal` option that indicates whether horizontal tabs are being rendered (label to the right of the icon) or not.
|
||||
- Add some missing flow types ([1](https://github.com/react-navigation/react-navigation/pull/4836), [2](https://github.com/react-navigation/react-navigation/pull/4917)).
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated react-navigation-stack to 0.3.0.
|
||||
- Updated react-navigation-tabs to 0.7.0.
|
||||
- Pinned `create-react-context` dependency to `0.2.2` (https://github.com/react-navigation/react-navigation/issues/4934)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixes tab label font sizes in landscape and portrait.
|
||||
- Default tab bar background color and header background color are white on iOS.
|
||||
|
||||
## [2.12.1] - [2018-08-23](https://github.com/react-navigation/react-navigation/releases/tag/2.12.1)
|
||||
|
||||
### Fixed
|
||||
- Fix crash on react-native@>=0.56 described in https://github.com/react-navigation/react-navigation/issues/4886
|
||||
|
||||
## [2.12.0] - [2018-08-22](https://github.com/react-navigation/react-navigation/releases/tag/2.12.0)
|
||||
|
||||
### Changed
|
||||
- Move stack specific view code to react-navigation-stack
|
||||
- Add accessibility props for inactive screens in stack (https://github.com/react-navigation/react-navigation-stack/commit/4e04428e26df9076413b57b3346a7ce357de1a77)
|
||||
- Updated header title to match iOS 11/12 style correctly (https://github.com/react-navigation/react-navigation-stack/pull/1)
|
||||
- Add support for animating the header background on screen transitions and add interpolator to animate it along with the rest of the screen, but this is still opt-in behavior (https://github.com/react-navigation/react-navigation-stack/pull/3)
|
||||
- Updated react-native-safe-area-view to 0.9.0
|
||||
|
||||
## [2.11.2] - [2018-08-03](https://github.com/react-navigation/react-navigation/releases/tag/2.11.2)
|
||||
### Changed
|
||||
- Revert rename of pathUtils
|
||||
|
||||
## [2.11.1] - [2018-08-03](https://github.com/react-navigation/react-navigation/releases/tag/2.11.1)
|
||||
### Changed
|
||||
- Fix some exports related to the 2.11.0 changes to move stack navigator out of core
|
||||
|
||||
## [2.11.0] - [2018-08-03](https://github.com/react-navigation/react-navigation/releases/tag/2.11.0)
|
||||
### Added
|
||||
- Export some modules that are useful for moving stack navigator outside of core
|
||||
|
||||
## [2.10.0] - [2018-08-02](https://github.com/react-navigation/react-navigation/releases/tag/2.10.0)
|
||||
### Added
|
||||
- `lazy` and `optimizationsEnabled` options to `createMaterialTopTabNavigator` (react-navigation-tabs@0.6.0)
|
||||
|
||||
### Fixed
|
||||
- Android back button in stack with drawer closes drawer properly if open (react-navigation-drawer@0.5.0)
|
||||
- Fixes bug where `null` doesn't work in routerOptions `paths` object for deeplinking ([#4791](https://github.com/react-navigation/react-navigation/pull/4791))
|
||||
|
||||
## [2.9.3] - [2018-07-26](https://github.com/react-navigation/react-navigation/releases/tag/2.9.3)
|
||||
### Added
|
||||
- Add `NavigationTestUtils` which can be imported by path to be used with jest snapshot testing.
|
||||
|
||||
## [2.9.2] - [2018-07-25](https://github.com/react-navigation/react-navigation/releases/tag/2.9.2)
|
||||
### Added
|
||||
- Export `StackViewTransitionConfigs` to allow you to extend default config in custom transition configs. [#4761](https://github.com/react-navigation/react-navigation/pull/4761)
|
||||
|
||||
### Fixed
|
||||
- Error when building with haul: ref to pathToRegexp.compile. [#4658](https://github.com/react-navigation/react-navigation/pull/4658).
|
||||
|
||||
## [2.9.1] - [2018-07-24](https://github.com/react-navigation/react-navigation/releases/tag/2.9.1)
|
||||
### Fixed
|
||||
- Incorrect parameters passed to title offset calculation led to bug in header layout when no right component (https://github.com/react-navigation/react-navigation/issues/4754)
|
||||
|
||||
### Fixed
|
||||
- Typo in Header transition preset check.
|
||||
|
||||
## [2.9.0] - [2018-07-20](https://github.com/react-navigation/react-navigation/releases/tag/2.9.0)
|
||||
### Added
|
||||
- `headerLayoutPreset: 'center' | 'left'` to provide an easy solution for [questions like this](https://github.com/react-navigation/react-navigation/issues/4615).
|
||||
- `headerBackTitleEnabled` - this configuration option for stack navigator allows you to force back button titles to either be rendered or not (if you disagree with defaults for your platform and layout preset).
|
||||
|
||||
### Fixed
|
||||
- Android back button ripple is now appropriately sized (fixes [#3955](https://github.com/react-navigation/react-navigation/issues/3955)).
|
||||
- Respect header background color on container (fixes edge case where user depended on displaying content that was rendered behind the navigator, this particular behavior should not be depended on and may break in the future, but this change is still useful regardless).
|
||||
|
||||
|
||||
## [2.8.0] - [2018-07-19](https://github.com/react-navigation/react-navigation/releases/tag/2.8.0)
|
||||
### Added
|
||||
- `headerLeftContainerStyle`, `headerTitleContainerStyle`, and `headerRightContainerStyle` are exposed on `navigationOptions`. These properties allow you to customize the style of the container of `headerLeft`, `headerTitle` and `headerRight` components.
|
||||
|
||||
### Fixed
|
||||
- Fixed memory leaks in `createNavigator`: [closure scope leak](https://github.com/react-navigation/react-navigation/commit/1a765562905e93bbae0262dd20c2688221c999e8), and [clean up old descriptors](https://github.com/react-navigation/react-navigation/commit/93642e16e7ff029586b68ee732ec790504ee4862).
|
||||
|
||||
## [2.7.0] - [2018-07-17](https://github.com/react-navigation/react-navigation/releases/tag/2.7.0)
|
||||
### Added
|
||||
- The enableURLHandling prop on the top level navigator component allows you to disable deep linking handling. Currently it is always enabled. To disable it, `<RootNavigator enableURLHandling={false} />`
|
||||
|
||||
### Changed
|
||||
- StackNavigator.replace method no longer requires a key param. If the key is left undefined, the last screen in the stack will be replaced.
|
||||
|
||||
### Fixed
|
||||
- Support headerLeft component for the first screen in a stack (#4608).
|
||||
- Removed bottomBorder when `headerTransparent` is set to true.
|
||||
- Improve empty path and param handling in deep linking (#4671). This fixes issues with deep linking and fully tests the differences between path: '' and path: null. Empty string matches empty paths, and null path will let the child router handle paths at the same level. Also it makes sure that params are not duplicated between path and query when they are serialized with getPathAndParamsForState.
|
||||
- Fix onTransitionStart not being invoked when provided in navigator config.(#4100)
|
||||
- Rare case when users navigated back and forth quickly with exactly the right timing would cause a crash due to a scene being queued to transition, then clobbered, then attempted to render as a stale scene but without a descriptor. ([commit](https://github.com/react-navigation/react-navigation/commit/cab4d71a5e09188df3f4a294c98779eecb860a78))
|
||||
|
||||
## [2.6.2] - [2018-07-06](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.13.0...HEAD
|
||||
[2.13.0]: https://github.com/react-navigation/react-navigation/compare/2.12.1...2.13.0
|
||||
[2.12.1]: https://github.com/react-navigation/react-navigation/compare/2.12.0...2.12.1
|
||||
[2.12.0]: https://github.com/react-navigation/react-navigation/compare/2.11.2...2.12.0
|
||||
[2.11.2]: https://github.com/react-navigation/react-navigation/compare/2.11.1...2.11.2
|
||||
[2.11.1]: https://github.com/react-navigation/react-navigation/compare/2.11.0...2.11.1
|
||||
[2.11.0]: https://github.com/react-navigation/react-navigation/compare/2.10.0...2.11.0
|
||||
[2.10.0]: https://github.com/react-navigation/react-navigation/compare/2.9.3...2.10.0
|
||||
[2.9.3]: https://github.com/react-navigation/react-navigation/compare/2.9.2...2.9.3
|
||||
[2.9.2]: https://github.com/react-navigation/react-navigation/compare/2.9.1...2.9.2
|
||||
[2.9.1]: https://github.com/react-navigation/react-navigation/compare/2.9.0...2.9.1
|
||||
[2.9.0]: https://github.com/react-navigation/react-navigation/compare/2.8.0...2.9.0
|
||||
[2.8.0]: https://github.com/react-navigation/react-navigation/compare/2.7.0...2.8.0
|
||||
[2.7.0]: https://github.com/react-navigation/react-navigation/compare/2.6.2...2.7.0
|
||||
[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
|
||||
7
NavigationTestUtils.js
Normal file
7
NavigationTestUtils.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { _TESTING_ONLY_reset_container_count } from './src/createNavigationContainer';
|
||||
|
||||
export default {
|
||||
resetInternalState: () => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
# React Navigation
|
||||
|
||||
[](https://badge.fury.io/js/react-navigation) [](https://codecov.io/gh/react-community/react-navigation) [](https://reactnavigation.org/docs/guides/contributors)
|
||||
[](https://badge.fury.io/js/react-navigation) [](https://codecov.io/gh/react-navigation/react-navigation) [](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [](https://reactnavigation.org/docs/contributing.html)
|
||||
|
||||
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
|
||||
|
||||
@@ -55,4 +55,4 @@ This library has adopted a Code of Conduct that we expect project participants t
|
||||
|
||||
## License
|
||||
|
||||
React-navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
|
||||
React Navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
|
||||
|
||||
12
assetsTransformer.js
Normal file
12
assetsTransformer.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* This file is needed to hijack asset imports so that test files don't attempt
|
||||
* to import them as JavaScript modules.
|
||||
* See https://github.com/facebook/jest/issues/2663#issuecomment-317109798
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
process(src, filename, config, options) {
|
||||
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
|
||||
},
|
||||
};
|
||||
@@ -55,8 +55,6 @@ module.system=haste
|
||||
|
||||
emoji=true
|
||||
|
||||
experimental.strict_type_args=true
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
@@ -77,7 +75,5 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.61.0
|
||||
^0.67.0
|
||||
|
||||
@@ -5,5 +5,6 @@ import renderer from 'react-test-renderer';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const rendered = renderer.create(<App />).toJSON();
|
||||
expect(rendered).toBeTruthy();
|
||||
// Will be null because the playground uses state persistence which happens asyncronously
|
||||
expect(rendered).toEqual(null);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,6 @@ A playground for experimenting with react-navigation in a pure-JS React Native a
|
||||
|
||||
## Usage
|
||||
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
||||
|
||||
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
"splash": {
|
||||
"image": "./assets/icons/splash.png"
|
||||
},
|
||||
"sdkVersion": "25.0.0",
|
||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"packagerOpts": {
|
||||
"assetExts": [
|
||||
"ttf",
|
||||
"mp4"
|
||||
]
|
||||
},
|
||||
"sdkVersion": "28.0.0",
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"bundleIdentifier": "com.reactnavigation.example",
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"package": "com.reactnavigation.example"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import {
|
||||
StatusBar,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, StackNavigator } from 'react-navigation';
|
||||
import { SafeAreaView, createStackNavigator } from 'react-navigation';
|
||||
|
||||
import CustomTabs from './CustomTabs';
|
||||
import CustomTransitioner from './CustomTransitioner';
|
||||
@@ -26,19 +26,37 @@ import TabsInDrawer from './TabsInDrawer';
|
||||
import ModalStack from './ModalStack';
|
||||
import StacksInTabs from './StacksInTabs';
|
||||
import StacksOverTabs from './StacksOverTabs';
|
||||
import StacksOverTopTabs from './StacksOverTopTabs';
|
||||
import StacksWithKeys from './StacksWithKeys';
|
||||
import InactiveStack from './InactiveStack';
|
||||
import StackWithCustomHeaderBackImage from './StackWithCustomHeaderBackImage';
|
||||
import SimpleStack from './SimpleStack';
|
||||
import StackWithHeaderPreset from './StackWithHeaderPreset';
|
||||
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
import TabAnimations from './TabAnimations';
|
||||
import SwitchWithStacks from './SwitchWithStacks';
|
||||
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
||||
import TabsWithNavigationEvents from './TabsWithNavigationEvents';
|
||||
import KeyboardHandlingExample from './KeyboardHandlingExample';
|
||||
|
||||
const ExampleInfo = {
|
||||
SimpleStack: {
|
||||
name: 'Stack Example',
|
||||
description: 'A card stack',
|
||||
},
|
||||
SwitchWithStacks: {
|
||||
name: 'Switch between routes',
|
||||
description: 'Jump between routes',
|
||||
},
|
||||
InactiveStack: {
|
||||
name: 'Navigate idempotently to stacks in inactive routes',
|
||||
description:
|
||||
'An inactive route in a stack should be given the opportunity to handle actions',
|
||||
},
|
||||
StackWithCustomHeaderBackImage: {
|
||||
name: 'Custom header back image',
|
||||
description: 'Stack with custom header back image',
|
||||
},
|
||||
SimpleTabs: {
|
||||
name: 'Tabs Example',
|
||||
description: 'Tabs following platform conventions',
|
||||
@@ -51,10 +69,6 @@ const ExampleInfo = {
|
||||
name: 'UIKit-style Header Transitions',
|
||||
description: 'Masked back button and sliding header items. iOS only.',
|
||||
},
|
||||
StackWithHeaderPreset: {
|
||||
name: 'UIKit-style Header Transitions',
|
||||
description: 'Masked back button and sliding header items. iOS only.',
|
||||
},
|
||||
StackWithTranslucentHeader: {
|
||||
name: 'Translucent Header',
|
||||
description: 'Render arbitrary translucent content in header background.',
|
||||
@@ -93,6 +107,10 @@ const ExampleInfo = {
|
||||
name: 'Stacks over Tabs',
|
||||
description: 'Nested stack navigation that pushes on top of tabs',
|
||||
},
|
||||
StacksOverTopTabs: {
|
||||
name: 'Stacks with non-standard header height',
|
||||
description: 'Tab navigator in stack with custom header heights',
|
||||
},
|
||||
StacksWithKeys: {
|
||||
name: 'Link in Stack with keys',
|
||||
description: 'Use keys to link between screens',
|
||||
@@ -105,24 +123,37 @@ const ExampleInfo = {
|
||||
name: 'Link to Settings Tab',
|
||||
description: 'Deep linking into a route in tab',
|
||||
},
|
||||
TabAnimations: {
|
||||
name: 'Animated Tabs Example',
|
||||
description: 'Tab transitions have custom animations',
|
||||
},
|
||||
TabsWithNavigationFocus: {
|
||||
name: 'withNavigationFocus',
|
||||
description: 'Receive the focus prop to know when a screen is focused',
|
||||
},
|
||||
TabsWithNavigationEvents: {
|
||||
name: 'NavigationEvents',
|
||||
description:
|
||||
'Declarative NavigationEvents component to subscribe to navigation events',
|
||||
},
|
||||
KeyboardHandlingExample: {
|
||||
name: 'Keyboard Handling Example',
|
||||
description:
|
||||
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
|
||||
},
|
||||
};
|
||||
|
||||
const ExampleRoutes = {
|
||||
SimpleStack: SimpleStack,
|
||||
SimpleStack,
|
||||
SwitchWithStacks,
|
||||
SimpleTabs: SimpleTabs,
|
||||
Drawer: Drawer,
|
||||
// MultipleDrawer: {
|
||||
// screen: MultipleDrawer,
|
||||
// },
|
||||
StackWithHeaderPreset: StackWithHeaderPreset,
|
||||
StackWithCustomHeaderBackImage: StackWithCustomHeaderBackImage,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
StackWithHeaderPreset: StackWithHeaderPreset,
|
||||
},
|
||||
android: {},
|
||||
}),
|
||||
StackWithTranslucentHeader: StackWithTranslucentHeader,
|
||||
TabsInDrawer: TabsInDrawer,
|
||||
CustomTabs: CustomTabs,
|
||||
@@ -131,6 +162,7 @@ const ExampleRoutes = {
|
||||
StacksWithKeys: StacksWithKeys,
|
||||
StacksInTabs: StacksInTabs,
|
||||
StacksOverTabs: StacksOverTabs,
|
||||
StacksOverTopTabs: StacksOverTopTabs,
|
||||
LinkStack: {
|
||||
screen: SimpleStack,
|
||||
path: 'people/Jordan',
|
||||
@@ -139,8 +171,11 @@ const ExampleRoutes = {
|
||||
screen: SimpleTabs,
|
||||
path: 'settings',
|
||||
},
|
||||
TabAnimations,
|
||||
TabsWithNavigationFocus,
|
||||
TabsWithNavigationEvents,
|
||||
KeyboardHandlingExample,
|
||||
// This is commented out because it's rarely useful
|
||||
// InactiveStack,
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -151,7 +186,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
scrollY: new Animated.Value(0),
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
Asset.fromModule(
|
||||
require('react-navigation/src/views/assets/back-icon-mask.png')
|
||||
).downloadAsync();
|
||||
@@ -286,7 +321,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
}
|
||||
}
|
||||
|
||||
const AppNavigator = StackNavigator(
|
||||
const AppNavigator = createStackNavigator(
|
||||
{
|
||||
...ExampleRoutes,
|
||||
Index: {
|
||||
@@ -305,8 +340,7 @@ const AppNavigator = StackNavigator(
|
||||
}
|
||||
);
|
||||
|
||||
// export default () => <AppNavigator />;
|
||||
export default SimpleStack;
|
||||
export default AppNavigator;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
item: {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
TabRouter,
|
||||
} from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
Button,
|
||||
Easing,
|
||||
Image,
|
||||
Platform,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
createNavigator,
|
||||
} from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<SafeAreaView forceInset={{ top: 'always' }}>
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import { Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import {
|
||||
StackNavigator,
|
||||
DrawerNavigator,
|
||||
createStackNavigator,
|
||||
createDrawerNavigator,
|
||||
SafeAreaView,
|
||||
} from 'react-navigation';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
@@ -31,14 +32,7 @@ const InboxScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner={'Inbox Screen'} navigation={navigation} />
|
||||
);
|
||||
InboxScreen.navigationOptions = {
|
||||
drawerLabel: 'Inbox',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="move-to-inbox"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
headerTitle: 'Inbox',
|
||||
};
|
||||
|
||||
const EmailScreen = ({ navigation }) => (
|
||||
@@ -49,23 +43,38 @@ const DraftsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
|
||||
);
|
||||
DraftsScreen.navigationOptions = {
|
||||
headerTitle: 'Drafts',
|
||||
};
|
||||
|
||||
const InboxStack = createStackNavigator({
|
||||
Inbox: { screen: InboxScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
InboxStack.navigationOptions = {
|
||||
drawerLabel: 'Inbox',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="move-to-inbox"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const DraftsStack = createStackNavigator({
|
||||
Drafts: { screen: DraftsScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
DraftsStack.navigationOptions = {
|
||||
drawerLabel: 'Drafts',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
};
|
||||
|
||||
const InboxStack = StackNavigator({
|
||||
Inbox: { screen: InboxScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
const DraftsStack = StackNavigator({
|
||||
Drafts: { screen: DraftsScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
const DrawerExample = DrawerNavigator(
|
||||
const DrawerExample = createDrawerNavigator(
|
||||
{
|
||||
Inbox: {
|
||||
path: '/',
|
||||
|
||||
96
examples/NavigationPlayground/js/InactiveStack.js
Normal file
96
examples/NavigationPlayground/js/InactiveStack.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { Button, Text, StatusBar, View, StyleSheet } from 'react-native';
|
||||
import {
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createSwitchNavigator,
|
||||
NavigationActions,
|
||||
} from 'react-navigation';
|
||||
|
||||
const runSubRoutes = navigation => {
|
||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
|
||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'Second2' }));
|
||||
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
|
||||
};
|
||||
|
||||
const runSubRoutesWithIntermediate = navigation => {
|
||||
navigation.dispatch(toFirst1);
|
||||
navigation.dispatch(toSecond2);
|
||||
navigation.dispatch(toFirst);
|
||||
navigation.dispatch(toFirst2);
|
||||
};
|
||||
|
||||
const runSubAction = navigation => {
|
||||
navigation.dispatch(toFirst2);
|
||||
navigation.dispatch(toSecond2);
|
||||
navigation.dispatch(toFirstChild1);
|
||||
};
|
||||
|
||||
const DummyScreen = ({ routeName, navigation, style }) => {
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontWeight: '800' }}>
|
||||
{routeName}({navigation.state.key})
|
||||
</Text>
|
||||
<View>
|
||||
<Button title="back" onPress={() => navigation.goBack()} />
|
||||
<Button title="dismiss" onPress={() => navigation.dismiss()} />
|
||||
<Button
|
||||
title="between sub-routes"
|
||||
onPress={() => runSubRoutes(navigation)}
|
||||
/>
|
||||
<Button
|
||||
title="between sub-routes (with intermediate)"
|
||||
onPress={() => runSubRoutesWithIntermediate(navigation)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="with sub-action"
|
||||
onPress={() => runSubAction(navigation)}
|
||||
/>
|
||||
</View>
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const createDummyScreen = routeName => {
|
||||
const BoundDummyScreen = props => DummyScreen({ ...props, routeName });
|
||||
return BoundDummyScreen;
|
||||
};
|
||||
|
||||
const toFirst = NavigationActions.navigate({ routeName: 'First' });
|
||||
const toFirst1 = NavigationActions.navigate({ routeName: 'First1' });
|
||||
const toFirst2 = NavigationActions.navigate({ routeName: 'First2' });
|
||||
const toSecond2 = NavigationActions.navigate({ routeName: 'Second2' });
|
||||
const toFirstChild1 = NavigationActions.navigate({
|
||||
routeName: 'First',
|
||||
action: NavigationActions.navigate({ routeName: 'First1' }),
|
||||
});
|
||||
|
||||
export default createStackNavigator(
|
||||
{
|
||||
Other: createDummyScreen('Leaf'),
|
||||
First: createStackNavigator({
|
||||
First1: createDummyScreen('First1'),
|
||||
First2: createDummyScreen('First2'),
|
||||
}),
|
||||
Second: createStackNavigator({
|
||||
Second1: createDummyScreen('Second1'),
|
||||
Second2: createDummyScreen('Second2'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
headerMode: 'none',
|
||||
}
|
||||
);
|
||||
63
examples/NavigationPlayground/js/KeyboardHandlingExample.js
Normal file
63
examples/NavigationPlayground/js/KeyboardHandlingExample.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { StatusBar, View, TextInput, InteractionManager } from 'react-native';
|
||||
import { createStackNavigator, withNavigationFocus } from 'react-navigation';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
class ScreenOne extends React.Component {
|
||||
static navigationOptions = {
|
||||
title: 'Home',
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<View style={{ paddingTop: 30 }}>
|
||||
<Button
|
||||
onPress={() => navigation.push('ScreenTwo')}
|
||||
title="Push screen with focused text input"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenTwo extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: navigation.getParam('inputValue', 'Screen w/ Input'),
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this._textInput.focus();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<View style={{ paddingTop: 30 }}>
|
||||
<View style={{ alignSelf: 'center', paddingVertical: 20 }}>
|
||||
<TextInput
|
||||
ref={c => (this._textInput = c)}
|
||||
onChangeText={inputValue => navigation.setParams({ inputValue })}
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
height: 24,
|
||||
width: 150,
|
||||
borderColor: '#555',
|
||||
borderWidth: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createStackNavigator({
|
||||
ScreenOne,
|
||||
ScreenTwo: withNavigationFocus(ScreenTwo),
|
||||
});
|
||||
@@ -3,9 +3,10 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ScrollView, StatusBar, Text } from 'react-native';
|
||||
import { SafeAreaView, StackNavigator } from 'react-navigation';
|
||||
import { ScrollView, StatusBar, Text } from 'react-native';
|
||||
import { SafeAreaView, createStackNavigator } from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
@@ -31,7 +32,8 @@ const MyNavScreen = ({ navigation, banner }) => (
|
||||
headerVisible:
|
||||
!navigation.state.params ||
|
||||
!navigation.state.params.headerVisible,
|
||||
})}
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
@@ -57,7 +59,7 @@ MyProfileScreen.navigationOptions = ({ navigation }) => ({
|
||||
title: `${navigation.state.params.name}'s Profile!`,
|
||||
});
|
||||
|
||||
const ProfileNavigator = StackNavigator(
|
||||
const ProfileNavigator = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
@@ -87,7 +89,7 @@ MyHeaderTestScreen.navigationOptions = ({ navigation }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const ModalStack = StackNavigator(
|
||||
const ModalStack = createStackNavigator(
|
||||
{
|
||||
ProfileNavigator: {
|
||||
screen: ProfileNavigator,
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView, StyleSheet } from 'react-native';
|
||||
import { DrawerNavigator } from 'react-navigation';
|
||||
import { Platform, ScrollView, StyleSheet } from 'react-native';
|
||||
import { createDrawerNavigator } from 'react-navigation';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView style={styles.container}>
|
||||
@@ -40,7 +41,7 @@ DraftsScreen.navigationOptions = {
|
||||
),
|
||||
};
|
||||
|
||||
const DrawerExample = DrawerNavigator(
|
||||
const DrawerExample = createDrawerNavigator(
|
||||
{
|
||||
Inbox: {
|
||||
path: '/',
|
||||
@@ -59,7 +60,7 @@ const DrawerExample = DrawerNavigator(
|
||||
}
|
||||
);
|
||||
|
||||
const MainDrawerExample = DrawerNavigator({
|
||||
const MainDrawerExample = createDrawerNavigator({
|
||||
Drafts: {
|
||||
screen: DrawerExample,
|
||||
},
|
||||
|
||||
@@ -4,22 +4,44 @@
|
||||
|
||||
import type {
|
||||
NavigationScreenProp,
|
||||
NavigationState,
|
||||
NavigationStateRoute,
|
||||
NavigationEventSubscription,
|
||||
} from 'react-navigation';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||
import { StackNavigator, SafeAreaView, withNavigation } from 'react-navigation';
|
||||
import { Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
SafeAreaView,
|
||||
withNavigation,
|
||||
NavigationActions,
|
||||
StackActions,
|
||||
} from 'react-navigation';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
import { HeaderButtons } from './commonComponents/HeaderButtons';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
type MyNavScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
navigation: NavigationScreenProp<NavigationState>,
|
||||
banner: React.Node,
|
||||
};
|
||||
|
||||
class MyBackButton extends React.Component<any, any> {
|
||||
type BackButtonProps = {
|
||||
navigation: NavigationScreenProp<NavigationStateRoute>,
|
||||
};
|
||||
|
||||
class MyBackButton extends React.Component<BackButtonProps, any> {
|
||||
render() {
|
||||
return <Button onPress={this._navigateBack} title="Custom Back" />;
|
||||
return (
|
||||
<HeaderButtons>
|
||||
<HeaderButtons.Item title="Back" onPress={this._navigateBack} />
|
||||
</HeaderButtons>
|
||||
);
|
||||
}
|
||||
|
||||
_navigateBack = () => {
|
||||
@@ -32,24 +54,55 @@ const MyBackButtonWithNavigation = withNavigation(MyBackButton);
|
||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
render() {
|
||||
const { navigation, banner } = this.props;
|
||||
const { push, replace, popToTop, pop, dismiss } = navigation;
|
||||
invariant(
|
||||
push && replace && popToTop && pop && dismiss,
|
||||
'missing action creators for StackNavigator'
|
||||
);
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||
onPress={() => push('Profile', { name: 'Jane' })}
|
||||
title="Push a profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() =>
|
||||
navigation.dispatch(
|
||||
StackActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
NavigationActions.navigate({
|
||||
routeName: 'Photos',
|
||||
params: { name: 'Jane' },
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
title="Reset photos"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
||||
title="Navigate to a photos screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||
onPress={() => replace('Profile', { name: 'Lucy' })}
|
||||
title="Replace with profile"
|
||||
/>
|
||||
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<Button onPress={() => popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => pop()} title="Pop" />
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (navigation.goBack()) {
|
||||
console.log('goBack handled');
|
||||
} else {
|
||||
console.log('goBack unhandled');
|
||||
}
|
||||
}}
|
||||
title="Go back"
|
||||
/>
|
||||
<Button onPress={() => dismiss()} title="Dismiss" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -57,7 +110,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
}
|
||||
|
||||
type MyHomeScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
navigation: NavigationScreenProp<NavigationState>,
|
||||
};
|
||||
|
||||
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
@@ -82,16 +135,16 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus HomeScreen', a);
|
||||
DEBUG && console.log('_willFocus HomeScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus HomeScreen', a);
|
||||
DEBUG && console.log('_didFocus HomeScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur HomeScreen', a);
|
||||
DEBUG && console.log('_willBlur HomeScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur HomeScreen', a);
|
||||
DEBUG && console.log('_didBlur HomeScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -101,7 +154,7 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
}
|
||||
|
||||
type MyPhotosScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
navigation: NavigationScreenProp<NavigationState>,
|
||||
};
|
||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
static navigationOptions = {
|
||||
@@ -126,23 +179,23 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus PhotosScreen', a);
|
||||
DEBUG && console.log('_willFocus PhotosScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus PhotosScreen', a);
|
||||
DEBUG && console.log('_didFocus PhotosScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur PhotosScreen', a);
|
||||
DEBUG && console.log('_willBlur PhotosScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur PhotosScreen', a);
|
||||
DEBUG && console.log('_didBlur PhotosScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.name}'s Photos`}
|
||||
banner={`${navigation.getParam('name')}'s Photos`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
@@ -151,9 +204,9 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
|
||||
const MyProfileScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
|
||||
navigation.state.params.name
|
||||
}'s Profile`}
|
||||
banner={`${
|
||||
navigation.getParam('mode') === 'edit' ? 'Now Editing ' : ''
|
||||
}${navigation.getParam('name')}'s Profile`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
@@ -168,28 +221,35 @@ MyProfileScreen.navigationOptions = props => {
|
||||
// Render a button on the right side of the header.
|
||||
// When pressed switches the screen to edit mode.
|
||||
headerRight: (
|
||||
<Button
|
||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||
onPress={() =>
|
||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||
}
|
||||
/>
|
||||
<HeaderButtons>
|
||||
<HeaderButtons.Item
|
||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||
onPress={() =>
|
||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||
}
|
||||
/>
|
||||
</HeaderButtons>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const SimpleStack = StackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
const SimpleStack = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
});
|
||||
{
|
||||
// headerLayoutPreset: 'center',
|
||||
}
|
||||
);
|
||||
|
||||
export default SimpleStack;
|
||||
|
||||
@@ -8,10 +8,11 @@ import type {
|
||||
} from 'react-navigation';
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView, StatusBar, View } from 'react-native';
|
||||
import { SafeAreaView, TabNavigator } from 'react-navigation';
|
||||
import { Platform, ScrollView, StatusBar, View } from 'react-native';
|
||||
import { SafeAreaView, createBottomTabNavigator } from 'react-navigation';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
@@ -39,10 +40,10 @@ MyHomeScreen.navigationOptions = {
|
||||
accessibilityLabel: 'TEST_ID_HOME_ACLBL',
|
||||
},
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||
size={26}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
@@ -59,10 +60,10 @@ class MyPeopleScreen extends React.Component<MyPeopleScreenProps> {
|
||||
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'People',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-people' : 'ios-people-outline'}
|
||||
size={26}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
@@ -99,10 +100,10 @@ class MyChatScreen extends React.Component<MyChatScreenProps> {
|
||||
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
|
||||
size={26}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
@@ -134,16 +135,16 @@ const MySettingsScreen = ({ navigation }) => (
|
||||
|
||||
MySettingsScreen.navigationOptions = {
|
||||
tabBarLabel: 'Settings',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||
size={26}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const SimpleTabs = TabNavigator(
|
||||
const SimpleTabs = createBottomTabNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
@@ -163,10 +164,8 @@ const SimpleTabs = TabNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
lazy: true,
|
||||
removeClippedSubviews: true,
|
||||
tabBarOptions: {
|
||||
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
|
||||
activeTintColor: '#e91e63',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { NavigationScreenProp } from 'react-navigation';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Image, Button, StatusBar, StyleSheet } from 'react-native';
|
||||
import { createStackNavigator, SafeAreaView } from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
type MyNavScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
banner: React.Node,
|
||||
};
|
||||
|
||||
class MyCustomHeaderBackImage extends React.Component<any, any> {
|
||||
render() {
|
||||
const source = require('./assets/back.png');
|
||||
return (
|
||||
<Image
|
||||
source={source}
|
||||
style={[styles.myCustomHeaderBackImage, this.props.style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
render() {
|
||||
const { navigation, banner } = this.props;
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
||||
title="Navigate to a photos screen"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type MyHomeScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
};
|
||||
|
||||
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
static navigationOptions = {
|
||||
title: 'Welcome',
|
||||
headerBackTitle: null,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
|
||||
}
|
||||
}
|
||||
|
||||
type MyPhotosScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
};
|
||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: `${navigation.state.params.name}'s photos`,
|
||||
headerBackTitle: null,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<SampleText>{`${navigation.state.params.name}'s Photos`}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jane' })}
|
||||
title="Navigate to a profile screen"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type MyProfileScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
};
|
||||
class MyProfileScreen extends React.Component<MyProfileScreenProps> {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: 'Profile',
|
||||
headerBackImage: (
|
||||
<MyCustomHeaderBackImage style={styles.myCustomHeaderBackImageAlt} />
|
||||
),
|
||||
});
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<SampleText>{`${navigation.state.params.name}'s Profile`}</SampleText>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const StackWithCustomHeaderBackImage = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'profile/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
headerBackImage: MyCustomHeaderBackImage,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default StackWithCustomHeaderBackImage;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
myCustomHeaderBackImage: {
|
||||
height: 14.5,
|
||||
width: 24,
|
||||
marginLeft: 9,
|
||||
marginRight: 12,
|
||||
marginVertical: 12,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
myCustomHeaderBackImageAlt: {
|
||||
tintColor: '#f00',
|
||||
},
|
||||
});
|
||||
@@ -4,8 +4,11 @@
|
||||
import type { NavigationScreenProp } from 'react-navigation';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||
import { StackNavigator, SafeAreaView } from 'react-navigation';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import { createStackNavigator, SafeAreaView } from 'react-navigation';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
type NavScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
@@ -18,15 +21,17 @@ class HomeScreen extends React.Component<NavScreenProps> {
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { push } = navigation;
|
||||
invariant(push, 'missing `push` action creator for StackNavigator');
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||
<Button onPress={() => push('Other')} title="Push another screen" />
|
||||
<Button
|
||||
onPress={() => navigation.push('Other')}
|
||||
title="Push another screen"
|
||||
onPress={() => push('ScreenWithNoHeader')}
|
||||
title="Push screen with no header"
|
||||
/>
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -40,14 +45,20 @@ class OtherScreen extends React.Component<NavScreenProps> {
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { push, pop } = navigation;
|
||||
invariant(push && pop, 'missing action creators for StackNavigator');
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||
<Button
|
||||
onPress={() => navigation.push('Other')}
|
||||
onPress={() => push('ScreenWithLongTitle')}
|
||||
title="Push another screen"
|
||||
/>
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button
|
||||
onPress={() => push('ScreenWithNoHeader')}
|
||||
title="Push screen with no header"
|
||||
/>
|
||||
<Button onPress={() => pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
@@ -55,10 +66,54 @@ class OtherScreen extends React.Component<NavScreenProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const StackWithHeaderPreset = StackNavigator(
|
||||
class ScreenWithLongTitle extends React.Component<NavScreenProps> {
|
||||
static navigationOptions = {
|
||||
title: "Another title that's kind of long",
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { pop } = navigation;
|
||||
invariant(pop, 'missing `pop` action creator for StackNavigator');
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||
<Button onPress={() => pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenWithNoHeader extends React.Component<NavScreenProps> {
|
||||
static navigationOptions = {
|
||||
header: null,
|
||||
title: 'No Header',
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { push, pop } = navigation;
|
||||
invariant(push && pop, 'missing action creators for StackNavigator');
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ paddingTop: 30 }}>
|
||||
<Button onPress={() => push('Other')} title="Push another screen" />
|
||||
<Button onPress={() => pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const StackWithHeaderPreset = createStackNavigator(
|
||||
{
|
||||
Home: HomeScreen,
|
||||
Other: OtherScreen,
|
||||
ScreenWithNoHeader: ScreenWithNoHeader,
|
||||
ScreenWithLongTitle: ScreenWithLongTitle,
|
||||
},
|
||||
{
|
||||
headerTransitionPreset: 'uikit',
|
||||
|
||||
@@ -12,15 +12,23 @@ import { isIphoneX } from 'react-native-iphone-x-helper';
|
||||
import * as React from 'react';
|
||||
import { BlurView, Constants } from 'expo';
|
||||
import {
|
||||
Button,
|
||||
Dimensions,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Header, StackNavigator } from 'react-navigation';
|
||||
import {
|
||||
Header,
|
||||
HeaderStyleInterpolator,
|
||||
createStackNavigator,
|
||||
} from 'react-navigation';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
import { HeaderButtons } from './commonComponents/HeaderButtons';
|
||||
|
||||
type MyNavScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
@@ -30,11 +38,16 @@ type MyNavScreenProps = {
|
||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
render() {
|
||||
const { navigation, banner } = this.props;
|
||||
const { push, replace, popToTop, pop } = navigation;
|
||||
invariant(
|
||||
push && replace && popToTop && pop,
|
||||
'missing action creators for StackNavigator'
|
||||
);
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||
onPress={() => push('Profile', { name: 'Jane' })}
|
||||
title="Push a profile screen"
|
||||
/>
|
||||
<Button
|
||||
@@ -42,11 +55,11 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
title="Navigate to a photos screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||
onPress={() => replace('Profile', { name: 'Lucy' })}
|
||||
title="Replace with profile"
|
||||
/>
|
||||
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button onPress={() => popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
@@ -193,17 +206,19 @@ MyProfileScreen.navigationOptions = props => {
|
||||
// Render a button on the right side of the header.
|
||||
// When pressed switches the screen to edit mode.
|
||||
headerRight: (
|
||||
<Button
|
||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||
onPress={() =>
|
||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||
}
|
||||
/>
|
||||
<HeaderButtons>
|
||||
<HeaderButtons.Item
|
||||
title={params.mode === 'edit' ? 'Done' : 'Edit'}
|
||||
onPress={() =>
|
||||
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
|
||||
}
|
||||
/>
|
||||
</HeaderButtons>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const StackWithTranslucentHeader = StackNavigator(
|
||||
const StackWithTranslucentHeader = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
@@ -219,8 +234,20 @@ const StackWithTranslucentHeader = StackNavigator(
|
||||
},
|
||||
{
|
||||
headerTransitionPreset: 'uikit',
|
||||
|
||||
// You can leave this out if you don't want the card shadow to
|
||||
// be visible through the header
|
||||
transitionConfig: () => ({
|
||||
headerBackgroundInterpolator:
|
||||
HeaderStyleInterpolator.forBackgroundWithTranslation,
|
||||
}),
|
||||
|
||||
navigationOptions: {
|
||||
headerTransparent: true,
|
||||
headerStyle: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#A7A7AA',
|
||||
},
|
||||
headerBackground: Platform.select({
|
||||
ios: <BlurView style={{ flex: 1 }} intensity={98} />,
|
||||
android: (
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import {
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createBottomTabNavigator,
|
||||
} from 'react-navigation';
|
||||
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
@@ -51,7 +56,7 @@ const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MainTab = StackNavigator({
|
||||
const MainTab = createStackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
path: '/',
|
||||
@@ -68,7 +73,7 @@ const MainTab = StackNavigator({
|
||||
},
|
||||
});
|
||||
|
||||
const SettingsTab = StackNavigator({
|
||||
const SettingsTab = createStackNavigator({
|
||||
Settings: {
|
||||
screen: MySettingsScreen,
|
||||
path: '/',
|
||||
@@ -84,7 +89,7 @@ const SettingsTab = StackNavigator({
|
||||
},
|
||||
});
|
||||
|
||||
const StacksInTabs = TabNavigator(
|
||||
const StacksInTabs = createBottomTabNavigator(
|
||||
{
|
||||
MainTab: {
|
||||
screen: MainTab,
|
||||
@@ -116,9 +121,9 @@ const StacksInTabs = TabNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
tabBarPosition: 'bottom',
|
||||
animationEnabled: false,
|
||||
swipeEnabled: false,
|
||||
tabBarOptions: {
|
||||
showLabel: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import {
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createBottomTabNavigator,
|
||||
} from 'react-navigation';
|
||||
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
@@ -50,7 +55,7 @@ const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const TabNav = TabNavigator(
|
||||
const TabNav = createBottomTabNavigator(
|
||||
{
|
||||
MainTab: {
|
||||
screen: MyHomeScreen,
|
||||
@@ -89,7 +94,20 @@ const TabNav = TabNavigator(
|
||||
}
|
||||
);
|
||||
|
||||
const StacksOverTabs = StackNavigator({
|
||||
TabNav.navigationOptions = ({ navigation }) => {
|
||||
let { routeName } = navigation.state.routes[navigation.state.index];
|
||||
let title;
|
||||
if (routeName === 'SettingsTab') {
|
||||
title = 'Settings';
|
||||
} else if (routeName === 'MainTab') {
|
||||
title = 'Home';
|
||||
}
|
||||
return {
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
const StacksOverTabs = createStackNavigator({
|
||||
Root: {
|
||||
screen: TabNav,
|
||||
},
|
||||
@@ -102,9 +120,9 @@ const StacksOverTabs = StackNavigator({
|
||||
Profile: {
|
||||
screen: MyProfileScreen,
|
||||
path: '/people/:name',
|
||||
navigationOptions: ({ navigation }) => {
|
||||
title: `${navigation.state.params.name}'s Profile!`;
|
||||
},
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: `${navigation.state.params.name}'s Profile!`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
142
examples/NavigationPlayground/js/StacksOverTopTabs.js
Normal file
142
examples/NavigationPlayground/js/StacksOverTopTabs.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, ScrollView, StatusBar, StyleSheet } from 'react-native';
|
||||
import {
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createMaterialTopTabNavigator,
|
||||
} from 'react-navigation';
|
||||
import { Constants } from 'expo';
|
||||
import { MaterialTopTabBar } from 'react-navigation-tabs';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const HEADER_HEIGHT = 64;
|
||||
|
||||
const MyNavScreen = ({ navigation, banner, statusBarStyle }) => (
|
||||
<ScrollView>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||
title="Open profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('NotifSettings')}
|
||||
title="Open notifications screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('SettingsTab')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
<StatusBar barStyle={statusBarStyle || 'default'} />
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
const MyHomeScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner="Home Screen"
|
||||
navigation={navigation}
|
||||
statusBarStyle="light-content"
|
||||
/>
|
||||
);
|
||||
|
||||
const MyProfileScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.name}s Profile`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
const MyNotificationsSettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner="Settings Screen"
|
||||
navigation={navigation}
|
||||
statusBarStyle="light-content"
|
||||
/>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
stackHeader: {
|
||||
height: HEADER_HEIGHT,
|
||||
},
|
||||
tab: {
|
||||
height: HEADER_HEIGHT,
|
||||
},
|
||||
});
|
||||
|
||||
function MaterialTopTabBarWithStatusBar(props) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingTop: Constants.statusBarHeight,
|
||||
backgroundColor: '#2196f3',
|
||||
}}
|
||||
>
|
||||
<MaterialTopTabBar {...props} jumpToIndex={() => {}} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const TabNavigator = createMaterialTopTabNavigator(
|
||||
{
|
||||
MainTab: {
|
||||
screen: MyHomeScreen,
|
||||
navigationOptions: {
|
||||
title: 'Welcome',
|
||||
},
|
||||
},
|
||||
SettingsTab: {
|
||||
screen: MySettingsScreen,
|
||||
navigationOptions: {
|
||||
title: 'Settings',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tabBarComponent: MaterialTopTabBarWithStatusBar,
|
||||
tabBarOptions: {
|
||||
tabStyle: styles.tab,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const StackNavigator = createStackNavigator(
|
||||
{
|
||||
Root: {
|
||||
screen: TabNavigator,
|
||||
navigationOptions: {
|
||||
header: null,
|
||||
},
|
||||
},
|
||||
NotifSettings: {
|
||||
screen: MyNotificationsSettingsScreen,
|
||||
navigationOptions: {
|
||||
title: 'Notifications',
|
||||
},
|
||||
},
|
||||
Profile: {
|
||||
screen: MyProfileScreen,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: `${navigation.state.params.name}'s Profile!`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
headerStyle: styles.stackHeader,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default StackNavigator;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Button, StatusBar, Text, View } from 'react-native';
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
import { StatusBar, Text, View } from 'react-native';
|
||||
import { createStackNavigator } from 'react-navigation';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
class HomeScreen extends React.Component<any, any> {
|
||||
render() {
|
||||
@@ -81,7 +82,7 @@ class SettingsScreen extends React.Component<any, any> {
|
||||
}
|
||||
}
|
||||
|
||||
const Stack = StackNavigator(
|
||||
const Stack = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
|
||||
121
examples/NavigationPlayground/js/SwitchWithStacks.js
Normal file
121
examples/NavigationPlayground/js/SwitchWithStacks.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AsyncStorage,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { createStackNavigator, createSwitchNavigator } from 'react-navigation';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
class SignInScreen extends React.Component<any, any> {
|
||||
static navigationOptions = {
|
||||
title: 'Please sign in',
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Button title="Sign in!" onPress={this._signInAsync} />
|
||||
<Button
|
||||
title="Go back to other examples"
|
||||
onPress={() => this.props.navigation.goBack(null)}
|
||||
/>
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_signInAsync = async () => {
|
||||
await AsyncStorage.setItem('userToken', 'abc');
|
||||
this.props.navigation.navigate('Home');
|
||||
};
|
||||
}
|
||||
|
||||
class HomeScreen extends React.Component<any, any> {
|
||||
static navigationOptions = {
|
||||
title: 'Welcome to the app!',
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Button title="Show me more of the app" onPress={this._showMoreApp} />
|
||||
<Button title="Actually, sign me out :)" onPress={this._signOutAsync} />
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_showMoreApp = () => {
|
||||
this.props.navigation.navigate('Other');
|
||||
};
|
||||
|
||||
_signOutAsync = async () => {
|
||||
await AsyncStorage.clear();
|
||||
this.props.navigation.navigate('Auth');
|
||||
};
|
||||
}
|
||||
|
||||
class OtherScreen extends React.Component<any, any> {
|
||||
static navigationOptions = {
|
||||
title: 'Lots of features here',
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Button title="I'm done, sign me out" onPress={this._signOutAsync} />
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_signOutAsync = async () => {
|
||||
await AsyncStorage.clear();
|
||||
this.props.navigation.navigate('Auth');
|
||||
};
|
||||
}
|
||||
|
||||
class LoadingScreen extends React.Component<any, any> {
|
||||
componentDidMount() {
|
||||
this._bootstrapAsync();
|
||||
}
|
||||
|
||||
_bootstrapAsync = async () => {
|
||||
const userToken = await AsyncStorage.getItem('userToken');
|
||||
let initialRouteName = userToken ? 'App' : 'Auth';
|
||||
this.props.navigation.navigate(initialRouteName);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ActivityIndicator />
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
|
||||
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
|
||||
|
||||
export default createSwitchNavigator({
|
||||
Loading: LoadingScreen,
|
||||
App: AppStack,
|
||||
Auth: AuthStack,
|
||||
});
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Animated, Button, ScrollView, StatusBar } from 'react-native';
|
||||
import { StackNavigator, TabNavigator } from 'react-navigation';
|
||||
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||
title="Open profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('NotifSettings')}
|
||||
title="Open notifications screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('SettingsTab')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
const MyHomeScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Home Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MyProfileScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
banner={`${navigation.state.params.name}s Profile`}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
const MyNotificationsSettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MainTab = StackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
path: '/',
|
||||
navigationOptions: {
|
||||
title: 'Welcome',
|
||||
},
|
||||
},
|
||||
Profile: {
|
||||
screen: MyProfileScreen,
|
||||
path: '/people/:name',
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: `${navigation.state.params.name}'s Profile!`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const SettingsTab = StackNavigator({
|
||||
Settings: {
|
||||
screen: MySettingsScreen,
|
||||
path: '/',
|
||||
navigationOptions: () => ({
|
||||
title: 'Settings',
|
||||
}),
|
||||
},
|
||||
NotifSettings: {
|
||||
screen: MyNotificationsSettingsScreen,
|
||||
navigationOptions: {
|
||||
title: 'Notifications',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TabAnimations = TabNavigator(
|
||||
{
|
||||
MainTab: {
|
||||
screen: MainTab,
|
||||
path: '/',
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
SettingsTab: {
|
||||
screen: SettingsTab,
|
||||
path: '/settings',
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Settings',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tabBarPosition: 'bottom',
|
||||
animationEnabled: true,
|
||||
configureTransition: (currentTransitionProps,nextTransitionProps) => ({
|
||||
timing: Animated.spring,
|
||||
tension: 1,
|
||||
friction: 35,
|
||||
}),
|
||||
swipeEnabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
export default TabAnimations;
|
||||
@@ -3,41 +3,29 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView } from 'react-native';
|
||||
import { TabNavigator, DrawerNavigator } from 'react-navigation';
|
||||
import { Platform, ScrollView } from 'react-native';
|
||||
import { createDrawerNavigator } from 'react-navigation';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
import StacksOverTabs from './StacksOverTabs';
|
||||
|
||||
const TabsInDrawer = DrawerNavigator({
|
||||
const TabsInDrawer = createDrawerNavigator({
|
||||
SimpleTabs: {
|
||||
screen: SimpleTabs,
|
||||
navigationOptions: {
|
||||
drawer: () => ({
|
||||
label: 'Simple Tabs',
|
||||
icon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="filter-1"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
drawerLabel: 'Simple tabs',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="filter-1" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
},
|
||||
},
|
||||
StacksOverTabs: {
|
||||
screen: StacksOverTabs,
|
||||
navigationOptions: {
|
||||
drawer: () => ({
|
||||
label: 'Stacks Over Tabs',
|
||||
icon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="filter-2"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
drawerLabel: 'Stacks Over Tabs',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="filter-2" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -3,9 +3,11 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, SafeAreaView, Text } from 'react-native';
|
||||
import { TabNavigator, withNavigationFocus } from 'react-navigation';
|
||||
import { SafeAreaView, StatusBar, Text, View } from 'react-native';
|
||||
import { withNavigationFocus } from 'react-navigation';
|
||||
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
|
||||
@@ -65,9 +67,10 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
onPress={() => this.props.navigation.goBack(null)}
|
||||
onPress={() => this.props.navigation.pop()}
|
||||
title="Back to other examples"
|
||||
/>
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -75,7 +78,7 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
||||
return withNavigationFocus(TabScreen);
|
||||
};
|
||||
|
||||
const TabsWithNavigationFocus = TabNavigator(
|
||||
const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
|
||||
{
|
||||
One: {
|
||||
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
|
||||
@@ -92,9 +95,8 @@ const TabsWithNavigationFocus = TabNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
tabBarPosition: 'bottom',
|
||||
animationEnabled: true,
|
||||
swipeEnabled: true,
|
||||
shifting: false,
|
||||
activeTintColor: '#F44336',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
BIN
examples/NavigationPlayground/js/assets/back.png
Normal file
BIN
examples/NavigationPlayground/js/assets/back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,18 @@
|
||||
import { Button as RNButton, StyleSheet, View, Platform } from 'react-native';
|
||||
import React from 'react';
|
||||
|
||||
export const Button = props => (
|
||||
<View style={styles.margin}>
|
||||
<RNButton {...props} />
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
margin: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
margin: 10,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import DefaultHeaderButtons from 'react-navigation-header-buttons';
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export class HeaderButtons extends React.PureComponent {
|
||||
static Item = DefaultHeaderButtons.Item;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DefaultHeaderButtons
|
||||
color={Platform.OS === 'ios' ? '#037aff' : 'black'}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,29 +2,31 @@
|
||||
"name": "NavigationPlayground",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "react-native-scripts start",
|
||||
"eject": "react-native-scripts eject",
|
||||
"android": "react-native-scripts android",
|
||||
"ios": "react-native-scripts ios",
|
||||
"test": "node node_modules/jest/bin/jest.js && flow"
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"test": "flow"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^25.0.0",
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"expo": "^28.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"react": "16.3.1",
|
||||
"react-native": "^0.55.0",
|
||||
"react-native-iphone-x-helper": "^1.0.2",
|
||||
"react-navigation": "link:../.."
|
||||
"react-navigation": "link:../..",
|
||||
"react-navigation-header-buttons": "^0.0.4",
|
||||
"react-navigation-material-bottom-tabs": "^0.3.0",
|
||||
"react-navigation-tabs": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^21.0.0",
|
||||
"babel-jest": "^22.4.1",
|
||||
"babel-plugin-transform-remove-console": "^6.9.0",
|
||||
"flow-bin": "^0.61.0",
|
||||
"jest": "^21.0.1",
|
||||
"jest-expo": "^25.1.0",
|
||||
"react-native-scripts": "^1.5.0",
|
||||
"react-test-renderer": "16.0.0"
|
||||
"flow-bin": "^0.67.0",
|
||||
"jest": "^22.1.3",
|
||||
"jest-expo": "^28.0.0",
|
||||
"react-test-renderer": "16.3.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo",
|
||||
|
||||
@@ -11,7 +11,9 @@ module.exports = {
|
||||
return blacklist([
|
||||
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
|
||||
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react\/(.*)/
|
||||
/react\-navigation\/node_modules\/react\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react-native-paper\/(.*)/,
|
||||
/react\-navigation\/node_modules\/@expo\/vector-icons\/(.*)/,
|
||||
]);
|
||||
},
|
||||
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,15 @@ import { Provider } from 'react-redux';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
|
||||
import AppReducer from './src/reducers';
|
||||
import AppWithNavigationState from './src/navigators/AppNavigator';
|
||||
import { middleware } from './src/utils/redux';
|
||||
import { AppNavigator, middleware } from './src/navigators/AppNavigator';
|
||||
|
||||
const store = createStore(
|
||||
AppReducer,
|
||||
applyMiddleware(middleware),
|
||||
);
|
||||
const store = createStore(AppReducer, applyMiddleware(middleware));
|
||||
|
||||
class ReduxExampleApp extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<AppWithNavigationState />
|
||||
<AppNavigator />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
## Usage
|
||||
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"icon": "./assets/icons/react-navigation.png",
|
||||
"hideExponentText": false
|
||||
},
|
||||
"sdkVersion": "25.0.0",
|
||||
"sdkVersion": "27.0.0",
|
||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 20 KiB |
@@ -21,18 +21,19 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^25.0.0",
|
||||
"expo": "^27.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react": "16.3.1",
|
||||
"react-native": "^0.55.0",
|
||||
"react-navigation": "link:../..",
|
||||
"react-navigation-redux-helpers": "^1.0.0",
|
||||
"react-navigation-redux-helpers": "^2.0.0-beta.1",
|
||||
"react-redux": "^5.0.6",
|
||||
"redux": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^21.0.0",
|
||||
"jest": "^21.0.1",
|
||||
"babel-jest": "^22.4.1",
|
||||
"flow-bin": "^0.74.0",
|
||||
"jest": "^22.1.3",
|
||||
"jest-expo": "^25.1.0",
|
||||
"react-native-scripts": "^1.3.1",
|
||||
"react-test-renderer": "16.0.0"
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
|
||||
import { createStackNavigator } from 'react-navigation';
|
||||
import {
|
||||
reduxifyNavigator,
|
||||
createReactNavigationReduxMiddleware,
|
||||
} from 'react-navigation-redux-helpers';
|
||||
|
||||
import LoginScreen from '../components/LoginScreen';
|
||||
import MainScreen from '../components/MainScreen';
|
||||
import ProfileScreen from '../components/ProfileScreen';
|
||||
import { addListener } from '../utils/redux';
|
||||
|
||||
export const AppNavigator = StackNavigator({
|
||||
const middleware = createReactNavigationReduxMiddleware(
|
||||
'root',
|
||||
state => state.nav
|
||||
);
|
||||
|
||||
const RootNavigator = createStackNavigator({
|
||||
Login: { screen: LoginScreen },
|
||||
Main: { screen: MainScreen },
|
||||
Profile: { screen: ProfileScreen },
|
||||
});
|
||||
|
||||
class AppWithNavigationState extends React.Component {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
nav: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dispatch, nav } = this.props;
|
||||
return (
|
||||
<AppNavigator
|
||||
navigation={addNavigationHelpers({
|
||||
dispatch,
|
||||
state: nav,
|
||||
addListener,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
nav: state.nav,
|
||||
state: state.nav,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AppWithNavigationState);
|
||||
const AppNavigator = connect(mapStateToProps)(AppWithNavigationState);
|
||||
|
||||
export { RootNavigator, AppNavigator, middleware };
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
|
||||
import { AppNavigator } from '../navigators/AppNavigator';
|
||||
import { RootNavigator } from '../navigators/AppNavigator';
|
||||
|
||||
// Start with two routes: The Main screen, with the Login screen on top.
|
||||
const firstAction = AppNavigator.router.getActionForPathAndParams('Main');
|
||||
const tempNavState = AppNavigator.router.getStateForAction(firstAction);
|
||||
const secondAction = AppNavigator.router.getActionForPathAndParams('Login');
|
||||
const initialNavState = AppNavigator.router.getStateForAction(
|
||||
const firstAction = RootNavigator.router.getActionForPathAndParams('Main');
|
||||
const tempNavState = RootNavigator.router.getStateForAction(firstAction);
|
||||
const secondAction = RootNavigator.router.getActionForPathAndParams('Login');
|
||||
const initialNavState = RootNavigator.router.getStateForAction(
|
||||
secondAction,
|
||||
tempNavState
|
||||
);
|
||||
@@ -16,19 +16,19 @@ function nav(state = initialNavState, action) {
|
||||
let nextState;
|
||||
switch (action.type) {
|
||||
case 'Login':
|
||||
nextState = AppNavigator.router.getStateForAction(
|
||||
nextState = RootNavigator.router.getStateForAction(
|
||||
NavigationActions.back(),
|
||||
state
|
||||
);
|
||||
break;
|
||||
case 'Logout':
|
||||
nextState = AppNavigator.router.getStateForAction(
|
||||
nextState = RootNavigator.router.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'Login' }),
|
||||
state
|
||||
);
|
||||
break;
|
||||
default:
|
||||
nextState = AppNavigator.router.getStateForAction(action, state);
|
||||
nextState = RootNavigator.router.getStateForAction(action, state);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
createReactNavigationReduxMiddleware,
|
||||
createReduxBoundAddListener,
|
||||
} from 'react-navigation-redux-helpers';
|
||||
|
||||
const middleware = createReactNavigationReduxMiddleware(
|
||||
"root",
|
||||
state => state.nav,
|
||||
);
|
||||
const addListener = createReduxBoundAddListener("root");
|
||||
|
||||
export {
|
||||
middleware,
|
||||
addListener,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
400
flow/react-navigation.js
vendored
400
flow/react-navigation.js
vendored
@@ -69,6 +69,14 @@ declare module 'react-navigation' {
|
||||
[key: string]: mixed,
|
||||
};
|
||||
|
||||
declare export type NavigationBackAction = {|
|
||||
type: 'Navigation/BACK',
|
||||
key?: ?string,
|
||||
|};
|
||||
declare export type NavigationInitAction = {|
|
||||
type: 'Navigation/INIT',
|
||||
params?: NavigationParams,
|
||||
|};
|
||||
declare export type NavigationNavigateAction = {|
|
||||
type: 'Navigation/NAVIGATE',
|
||||
routeName: string,
|
||||
@@ -79,12 +87,6 @@ declare module 'react-navigation' {
|
||||
|
||||
key?: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationBackAction = {|
|
||||
type: 'Navigation/BACK',
|
||||
key?: ?string,
|
||||
|};
|
||||
|
||||
declare export type NavigationSetParamsAction = {|
|
||||
type: 'Navigation/SET_PARAMS',
|
||||
|
||||
@@ -95,30 +97,6 @@ declare module 'react-navigation' {
|
||||
params: NavigationParams,
|
||||
|};
|
||||
|
||||
declare export type NavigationInitAction = {|
|
||||
type: 'Navigation/INIT',
|
||||
params?: NavigationParams,
|
||||
|};
|
||||
|
||||
declare export type NavigationResetAction = {|
|
||||
type: 'Navigation/RESET',
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
|};
|
||||
|
||||
declare export type NavigationUriAction = {|
|
||||
type: 'Navigation/URI',
|
||||
uri: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationReplaceAction = {|
|
||||
+type: 'Navigation/REPLACE',
|
||||
+key: string,
|
||||
+routeName: string,
|
||||
+params?: NavigationParams,
|
||||
+action?: NavigationNavigateAction,
|
||||
|};
|
||||
declare export type NavigationPopAction = {|
|
||||
+type: 'Navigation/POP',
|
||||
+n?: number,
|
||||
@@ -135,17 +113,61 @@ declare module 'react-navigation' {
|
||||
+action?: NavigationNavigateAction,
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationResetAction = {|
|
||||
type: 'Navigation/RESET',
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
|};
|
||||
declare export type NavigationReplaceAction = {|
|
||||
+type: 'Navigation/REPLACE',
|
||||
+key: string,
|
||||
+routeName: string,
|
||||
+params?: NavigationParams,
|
||||
+action?: NavigationNavigateAction,
|
||||
|};
|
||||
declare export type NavigationCompleteTransitionAction = {|
|
||||
+type: 'Navigation/COMPLETE_TRANSITION',
|
||||
+key?: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationOpenDrawerAction = {|
|
||||
+type: 'Navigation/OPEN_DRAWER',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationCloseDrawerAction = {|
|
||||
+type: 'Navigation/CLOSE_DRAWER',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationToggleDrawerAction = {|
|
||||
+type: 'Navigation/TOGGLE_DRAWER',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationDrawerOpenedAction = {|
|
||||
+type: 'Navigation/DRAWER_OPENED',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationDrawerClosedAction = {|
|
||||
+type: 'Navigation/DRAWER_CLOSED',
|
||||
+key?: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationAction =
|
||||
| NavigationBackAction
|
||||
| NavigationInitAction
|
||||
| NavigationNavigateAction
|
||||
| NavigationReplaceAction
|
||||
| NavigationSetParamsAction
|
||||
| NavigationPopAction
|
||||
| NavigationPopToTopAction
|
||||
| NavigationPushAction
|
||||
| NavigationBackAction
|
||||
| NavigationSetParamsAction
|
||||
| NavigationResetAction;
|
||||
| NavigationResetAction
|
||||
| NavigationReplaceAction
|
||||
| NavigationCompleteTransitionAction
|
||||
| NavigationOpenDrawerAction
|
||||
| NavigationCloseDrawerAction
|
||||
| NavigationToggleDrawerAction
|
||||
| NavigationDrawerOpenedAction
|
||||
| NavigationDrawerClosedAction;
|
||||
|
||||
/**
|
||||
* NavigationState is a tree of routes for a single navigator, where each
|
||||
@@ -172,7 +194,7 @@ declare module 'react-navigation' {
|
||||
| NavigationLeafRoute
|
||||
| NavigationStateRoute;
|
||||
|
||||
declare export type NavigationLeafRoute = {
|
||||
declare export type NavigationLeafRoute = {|
|
||||
/**
|
||||
* React's key used by some navigators. No need to specify these manually,
|
||||
* they will be defined by the router.
|
||||
@@ -192,10 +214,12 @@ declare module 'react-navigation' {
|
||||
* e.g. `{ car_id: 123 }` in a route that displays a car.
|
||||
*/
|
||||
params?: NavigationParams,
|
||||
};
|
||||
|};
|
||||
|
||||
declare export type NavigationStateRoute = NavigationLeafRoute &
|
||||
NavigationState;
|
||||
declare export type NavigationStateRoute = {|
|
||||
...NavigationLeafRoute,
|
||||
...$Exact<NavigationState>,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Router
|
||||
@@ -269,24 +293,36 @@ declare module 'react-navigation' {
|
||||
|
||||
declare export type NavigationComponent =
|
||||
| NavigationScreenComponent<NavigationRoute, *, *>
|
||||
| NavigationContainer<*, *, *>
|
||||
| any;
|
||||
| NavigationContainer<*, *, *>;
|
||||
|
||||
declare interface withOptionalNavigationOptions<Options> {
|
||||
navigationOptions?: NavigationScreenConfig<Options>;
|
||||
}
|
||||
|
||||
declare export type NavigationScreenComponent<
|
||||
Route: NavigationRoute,
|
||||
Options: {},
|
||||
Props: {}
|
||||
> = React$ComponentType<NavigationNavigatorProps<Options, Route> & Props> &
|
||||
({} | { navigationOptions: NavigationScreenConfig<Options> });
|
||||
> = React$ComponentType<{
|
||||
...Props,
|
||||
...NavigationNavigatorProps<Options, Route>,
|
||||
}> &
|
||||
withOptionalNavigationOptions<Options>;
|
||||
|
||||
declare interface withRouter<State, Options> {
|
||||
router: NavigationRouter<State, Options>;
|
||||
}
|
||||
|
||||
declare export type NavigationNavigator<
|
||||
State: NavigationState,
|
||||
Options: {},
|
||||
Props: {}
|
||||
> = React$ComponentType<NavigationNavigatorProps<Options, State> & Props> & {
|
||||
router: NavigationRouter<State, Options>,
|
||||
navigationOptions?: ?NavigationScreenConfig<Options>,
|
||||
};
|
||||
> = React$ComponentType<{
|
||||
...Props,
|
||||
...NavigationNavigatorProps<Options, State>,
|
||||
}> &
|
||||
withRouter<State, Options> &
|
||||
withOptionalNavigationOptions<Options>;
|
||||
|
||||
declare export type NavigationRouteConfig =
|
||||
| NavigationComponent
|
||||
@@ -296,7 +332,6 @@ declare module 'react-navigation' {
|
||||
} & NavigationScreenRouteConfig);
|
||||
|
||||
declare export type NavigationScreenRouteConfig =
|
||||
| NavigationComponent
|
||||
| {
|
||||
screen: NavigationComponent,
|
||||
}
|
||||
@@ -344,7 +379,7 @@ declare module 'react-navigation' {
|
||||
headerTintColor?: string,
|
||||
headerLeft?: React$Node | React$ElementType,
|
||||
headerBackTitle?: string,
|
||||
headerBackImage?: ImageSource,
|
||||
headerBackImage?: React$Node | React$ElementType,
|
||||
headerTruncatedBackTitle?: string,
|
||||
headerBackTitleStyle?: TextStyleProp,
|
||||
headerPressColorAndroid?: string,
|
||||
@@ -369,6 +404,8 @@ declare module 'react-navigation' {
|
||||
mode?: 'card' | 'modal',
|
||||
headerMode?: HeaderMode,
|
||||
headerTransitionPreset?: 'fade-in-place' | 'uikit',
|
||||
headerLayoutPreset?: 'left' | 'center',
|
||||
headerBackTitleVisible?: boolean,
|
||||
cardStyle?: ViewStyleProp,
|
||||
transitionConfig?: () => TransitionConfig,
|
||||
onTransitionStart?: () => void,
|
||||
@@ -380,6 +417,20 @@ declare module 'react-navigation' {
|
||||
...NavigationStackRouterConfig,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Switch Navigator
|
||||
*/
|
||||
|
||||
declare export type NavigationSwitchRouterConfig = {|
|
||||
initialRouteName?: string,
|
||||
initialRouteParams?: NavigationParams,
|
||||
paths?: NavigationPathsConfig,
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
order?: Array<string>,
|
||||
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
|
||||
resetOnBlur?: boolean, // defaults to `true`
|
||||
|};
|
||||
|
||||
/**
|
||||
* Tab Navigator
|
||||
*/
|
||||
@@ -391,7 +442,6 @@ declare module 'react-navigation' {
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
// todo: type these as the real route names rather than 'string'
|
||||
order?: Array<string>,
|
||||
|
||||
// Does the back button cause the router to switch to the initial tab
|
||||
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|
||||
|};
|
||||
@@ -414,10 +464,10 @@ declare module 'react-navigation' {
|
||||
| ((options: { tintColor: ?string, focused: boolean }) => ?React$Node),
|
||||
tabBarVisible?: boolean,
|
||||
tabBarTestIDProps?: { testID?: string, accessibilityLabel?: string },
|
||||
tabBarOnPress?: (
|
||||
scene: TabScene,
|
||||
jumpToIndex: (index: number) => void
|
||||
) => void,
|
||||
tabBarOnPress?: ({
|
||||
navigation: NavigationScreenProp<NavigationRoute>,
|
||||
defaultHandler: () => void,
|
||||
}) => void,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -473,29 +523,46 @@ declare module 'react-navigation' {
|
||||
declare export type NavigationScreenProp<+S> = {
|
||||
+state: S,
|
||||
dispatch: NavigationDispatch,
|
||||
goBack: (routeKey?: ?string) => boolean,
|
||||
navigate: (
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
setParams: (newParams: NavigationParams) => boolean,
|
||||
addListener: (
|
||||
eventName: string,
|
||||
callback: NavigationEventCallback
|
||||
) => NavigationEventSubscription,
|
||||
push: (
|
||||
getParam: (paramName: string, fallback?: any) => any,
|
||||
isFocused: () => boolean,
|
||||
// Shared action creators that exist for all routers
|
||||
goBack: (routeKey?: ?string) => boolean,
|
||||
navigate: (
|
||||
routeName:
|
||||
| string
|
||||
| {
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction,
|
||||
key?: string,
|
||||
},
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
setParams: (newParams: NavigationParams) => boolean,
|
||||
// StackRouter action creators
|
||||
pop?: (n?: number, params?: { immediate?: boolean }) => boolean,
|
||||
popToTop?: (params?: { immediate?: boolean }) => boolean,
|
||||
push?: (
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
replace: (
|
||||
replace?: (
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
|
||||
popToTop: (params?: { immediate?: boolean }) => boolean,
|
||||
reset?: (actions: NavigationAction[], index: number) => boolean,
|
||||
dismiss?: () => boolean,
|
||||
// DrawerRouter action creators
|
||||
openDrawer?: () => boolean,
|
||||
closeDrawer?: () => boolean,
|
||||
toggleDrawer?: () => boolean,
|
||||
};
|
||||
|
||||
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
|
||||
@@ -504,6 +571,21 @@ declare module 'react-navigation' {
|
||||
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
|
||||
*/
|
||||
@@ -512,19 +594,23 @@ declare module 'react-navigation' {
|
||||
State: NavigationState,
|
||||
Options: {},
|
||||
Props: {}
|
||||
> = React$ComponentType<NavigationContainerProps<State, Options> & Props> & {
|
||||
router: NavigationRouter<State, Options>,
|
||||
navigationOptions?: ?NavigationScreenConfig<Options>,
|
||||
};
|
||||
> = React$ComponentType<{
|
||||
...Props,
|
||||
...NavigationContainerProps<State, Options>,
|
||||
}> &
|
||||
withRouter<State, Options> &
|
||||
withOptionalNavigationOptions<Options>;
|
||||
|
||||
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
|
||||
uriPrefix?: string | RegExp,
|
||||
onNavigationStateChange?: (
|
||||
onNavigationStateChange?: ?(
|
||||
NavigationState,
|
||||
NavigationState,
|
||||
NavigationAction
|
||||
) => void,
|
||||
navigation?: NavigationScreenProp<S>,
|
||||
persistenceKey?: ?string,
|
||||
renderLoadingExperimental?: React$ComponentType<{}>,
|
||||
screenProps?: *,
|
||||
navigationOptions?: O,
|
||||
}>;
|
||||
@@ -682,44 +768,75 @@ declare module 'react-navigation' {
|
||||
BACK: 'Navigation/BACK',
|
||||
INIT: 'Navigation/INIT',
|
||||
NAVIGATE: 'Navigation/NAVIGATE',
|
||||
RESET: 'Navigation/RESET',
|
||||
SET_PARAMS: 'Navigation/SET_PARAMS',
|
||||
URI: 'Navigation/URI',
|
||||
back: {
|
||||
(payload?: { key?: ?string }): NavigationBackAction,
|
||||
toString: () => string,
|
||||
},
|
||||
init: {
|
||||
(payload?: { params?: NavigationParams }): NavigationInitAction,
|
||||
toString: () => string,
|
||||
},
|
||||
navigate: {
|
||||
(payload: {
|
||||
routeName: string,
|
||||
params?: ?NavigationParams,
|
||||
action?: ?NavigationNavigateAction,
|
||||
}): NavigationNavigateAction,
|
||||
toString: () => string,
|
||||
},
|
||||
reset: {
|
||||
(payload: {
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
}): NavigationResetAction,
|
||||
toString: () => string,
|
||||
},
|
||||
setParams: {
|
||||
(payload: {
|
||||
key: string,
|
||||
params: NavigationParams,
|
||||
}): NavigationSetParamsAction,
|
||||
toString: () => string,
|
||||
},
|
||||
uri: {
|
||||
(payload: { uri: string }): NavigationUriAction,
|
||||
toString: () => string,
|
||||
},
|
||||
|
||||
back: (payload?: { key?: ?string }) => NavigationBackAction,
|
||||
init: (payload?: { params?: NavigationParams }) => NavigationInitAction,
|
||||
navigate: (payload: {
|
||||
routeName: string,
|
||||
params?: ?NavigationParams,
|
||||
action?: ?NavigationNavigateAction,
|
||||
key?: string,
|
||||
}) => NavigationNavigateAction,
|
||||
setParams: (payload: {
|
||||
key: string,
|
||||
params: NavigationParams,
|
||||
}) => NavigationSetParamsAction,
|
||||
};
|
||||
|
||||
declare export var StackActions: {
|
||||
POP: 'Navigation/POP',
|
||||
POP_TO_TOP: 'Navigation/POP_TO_TOP',
|
||||
PUSH: 'Navigation/PUSH',
|
||||
RESET: 'Navigation/RESET',
|
||||
REPLACE: 'Navigation/REPLACE',
|
||||
COMPLETE_TRANSITION: 'Navigation/COMPLETE_TRANSITION',
|
||||
|
||||
pop: (payload: {
|
||||
n?: number,
|
||||
immediate?: boolean,
|
||||
}) => NavigationPopAction,
|
||||
popToTop: (payload: {
|
||||
immediate?: boolean,
|
||||
}) => NavigationPopToTopAction,
|
||||
push: (payload: {
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction,
|
||||
key?: string,
|
||||
}) => NavigationPushAction,
|
||||
reset: (payload: {
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
}) => NavigationResetAction,
|
||||
replace: (payload: {
|
||||
key: string,
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction,
|
||||
}) => NavigationReplaceAction,
|
||||
completeTransition: (payload: {
|
||||
key?: string,
|
||||
}) => NavigationCompleteTransitionAction,
|
||||
};
|
||||
|
||||
declare export var DrawerActions: {
|
||||
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
|
||||
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
|
||||
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
|
||||
DRAWER_OPENED: 'Navigation/DRAWER_OPENED',
|
||||
DRAWER_CLOSED: 'Navigation/DRAWER_CLOSED',
|
||||
|
||||
openDrawer: (payload: {
|
||||
key?: string,
|
||||
}) => NavigationOpenDrawerAction,
|
||||
closeDrawer: (payload: {
|
||||
key?: string,
|
||||
}) => NavigationCloseDrawerAction,
|
||||
toggleDrawer: (payload: {
|
||||
key?: string,
|
||||
}) => NavigationToggleDrawerAction,
|
||||
};
|
||||
|
||||
declare type _RouterProp<S: NavigationState, O: {}> = {
|
||||
@@ -742,12 +859,16 @@ declare module 'react-navigation' {
|
||||
view: NavigationView<O, S>,
|
||||
router: NavigationRouter<S, O>,
|
||||
navigatorConfig?: NavigatorConfig
|
||||
): any;
|
||||
): NavigationNavigator<S, O, *>;
|
||||
|
||||
declare export function StackNavigator(
|
||||
routeConfigMap: NavigationRouteConfigMap,
|
||||
stackConfig?: StackNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare export function createStackNavigator(
|
||||
routeConfigMap: NavigationRouteConfigMap,
|
||||
stackConfig?: StackNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
|
||||
declare type _TabViewConfig = {|
|
||||
tabBarComponent?: React$ElementType,
|
||||
@@ -772,6 +893,30 @@ declare module 'react-navigation' {
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare export function createTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
/* TODO: fix the config for each of these tab navigator types */
|
||||
declare export function createBottomTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare export function createMaterialTopTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare type _SwitchNavigatorConfig = {|
|
||||
...NavigationSwitchRouterConfig,
|
||||
|};
|
||||
declare export function SwitchNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _SwitchNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare export function createSwitchNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _SwitchNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
|
||||
declare type _DrawerViewConfig = {|
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
@@ -793,6 +938,10 @@ declare module 'react-navigation' {
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _DrawerNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
declare export function createDrawerNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _DrawerNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
|
||||
declare export function StackRouter(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
@@ -878,12 +1027,14 @@ declare module 'react-navigation' {
|
||||
vertical?: _SafeAreaViewForceInsetValue,
|
||||
horizontal?: _SafeAreaViewForceInsetValue,
|
||||
},
|
||||
children: React$Node,
|
||||
children?: React$Node,
|
||||
style?: AnimatedViewStyleProp,
|
||||
};
|
||||
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
|
||||
|
||||
declare export var Header: React$ComponentType<HeaderProps>;
|
||||
declare export var Header: React$ComponentType<HeaderProps> & {
|
||||
HEIGHT: number,
|
||||
};
|
||||
|
||||
declare type _HeaderTitleProps = {
|
||||
children: React$Node,
|
||||
@@ -1026,13 +1177,26 @@ declare module 'react-navigation' {
|
||||
};
|
||||
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
||||
|
||||
declare type _NavigationInjectedProps = {
|
||||
navigation: NavigationScreenProp<NavigationStateRoute>,
|
||||
};
|
||||
declare export function withNavigation<T: {}>(
|
||||
Component: React$ComponentType<T & _NavigationInjectedProps>
|
||||
): React$ComponentType<T>;
|
||||
declare export function withNavigationFocus<T: {}>(
|
||||
Component: React$ComponentType<T & _NavigationInjectedProps>
|
||||
): React$ComponentType<T>;
|
||||
declare export function withNavigation<Props: {}>(
|
||||
Component: React$ComponentType<Props>
|
||||
): React$ComponentType<
|
||||
$Diff<
|
||||
Props,
|
||||
{
|
||||
navigation: NavigationScreenProp<NavigationStateRoute> | void,
|
||||
}
|
||||
>
|
||||
>;
|
||||
declare export function withNavigationFocus<Props: {}>(
|
||||
Component: React$ComponentType<Props>
|
||||
): React$ComponentType<$Diff<Props, { isFocused: boolean | void }>>;
|
||||
|
||||
declare export function getNavigation<State: NavigationState, Options: {}>(
|
||||
router: NavigationRouter<State, Options>,
|
||||
state: State,
|
||||
dispatch: NavigationDispatch,
|
||||
actionSubscribers: Set<NavigationEventCallback>,
|
||||
getScreenProps: () => {},
|
||||
getCurrentNavigation: () => NavigationScreenProp<State>
|
||||
): NavigationScreenProp<State>;
|
||||
}
|
||||
|
||||
61
package.json
61
package.json
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "1.2.1",
|
||||
"version": "2.13.0",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/react-navigation.js",
|
||||
"repository": {
|
||||
"url": "git@github.com:react-navigation/react-navigation.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author":
|
||||
"Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>",
|
||||
"author": "Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>, Brent Vatne <brent@expo.io>",
|
||||
"license": "BSD-2-Clause",
|
||||
"scripts": {
|
||||
"start": "npm run ios",
|
||||
@@ -22,38 +21,46 @@
|
||||
"format": "eslint --fix .",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"files": ["src"],
|
||||
"files": [
|
||||
"src",
|
||||
"NavigationTestUtils.js"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"clamp": "^1.0.1",
|
||||
"create-react-context": "0.2.2",
|
||||
"hoist-non-react-statics": "^2.2.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-native-drawer-layout-polyfill": "^1.3.2",
|
||||
"react-native-safe-area-view": "^0.7.0",
|
||||
"react-native-tab-view": "^0.0.74"
|
||||
"query-string": "^6.1.0",
|
||||
"react-lifecycles-compat": "^3",
|
||||
"react-native-safe-area-view": "^0.9.0",
|
||||
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
||||
"react-navigation-drawer": "0.5.0",
|
||||
"react-navigation-stack": "0.3.0",
|
||||
"react-navigation-tabs": "0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-core": "^6.25.0",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-jest": "^20.0.3",
|
||||
"babel-jest": "^22.4.1",
|
||||
"babel-preset-react-native": "^2.1.0",
|
||||
"codecov": "^2.2.0",
|
||||
"eslint": "^4.2.0",
|
||||
"eslint-config-prettier": "^2.3.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-prettier": "^2.1.2",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"eslint-plugin-react": "^7.1.0",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.1.3",
|
||||
"jest-expo": "^25.1.0",
|
||||
"lint-staged": "^4.2.1",
|
||||
"prettier": "^1.5.3",
|
||||
"prettier-eslint": "^6.4.2",
|
||||
"prettier": "^1.12.1",
|
||||
"prettier-eslint": "^8.8.1",
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native-vector-icons": "^4.2.0",
|
||||
@@ -62,14 +69,30 @@
|
||||
"jest": {
|
||||
"notify": true,
|
||||
"preset": "react-native",
|
||||
"testRegex": "./src/.*\\-test\\.js$",
|
||||
"setupFiles": ["<rootDir>/jest-setup.js"],
|
||||
"testRegex": "/__tests__/[^/]+-test\\.js$",
|
||||
"setupFiles": [
|
||||
"<rootDir>/jest-setup.js"
|
||||
],
|
||||
"coverageDirectory": "./coverage/",
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": ["lcov"],
|
||||
"collectCoverageFrom": ["src/**/*.js"],
|
||||
"coveragePathIgnorePatterns": ["jest-setup.js"],
|
||||
"modulePathIgnorePatterns": ["examples"]
|
||||
"coverageReporters": [
|
||||
"lcov"
|
||||
],
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.js"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"jest-setup.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.png$": "<rootDir>/assetsTransformer.js"
|
||||
},
|
||||
"modulePathIgnorePatterns": [
|
||||
"<rootDir>/examples/"
|
||||
],
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack)"
|
||||
]
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
|
||||
@@ -4,6 +4,6 @@ set -eo pipefail
|
||||
|
||||
case $CIRCLE_NODE_INDEX in
|
||||
0) yarn test && yarn codecov ;;
|
||||
1) yarn link && cd examples/NavigationPlayground && yarn && yarn link react-navigation && yarn test ;;
|
||||
#1) cd examples/NavigationPlayground && yarn && yarn test ;;
|
||||
#2) cd examples/ReduxExample && yarn && yarn test ;;
|
||||
esac
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
const BACK = 'Navigation/BACK';
|
||||
const INIT = 'Navigation/INIT';
|
||||
const NAVIGATE = 'Navigation/NAVIGATE';
|
||||
const POP = 'Navigation/POP';
|
||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
||||
const PUSH = 'Navigation/PUSH';
|
||||
const RESET = 'Navigation/RESET';
|
||||
const REPLACE = 'Navigation/REPLACE';
|
||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
||||
const URI = 'Navigation/URI';
|
||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
|
||||
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
|
||||
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
|
||||
|
||||
const createAction = (type, fn) => {
|
||||
fn.toString = () => type;
|
||||
return fn;
|
||||
};
|
||||
|
||||
const back = createAction(BACK, (payload = {}) => ({
|
||||
const back = (payload = {}) => ({
|
||||
type: BACK,
|
||||
key: payload.key,
|
||||
immediate: payload.immediate,
|
||||
}));
|
||||
});
|
||||
|
||||
const init = createAction(INIT, (payload = {}) => {
|
||||
const init = (payload = {}) => {
|
||||
const action = {
|
||||
type: INIT,
|
||||
};
|
||||
@@ -32,9 +17,9 @@ const init = createAction(INIT, (payload = {}) => {
|
||||
action.params = payload.params;
|
||||
}
|
||||
return action;
|
||||
});
|
||||
};
|
||||
|
||||
const navigate = createAction(NAVIGATE, payload => {
|
||||
const navigate = payload => {
|
||||
const action = {
|
||||
type: NAVIGATE,
|
||||
routeName: payload.routeName,
|
||||
@@ -49,107 +34,24 @@ const navigate = createAction(NAVIGATE, payload => {
|
||||
action.key = payload.key;
|
||||
}
|
||||
return action;
|
||||
});
|
||||
};
|
||||
|
||||
const pop = createAction(POP, payload => ({
|
||||
type: POP,
|
||||
n: payload && payload.n,
|
||||
immediate: payload && payload.immediate,
|
||||
}));
|
||||
|
||||
const popToTop = createAction(POP_TO_TOP, payload => ({
|
||||
type: POP_TO_TOP,
|
||||
immediate: payload && payload.immediate,
|
||||
key: payload && payload.key,
|
||||
}));
|
||||
|
||||
const push = createAction(PUSH, payload => {
|
||||
const action = {
|
||||
type: PUSH,
|
||||
routeName: payload.routeName,
|
||||
};
|
||||
if (payload.params) {
|
||||
action.params = payload.params;
|
||||
}
|
||||
if (payload.action) {
|
||||
action.action = payload.action;
|
||||
}
|
||||
return action;
|
||||
});
|
||||
|
||||
const reset = createAction(RESET, payload => ({
|
||||
type: RESET,
|
||||
index: payload.index,
|
||||
key: payload.key,
|
||||
actions: payload.actions,
|
||||
}));
|
||||
|
||||
const replace = createAction(REPLACE, payload => ({
|
||||
type: REPLACE,
|
||||
key: payload.key,
|
||||
newKey: payload.newKey,
|
||||
params: payload.params,
|
||||
action: payload.action,
|
||||
routeName: payload.routeName,
|
||||
immediate: payload.immediate,
|
||||
}));
|
||||
|
||||
const setParams = createAction(SET_PARAMS, payload => ({
|
||||
const setParams = payload => ({
|
||||
type: SET_PARAMS,
|
||||
key: payload.key,
|
||||
params: payload.params,
|
||||
}));
|
||||
|
||||
const uri = createAction(URI, payload => ({
|
||||
type: URI,
|
||||
uri: payload.uri,
|
||||
}));
|
||||
|
||||
const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
|
||||
type: COMPLETE_TRANSITION,
|
||||
key: payload && payload.key,
|
||||
}));
|
||||
|
||||
const openDrawer = createAction(OPEN_DRAWER, payload => ({
|
||||
type: OPEN_DRAWER,
|
||||
}));
|
||||
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
|
||||
type: CLOSE_DRAWER,
|
||||
}));
|
||||
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
|
||||
type: TOGGLE_DRAWER,
|
||||
}));
|
||||
});
|
||||
|
||||
export default {
|
||||
// Action constants
|
||||
BACK,
|
||||
INIT,
|
||||
NAVIGATE,
|
||||
POP,
|
||||
POP_TO_TOP,
|
||||
PUSH,
|
||||
RESET,
|
||||
REPLACE,
|
||||
SET_PARAMS,
|
||||
URI,
|
||||
COMPLETE_TRANSITION,
|
||||
OPEN_DRAWER,
|
||||
CLOSE_DRAWER,
|
||||
TOGGLE_DRAWER,
|
||||
|
||||
// Action creators
|
||||
back,
|
||||
init,
|
||||
navigate,
|
||||
pop,
|
||||
popToTop,
|
||||
push,
|
||||
reset,
|
||||
replace,
|
||||
setParams,
|
||||
uri,
|
||||
completeTransition,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
toggleDrawer,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
@@ -21,7 +21,7 @@ const StateUtils = {
|
||||
* routes of the navigation state, or -1 if it is not present.
|
||||
*/
|
||||
indexOf(state, key) {
|
||||
return state.routes.map(route => route.key).indexOf(key);
|
||||
return state.routes.findIndex(route => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -116,12 +116,32 @@ const StateUtils = {
|
||||
|
||||
/**
|
||||
* Replace a route by a key.
|
||||
* Note that this moves the index to the positon to where the new route in the
|
||||
* stack is at.
|
||||
* Note that this moves the index to the position to where the new route in the
|
||||
* stack is at and updates the routes array accordingly.
|
||||
*/
|
||||
replaceAt(state, key, route) {
|
||||
replaceAndPrune(state, key, route) {
|
||||
const index = StateUtils.indexOf(state, key);
|
||||
return StateUtils.replaceAtIndex(state, index, route);
|
||||
const replaced = StateUtils.replaceAtIndex(state, index, route);
|
||||
|
||||
return {
|
||||
...replaced,
|
||||
routes: replaced.routes.slice(0, index + 1),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a route by a key.
|
||||
* Note that this moves the index to the position to where the new route in the
|
||||
* stack is at. Does not prune the routes.
|
||||
* 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, preserveIndex = false) {
|
||||
const index = StateUtils.indexOf(state, key);
|
||||
const nextIndex = preserveIndex ? state.index : index;
|
||||
let nextState = StateUtils.replaceAtIndex(state, index, route);
|
||||
nextState.index = nextIndex;
|
||||
return nextState;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -137,7 +157,7 @@ const StateUtils = {
|
||||
route.key
|
||||
);
|
||||
|
||||
if (state.routes[index] === route) {
|
||||
if (state.routes[index] === route && index === state.index) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -153,7 +173,7 @@ const StateUtils = {
|
||||
|
||||
/**
|
||||
* Resets all routes.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* Note that this moves the index to the position to where the last route in the
|
||||
* stack is at if the param `index` isn't provided.
|
||||
*/
|
||||
reset(state, routes, index) {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import NavigationActions from '../NavigationActions';
|
||||
|
||||
describe('actions', () => {
|
||||
describe('generic navigation actions', () => {
|
||||
const params = { foo: 'bar' };
|
||||
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
|
||||
|
||||
it('exports back action and type', () => {
|
||||
expect(NavigationActions.back.toString()).toEqual(NavigationActions.BACK);
|
||||
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
|
||||
expect(NavigationActions.back({ key: 'test' })).toEqual({
|
||||
type: NavigationActions.BACK,
|
||||
@@ -14,7 +13,6 @@ describe('actions', () => {
|
||||
});
|
||||
|
||||
it('exports init action and type', () => {
|
||||
expect(NavigationActions.init.toString()).toEqual(NavigationActions.INIT);
|
||||
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
|
||||
expect(NavigationActions.init({ params })).toEqual({
|
||||
type: NavigationActions.INIT,
|
||||
@@ -23,9 +21,6 @@ describe('actions', () => {
|
||||
});
|
||||
|
||||
it('exports navigate action and type', () => {
|
||||
expect(NavigationActions.navigate.toString()).toEqual(
|
||||
NavigationActions.NAVIGATE
|
||||
);
|
||||
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'test',
|
||||
@@ -47,36 +42,7 @@ describe('actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('exports reset action and type', () => {
|
||||
expect(NavigationActions.reset.toString()).toEqual(NavigationActions.RESET);
|
||||
expect(NavigationActions.reset({ index: 0, actions: [] })).toEqual({
|
||||
type: NavigationActions.RESET,
|
||||
index: 0,
|
||||
actions: [],
|
||||
});
|
||||
expect(
|
||||
NavigationActions.reset({
|
||||
index: 0,
|
||||
key: 'test',
|
||||
actions: [navigateAction],
|
||||
})
|
||||
).toEqual({
|
||||
type: NavigationActions.RESET,
|
||||
index: 0,
|
||||
key: 'test',
|
||||
actions: [
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'another',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('exports setParams action and type', () => {
|
||||
expect(NavigationActions.setParams.toString()).toEqual(
|
||||
NavigationActions.SET_PARAMS
|
||||
);
|
||||
expect(
|
||||
NavigationActions.setParams({
|
||||
key: 'test',
|
||||
@@ -88,12 +54,4 @@ describe('actions', () => {
|
||||
params,
|
||||
});
|
||||
});
|
||||
|
||||
it('exports uri action and type', () => {
|
||||
expect(NavigationActions.uri.toString()).toEqual(NavigationActions.URI);
|
||||
expect(NavigationActions.uri({ uri: 'http://google.com' })).toEqual({
|
||||
type: NavigationActions.URI,
|
||||
uri: 'http://google.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,46 +1,55 @@
|
||||
import React from 'react';
|
||||
import 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import StackNavigator from '../navigators/createStackNavigator';
|
||||
|
||||
const FooScreen = () => <div />;
|
||||
const BarScreen = () => <div />;
|
||||
const BazScreen = () => <div />;
|
||||
const CarScreen = () => <div />;
|
||||
const DogScreen = () => <div />;
|
||||
const ElkScreen = () => <div />;
|
||||
const NavigationContainer = StackNavigator(
|
||||
{
|
||||
foo: {
|
||||
screen: FooScreen,
|
||||
},
|
||||
bar: {
|
||||
screen: BarScreen,
|
||||
},
|
||||
baz: {
|
||||
screen: BazScreen,
|
||||
},
|
||||
car: {
|
||||
screen: CarScreen,
|
||||
},
|
||||
dog: {
|
||||
screen: DogScreen,
|
||||
},
|
||||
elk: {
|
||||
screen: ElkScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'foo',
|
||||
}
|
||||
);
|
||||
|
||||
jest.useFakeTimers();
|
||||
// TODO: we should create a dummy navigator here
|
||||
import { createStackNavigator } from 'react-navigation-stack';
|
||||
import createNavigationContainer, {
|
||||
_TESTING_ONLY_reset_container_count,
|
||||
} from '../createNavigationContainer';
|
||||
|
||||
describe('NavigationContainer', () => {
|
||||
jest.useFakeTimers();
|
||||
beforeEach(() => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
});
|
||||
|
||||
const FooScreen = () => <div />;
|
||||
const BarScreen = () => <div />;
|
||||
const BazScreen = () => <div />;
|
||||
const CarScreen = () => <div />;
|
||||
const DogScreen = () => <div />;
|
||||
const ElkScreen = () => <div />;
|
||||
const Stack = createStackNavigator(
|
||||
{
|
||||
foo: {
|
||||
screen: FooScreen,
|
||||
},
|
||||
bar: {
|
||||
screen: BarScreen,
|
||||
},
|
||||
baz: {
|
||||
screen: BazScreen,
|
||||
},
|
||||
car: {
|
||||
screen: CarScreen,
|
||||
},
|
||||
dog: {
|
||||
screen: DogScreen,
|
||||
},
|
||||
elk: {
|
||||
screen: ElkScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'foo',
|
||||
}
|
||||
);
|
||||
const NavigationContainer = createNavigationContainer(Stack);
|
||||
|
||||
describe('state.nav', () => {
|
||||
it("should be preloaded with the router's initial state", () => {
|
||||
const navigationContainer = renderer
|
||||
@@ -198,4 +207,61 @@ describe('NavigationContainer', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('warnings', () => {
|
||||
function spyConsole() {
|
||||
let spy = {};
|
||||
|
||||
beforeEach(() => {
|
||||
spy.console = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spy.console.mockRestore();
|
||||
});
|
||||
|
||||
return spy;
|
||||
}
|
||||
|
||||
describe('detached navigators', () => {
|
||||
beforeEach(() => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
});
|
||||
|
||||
let spy = spyConsole();
|
||||
|
||||
it('warns when you render more than one container explicitly', () => {
|
||||
class BlankScreen extends React.Component {
|
||||
render() {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
class RootScreen extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<ChildNavigator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ChildNavigator = createNavigationContainer(
|
||||
createStackNavigator({
|
||||
Child: BlankScreen,
|
||||
})
|
||||
);
|
||||
|
||||
const RootStack = createNavigationContainer(
|
||||
createStackNavigator({
|
||||
Root: RootScreen,
|
||||
})
|
||||
);
|
||||
|
||||
renderer.create(<RootStack />).toJSON();
|
||||
expect(spy).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
||||
key: 'a',
|
||||
@@ -20,6 +21,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||
@@ -30,6 +32,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
||||
@@ -40,9 +43,11 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
};
|
||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
||||
@@ -54,6 +59,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() =>
|
||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
||||
@@ -65,10 +71,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||
});
|
||||
@@ -77,6 +85,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
@@ -86,10 +95,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||
@@ -99,6 +110,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
||||
});
|
||||
@@ -107,10 +119,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||
@@ -120,6 +134,7 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
||||
});
|
||||
@@ -128,10 +143,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
||||
@@ -141,10 +158,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
||||
@@ -155,10 +174,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
||||
@@ -169,24 +190,27 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Returns the state if index matches the route', () => {
|
||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
||||
).toEqual(state);
|
||||
).toEqual({ ...state, index: 1 });
|
||||
});
|
||||
|
||||
// Reset
|
||||
@@ -194,10 +218,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(state, [
|
||||
@@ -215,10 +241,12 @@ describe('StateUtils', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
|
||||
13
src/__tests__/__snapshots__/NavigationContainer-test.js.snap
Normal file
13
src/__tests__/__snapshots__/NavigationContainer-test.js.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NavigationContainer warnings detached navigators warns when you render more than one container explicitly 1`] = `
|
||||
Object {
|
||||
"console": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: https://reactnavigation.org/docs/common-mistakes.html#explicitly-rendering-more-than-one-navigator",
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
`;
|
||||
@@ -1,118 +0,0 @@
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import addNavigationHelpers from '../addNavigationHelpers';
|
||||
|
||||
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
||||
remove: () => {},
|
||||
});
|
||||
|
||||
describe('addNavigationHelpers', () => {
|
||||
it('handles Back action', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { key: 'A', routeName: 'Home' },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).goBack('A')
|
||||
).toEqual(true);
|
||||
expect(mockedDispatch).toBeCalledWith({
|
||||
type: NavigationActions.BACK,
|
||||
key: 'A',
|
||||
});
|
||||
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('handles Back action when the key is not defined', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { routeName: 'Home' },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).goBack()
|
||||
).toEqual(true);
|
||||
expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK });
|
||||
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('handles Navigate action', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { routeName: 'Home' },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).navigate('Profile', { name: 'Matt' })
|
||||
).toEqual(true);
|
||||
expect(mockedDispatch).toBeCalledWith({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
params: { name: 'Matt' },
|
||||
routeName: 'Profile',
|
||||
});
|
||||
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('handles SetParams action', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { key: 'B', routeName: 'Settings' },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).setParams({ notificationsEnabled: 'yes' })
|
||||
).toEqual(true);
|
||||
expect(mockedDispatch).toBeCalledWith({
|
||||
type: NavigationActions.SET_PARAMS,
|
||||
key: 'B',
|
||||
params: { notificationsEnabled: 'yes' },
|
||||
});
|
||||
expect(mockedDispatch.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('handles GetParams action', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { key: 'B', routeName: 'Settings', params: { name: 'Peter' } },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).getParam('name', 'Brent')
|
||||
).toEqual('Peter');
|
||||
});
|
||||
|
||||
it('handles GetParams action with default param', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { key: 'B', routeName: 'Settings' },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).getParam('name', 'Brent')
|
||||
).toEqual('Brent');
|
||||
});
|
||||
|
||||
it('handles GetParams action with param value as null', () => {
|
||||
const mockedDispatch = jest
|
||||
.fn(() => false)
|
||||
.mockImplementationOnce(() => true);
|
||||
expect(
|
||||
addNavigationHelpers({
|
||||
state: { key: 'B', routeName: 'Settings', params: { name: null } },
|
||||
dispatch: mockedDispatch,
|
||||
addListener: dummyEventSubscriber,
|
||||
}).getParam('name')
|
||||
).toEqual(null);
|
||||
});
|
||||
});
|
||||
102
src/__tests__/getNavigation-test.js
Normal file
102
src/__tests__/getNavigation-test.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import getNavigation from '../getNavigation';
|
||||
|
||||
test('getNavigation provides default action helpers', () => {
|
||||
const router = {
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
{},
|
||||
dispatch,
|
||||
new Set(),
|
||||
() => ({}),
|
||||
() => topNav
|
||||
);
|
||||
|
||||
topNav.navigate('GreatRoute');
|
||||
|
||||
expect(dispatch.mock.calls.length).toBe(1);
|
||||
expect(dispatch.mock.calls[0][0].type).toBe('Navigation/NAVIGATE');
|
||||
expect(dispatch.mock.calls[0][0].routeName).toBe('GreatRoute');
|
||||
});
|
||||
|
||||
test('getNavigation provides router action helpers', () => {
|
||||
const router = {
|
||||
getActionCreators: () => ({
|
||||
foo: bar => ({ type: 'FooBarAction', bar }),
|
||||
}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
{},
|
||||
dispatch,
|
||||
new Set(),
|
||||
() => ({}),
|
||||
() => topNav
|
||||
);
|
||||
|
||||
topNav.foo('Great');
|
||||
|
||||
expect(dispatch.mock.calls.length).toBe(1);
|
||||
expect(dispatch.mock.calls[0][0].type).toBe('FooBarAction');
|
||||
expect(dispatch.mock.calls[0][0].bar).toBe('Great');
|
||||
});
|
||||
|
||||
test('getNavigation get child navigation with router', () => {
|
||||
const actionSubscribers = new Set();
|
||||
let navigation = null;
|
||||
|
||||
const routerA = {
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
const router = {
|
||||
childRouters: {
|
||||
RouteA: routerA,
|
||||
},
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const initState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
key: 'a',
|
||||
routeName: 'RouteA',
|
||||
routes: [{ key: 'c', routeName: 'RouteC' }],
|
||||
index: 0,
|
||||
},
|
||||
{ key: 'b', routeName: 'RouteB' },
|
||||
],
|
||||
};
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
initState,
|
||||
() => {},
|
||||
actionSubscribers,
|
||||
() => ({}),
|
||||
() => navigation
|
||||
);
|
||||
|
||||
const childNavA = topNav.getChildNavigation('a');
|
||||
|
||||
expect(childNavA.router).toBe(routerA);
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
/* Helpers for navigation */
|
||||
|
||||
import NavigationActions from './NavigationActions';
|
||||
import invariant from './utils/invariant';
|
||||
|
||||
export default function(navigation) {
|
||||
return {
|
||||
...navigation,
|
||||
goBack: key => {
|
||||
let actualizedKey = key;
|
||||
if (key === undefined && navigation.state.key) {
|
||||
invariant(
|
||||
typeof navigation.state.key === 'string',
|
||||
'key should be a string'
|
||||
);
|
||||
actualizedKey = navigation.state.key;
|
||||
}
|
||||
return navigation.dispatch(
|
||||
NavigationActions.back({ key: actualizedKey })
|
||||
);
|
||||
},
|
||||
navigate: (navigateTo, params, action) => {
|
||||
if (typeof navigateTo === 'string') {
|
||||
return navigation.dispatch(
|
||||
NavigationActions.navigate({ routeName: navigateTo, params, action })
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
typeof navigateTo === 'object',
|
||||
'Must navigateTo an object or a string'
|
||||
);
|
||||
invariant(
|
||||
params == null,
|
||||
'Params must not be provided to .navigate() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
action == null,
|
||||
'Child action must not be provided to .navigate() when specifying an object'
|
||||
);
|
||||
return navigation.dispatch(NavigationActions.navigate(navigateTo));
|
||||
},
|
||||
pop: (n, params) =>
|
||||
navigation.dispatch(
|
||||
NavigationActions.pop({ n, immediate: params && params.immediate })
|
||||
),
|
||||
popToTop: params =>
|
||||
navigation.dispatch(
|
||||
NavigationActions.popToTop({ immediate: params && params.immediate })
|
||||
),
|
||||
/**
|
||||
* For updating current route params. For example the nav bar title and
|
||||
* buttons are based on the route params.
|
||||
* This means `setParams` can be used to update nav bar for example.
|
||||
*/
|
||||
setParams: params => {
|
||||
invariant(
|
||||
navigation.state.key && typeof navigation.state.key === 'string',
|
||||
'setParams cannot be called by root navigator'
|
||||
);
|
||||
const key = navigation.state.key;
|
||||
return navigation.dispatch(NavigationActions.setParams({ params, key }));
|
||||
},
|
||||
|
||||
getParam: (paramName, defaultValue) => {
|
||||
const params = navigation.state.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
push: (routeName, params, action) =>
|
||||
navigation.dispatch(
|
||||
NavigationActions.push({ routeName, params, action })
|
||||
),
|
||||
|
||||
replace: (routeName, params, action) =>
|
||||
navigation.dispatch(
|
||||
NavigationActions.replace({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
key: navigation.state.key,
|
||||
})
|
||||
),
|
||||
|
||||
openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
|
||||
closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
|
||||
toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import { BackHandler } from './PlatformHelpers';
|
||||
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
|
||||
import NavigationActions from './NavigationActions';
|
||||
import addNavigationHelpers from './addNavigationHelpers';
|
||||
import getNavigation from './getNavigation';
|
||||
import invariant from './utils/invariant';
|
||||
import docsUrl from './utils/docsUrl';
|
||||
import { urlToPathAndParams } from './routers/pathUtils';
|
||||
|
||||
function isStateful(props) {
|
||||
return !props.navigation;
|
||||
}
|
||||
|
||||
function validateProps(props) {
|
||||
if (isStateful(props)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { navigation, screenProps, ...containerProps } = props;
|
||||
|
||||
const keys = Object.keys(containerProps);
|
||||
|
||||
if (keys.length !== 0) {
|
||||
throw new Error(
|
||||
'This navigator has both navigation and container props, so it is ' +
|
||||
`unclear if it should own its own state. Remove props: "${keys.join(
|
||||
', '
|
||||
)}" ` +
|
||||
'if the navigator should get its state from the navigation prop. If the ' +
|
||||
'navigator should maintain its own state, do not pass a navigation prop.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Track the number of stateful container instances. Warn if >0 and not using the
|
||||
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
|
||||
// stateful navigation containers in a future release and require a provider style pattern
|
||||
// instead in order to eliminate confusion entirely.
|
||||
let _statefulContainerCount = 0;
|
||||
export function _TESTING_ONLY_reset_container_count() {
|
||||
_statefulContainerCount = 0;
|
||||
}
|
||||
|
||||
// We keep a global flag to catch errors during the state persistence hydrating scenario.
|
||||
// The innermost navigator who catches the error will dispatch a new init action.
|
||||
let _reactNavigationIsHydratingState = false;
|
||||
// Unfortunate to use global state here, but it seems necessesary for the time
|
||||
// being. There seems to be some problems with cascading componentDidCatch
|
||||
// handlers. Ideally the inner non-stateful navigator catches the error and
|
||||
// re-throws it, to be caught by the top-level stateful navigator.
|
||||
|
||||
/**
|
||||
* Create an HOC that injects the navigation and manages the navigation state
|
||||
@@ -18,12 +63,17 @@ export default function createNavigationContainer(Component) {
|
||||
static router = Component.router;
|
||||
static navigationOptions = null;
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
validateProps(nextProps);
|
||||
return null;
|
||||
}
|
||||
|
||||
_actionEventSubscribers = new Set();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._validateProps(props);
|
||||
validateProps(props);
|
||||
|
||||
this._initialAction = NavigationActions.init();
|
||||
|
||||
@@ -41,14 +91,21 @@ export default function createNavigationContainer(Component) {
|
||||
}
|
||||
|
||||
this.state = {
|
||||
nav: this._isStateful()
|
||||
? Component.router.getStateForAction(this._initialAction)
|
||||
: null,
|
||||
nav:
|
||||
this._isStateful() && !props.persistenceKey
|
||||
? Component.router.getStateForAction(this._initialAction)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
_renderLoading() {
|
||||
return this.props.renderLoadingExperimental
|
||||
? this.props.renderLoadingExperimental()
|
||||
: null;
|
||||
}
|
||||
|
||||
_isStateful() {
|
||||
return !this.props.navigation;
|
||||
return isStateful(this.props);
|
||||
}
|
||||
|
||||
_validateProps(props) {
|
||||
@@ -72,23 +129,12 @@ 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 }) => {
|
||||
const parsedUrl = this._urlToPathAndParams(url);
|
||||
const { enableURLHandling, uriPrefix } = this.props;
|
||||
if (enableURLHandling === false) {
|
||||
return;
|
||||
}
|
||||
const parsedUrl = urlToPathAndParams(url, uriPrefix);
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const action = Component.router.getActionForPathAndParams(path, params);
|
||||
@@ -127,98 +173,202 @@ export default function createNavigationContainer(Component) {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this._validateProps(nextProps);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Clear cached _nav every tick
|
||||
if (this._nav === this.state.nav) {
|
||||
this._nav = null;
|
||||
// Clear cached _navState every tick
|
||||
if (this._navState === this.state.nav) {
|
||||
this._navState = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
this._isMounted = true;
|
||||
if (!this._isStateful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__ && !this.props.detached) {
|
||||
if (_statefulContainerCount > 0) {
|
||||
// Temporarily only show this on iOS due to this issue:
|
||||
// https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
|
||||
if (Platform.OS === 'ios') {
|
||||
console.warn(
|
||||
`You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
|
||||
'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_statefulContainerCount++;
|
||||
Linking.addEventListener('url', this._handleOpenURL);
|
||||
|
||||
Linking.getInitialURL().then(url => url && this._handleOpenURL({ url }));
|
||||
// Pull out anything that can impact state
|
||||
const { persistenceKey, uriPrefix, enableURLHandling } = this.props;
|
||||
let parsedUrl = null;
|
||||
let startupStateJSON = null;
|
||||
if (enableURLHandling !== false) {
|
||||
startupStateJSON =
|
||||
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
||||
const url = await Linking.getInitialURL();
|
||||
parsedUrl = url && urlToPathAndParams(url, uriPrefix);
|
||||
}
|
||||
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action: this._initialAction,
|
||||
state: this.state.nav,
|
||||
lastState: null,
|
||||
})
|
||||
);
|
||||
// Initialize state. This must be done *after* any async code
|
||||
// so we don't end up with a different value for this.state.nav
|
||||
// due to changes while async function was resolving
|
||||
let action = this._initialAction;
|
||||
let startupState = this.state.nav;
|
||||
if (!startupState) {
|
||||
!!process.env.REACT_NAV_LOGGING &&
|
||||
console.log('Init new Navigation State');
|
||||
startupState = Component.router.getStateForAction(action);
|
||||
}
|
||||
|
||||
// Pull persisted state from AsyncStorage
|
||||
if (startupStateJSON) {
|
||||
try {
|
||||
startupState = JSON.parse(startupStateJSON);
|
||||
_reactNavigationIsHydratingState = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Pull state out of URL
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const urlAction = Component.router.getActionForPathAndParams(
|
||||
path,
|
||||
params
|
||||
);
|
||||
if (urlAction) {
|
||||
!!process.env.REACT_NAV_LOGGING &&
|
||||
console.log('Applying Navigation Action for Initial URL:', url);
|
||||
action = urlAction;
|
||||
startupState = Component.router.getStateForAction(
|
||||
urlAction,
|
||||
startupState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dispatchActions = () =>
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
state: this.state.nav,
|
||||
lastState: null,
|
||||
})
|
||||
);
|
||||
|
||||
if (startupState === this.state.nav) {
|
||||
dispatchActions();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ nav: startupState }, () => {
|
||||
_reactNavigationIsHydratingState = false;
|
||||
dispatchActions();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidCatch(e, errorInfo) {
|
||||
if (_reactNavigationIsHydratingState) {
|
||||
_reactNavigationIsHydratingState = false;
|
||||
console.warn(
|
||||
'Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state..'
|
||||
);
|
||||
this.dispatch(NavigationActions.init());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
_persistNavigationState = async nav => {
|
||||
const { persistenceKey } = this.props;
|
||||
if (!persistenceKey) {
|
||||
return;
|
||||
}
|
||||
await AsyncStorage.setItem(persistenceKey, JSON.stringify(nav));
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
Linking.removeEventListener('url', this._handleOpenURL);
|
||||
this.subs && this.subs.remove();
|
||||
|
||||
if (this._isStateful()) {
|
||||
_statefulContainerCount--;
|
||||
}
|
||||
}
|
||||
|
||||
// Per-tick temporary storage for state.nav
|
||||
|
||||
dispatch = action => {
|
||||
if (!this._isStateful()) {
|
||||
return false;
|
||||
if (this.props.navigation) {
|
||||
return this.props.navigation.dispatch(action);
|
||||
}
|
||||
this._nav = this._nav || this.state.nav;
|
||||
const oldNav = this._nav;
|
||||
invariant(oldNav, 'should be set in constructor if stateful');
|
||||
const nav = Component.router.getStateForAction(action, oldNav);
|
||||
|
||||
// navState will have the most up-to-date value, because setState sometimes behaves asyncronously
|
||||
this._navState = this._navState || this.state.nav;
|
||||
const lastNavState = this._navState;
|
||||
invariant(lastNavState, 'should be set in constructor if stateful');
|
||||
const reducedState = Component.router.getStateForAction(
|
||||
action,
|
||||
lastNavState
|
||||
);
|
||||
const navState = reducedState === null ? lastNavState : reducedState;
|
||||
|
||||
const dispatchActionEvents = () => {
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
state: nav,
|
||||
lastState: oldNav,
|
||||
state: navState,
|
||||
lastState: lastNavState,
|
||||
})
|
||||
);
|
||||
};
|
||||
if (nav && nav !== oldNav) {
|
||||
|
||||
if (reducedState === null) {
|
||||
// The router will return null when action has been handled and the state hasn't changed.
|
||||
// dispatch returns true when something has been handled.
|
||||
dispatchActionEvents();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (navState !== lastNavState) {
|
||||
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
|
||||
this._nav = nav;
|
||||
this.setState({ nav }, () => {
|
||||
this._onNavigationStateChange(oldNav, nav, action);
|
||||
this._navState = navState;
|
||||
this.setState({ nav: navState }, () => {
|
||||
this._onNavigationStateChange(lastNavState, navState, action);
|
||||
dispatchActionEvents();
|
||||
this._persistNavigationState(navState);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
dispatchActionEvents();
|
||||
}
|
||||
|
||||
dispatchActionEvents();
|
||||
return false;
|
||||
};
|
||||
|
||||
_getScreenProps = () => this.props.screenProps;
|
||||
|
||||
render() {
|
||||
let navigation = this.props.navigation;
|
||||
if (this._isStateful()) {
|
||||
const nav = this.state.nav;
|
||||
invariant(nav, 'should be set in constructor if stateful');
|
||||
if (!this._navigation || this._navigation.state !== nav) {
|
||||
this._navigation = addNavigationHelpers({
|
||||
dispatch: this.dispatch,
|
||||
state: nav,
|
||||
addListener: (eventName, handler) => {
|
||||
if (eventName !== 'action') {
|
||||
return { remove: () => {} };
|
||||
}
|
||||
this._actionEventSubscribers.add(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
this._actionEventSubscribers.delete(handler);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
const navState = this.state.nav;
|
||||
if (!navState) {
|
||||
return this._renderLoading();
|
||||
}
|
||||
if (!this._navigation || this._navigation.state !== navState) {
|
||||
this._navigation = getNavigation(
|
||||
Component.router,
|
||||
navState,
|
||||
this.dispatch,
|
||||
this._actionEventSubscribers,
|
||||
this._getScreenProps,
|
||||
() => this._navigation
|
||||
);
|
||||
}
|
||||
navigation = this._navigation;
|
||||
}
|
||||
@@ -227,5 +377,5 @@ export default function createNavigationContainer(Component) {
|
||||
}
|
||||
}
|
||||
|
||||
return NavigationContainer;
|
||||
return polyfill(NavigationContainer);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
action,
|
||||
type: eventName,
|
||||
};
|
||||
const isTransitioning = !!state && !!state.transitioningFromKey;
|
||||
const isTransitioning = !!state && state.isTransitioning;
|
||||
|
||||
const previouslyLastEmittedEvent = lastEmittedEvent;
|
||||
|
||||
@@ -138,11 +138,14 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEmittedEvent === 'didBlur' && !newRoute) {
|
||||
removeAll();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
removeAll,
|
||||
addListener(eventName, eventHandler) {
|
||||
const subscribers = getChildSubscribers(eventName);
|
||||
if (!subscribers) {
|
||||
|
||||
113
src/getChildNavigation.js
Normal file
113
src/getChildNavigation.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import getChildEventSubscriber from './getChildEventSubscriber';
|
||||
import getChildRouter from './getChildRouter';
|
||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
||||
import invariant from './utils/invariant';
|
||||
|
||||
const createParamGetter = route => (paramName, defaultValue) => {
|
||||
const params = route.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
const children =
|
||||
navigation._childrenNavigation || (navigation._childrenNavigation = {});
|
||||
|
||||
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
||||
|
||||
if (!childRoute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (children[childKey] && children[childKey].state === childRoute) {
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
const childRouter = getChildRouter(navigation.router, childRoute.routeName);
|
||||
|
||||
// If the route has children, we'll use this to pass in to the action creators
|
||||
// for the childRouter so that any action that depends on the active route will
|
||||
// behave as expected. We don't explicitly require that routers implement routes
|
||||
// and index properties, but if we did then we would put an invariant here to
|
||||
// ensure that a focusedGrandChildRoute exists if childRouter is defined.
|
||||
const focusedGrandChildRoute =
|
||||
childRoute.routes && typeof childRoute.index === 'number'
|
||||
? childRoute.routes[childRoute.index]
|
||||
: null;
|
||||
|
||||
const actionCreators = {
|
||||
...navigation.actions,
|
||||
...navigation.router.getActionCreators(childRoute, navigation.state.key),
|
||||
...(childRouter
|
||||
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
|
||||
: {}),
|
||||
...getNavigationActionCreators(childRoute),
|
||||
};
|
||||
|
||||
const actionHelpers = {};
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
actionHelpers[actionName] = (...args) => {
|
||||
const actionCreator = actionCreators[actionName];
|
||||
const action = actionCreator(...args);
|
||||
return navigation.dispatch(action);
|
||||
};
|
||||
});
|
||||
|
||||
if (children[childKey]) {
|
||||
children[childKey] = {
|
||||
...children[childKey],
|
||||
...actionHelpers,
|
||||
state: childRoute,
|
||||
router: childRouter,
|
||||
actions: actionCreators,
|
||||
getParam: createParamGetter(childRoute),
|
||||
};
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
const childSubscriber = getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
childKey
|
||||
);
|
||||
|
||||
children[childKey] = {
|
||||
...actionHelpers,
|
||||
|
||||
state: childRoute,
|
||||
router: childRouter,
|
||||
actions: actionCreators,
|
||||
getParam: createParamGetter(childRoute),
|
||||
|
||||
getChildNavigation: grandChildKey =>
|
||||
getChildNavigation(children[childKey], grandChildKey, () => {
|
||||
const nav = getCurrentParentNavigation();
|
||||
return nav && nav.getChildNavigation(childKey);
|
||||
}),
|
||||
|
||||
isFocused: () => {
|
||||
const currentNavigation = getCurrentParentNavigation();
|
||||
if (!currentNavigation) {
|
||||
return false;
|
||||
}
|
||||
const { routes, index } = currentNavigation.state;
|
||||
if (!currentNavigation.isFocused()) {
|
||||
return false;
|
||||
}
|
||||
if (routes[index].key === childKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
dispatch: navigation.dispatch,
|
||||
getScreenProps: navigation.getScreenProps,
|
||||
dangerouslyGetParent: getCurrentParentNavigation,
|
||||
addListener: childSubscriber.addListener,
|
||||
};
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
export default getChildNavigation;
|
||||
9
src/getChildRouter.js
Normal file
9
src/getChildRouter.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function getChildRouter(router, routeName) {
|
||||
if (router.childRouters && router.childRouters[routeName]) {
|
||||
return router.childRouters[routeName];
|
||||
}
|
||||
|
||||
const Component = router.getComponentForRouteName(routeName);
|
||||
|
||||
return Component.router;
|
||||
}
|
||||
54
src/getNavigation.js
Normal file
54
src/getNavigation.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
||||
import getChildNavigation from './getChildNavigation';
|
||||
|
||||
export default function getNavigation(
|
||||
router,
|
||||
state,
|
||||
dispatch,
|
||||
actionSubscribers,
|
||||
getScreenProps,
|
||||
getCurrentNavigation
|
||||
) {
|
||||
const actions = router.getActionCreators(state, null);
|
||||
|
||||
const navigation = {
|
||||
actions,
|
||||
router,
|
||||
state,
|
||||
dispatch,
|
||||
getScreenProps,
|
||||
getChildNavigation: childKey =>
|
||||
getChildNavigation(navigation, childKey, getCurrentNavigation),
|
||||
isFocused: childKey => {
|
||||
const { routes, index } = getCurrentNavigation().state;
|
||||
if (childKey == null || routes[index].key === childKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
addListener: (eventName, handler) => {
|
||||
if (eventName !== 'action') {
|
||||
return { remove: () => {} };
|
||||
}
|
||||
actionSubscribers.add(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
actionSubscribers.delete(handler);
|
||||
},
|
||||
};
|
||||
},
|
||||
dangerouslyGetParent: () => null,
|
||||
};
|
||||
|
||||
const actionCreators = {
|
||||
...getNavigationActionCreators(navigation.state),
|
||||
...actions,
|
||||
};
|
||||
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
navigation[actionName] = (...args) =>
|
||||
navigation.dispatch(actionCreators[actionName](...args));
|
||||
});
|
||||
|
||||
return navigation;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, Platform, ScrollView } from 'react-native';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import createNavigator from './createNavigator';
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import DrawerRouter from '../routers/DrawerRouter';
|
||||
import DrawerScreen from '../views/Drawer/DrawerScreen';
|
||||
import DrawerView from '../views/Drawer/DrawerView';
|
||||
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
||||
|
||||
// A stack navigators props are the intersection between
|
||||
// the base navigator props (navgiation, screenProps, etc)
|
||||
// and the view's props
|
||||
|
||||
const defaultContentComponent = props => (
|
||||
<ScrollView alwaysBounceVertical={false}>
|
||||
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
|
||||
<DrawerItems {...props} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
const DefaultDrawerConfig = {
|
||||
drawerWidth: () => {
|
||||
/*
|
||||
* Default drawer width is screen width - header height
|
||||
* with a max width of 280 on mobile and 320 on tablet
|
||||
* https://material.io/guidelines/patterns/navigation-drawer.html
|
||||
*/
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const smallerAxisSize = Math.min(height, width);
|
||||
const isLandscape = width > height;
|
||||
const isTablet = smallerAxisSize >= 600;
|
||||
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
|
||||
const maxWidth = isTablet ? 320 : 280;
|
||||
|
||||
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
|
||||
},
|
||||
contentComponent: defaultContentComponent,
|
||||
drawerPosition: 'left',
|
||||
drawerBackgroundColor: 'white',
|
||||
useNativeAnimations: true,
|
||||
};
|
||||
|
||||
const DrawerNavigator = (routeConfigs, config = {}) => {
|
||||
const mergedConfig = { ...DefaultDrawerConfig, ...config };
|
||||
|
||||
const {
|
||||
order,
|
||||
paths,
|
||||
initialRouteName,
|
||||
backBehavior,
|
||||
...drawerConfig
|
||||
} = mergedConfig;
|
||||
|
||||
const routerConfig = {
|
||||
order,
|
||||
paths,
|
||||
initialRouteName,
|
||||
backBehavior,
|
||||
};
|
||||
|
||||
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
|
||||
|
||||
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
|
||||
|
||||
return createNavigationContainer(navigator);
|
||||
};
|
||||
|
||||
export default DrawerNavigator;
|
||||
@@ -1,33 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import DrawerNavigator from '../DrawerNavigator';
|
||||
|
||||
class HomeScreen extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: `Welcome ${
|
||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
||||
}`,
|
||||
gesturesEnabled: true,
|
||||
});
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const routeConfig = {
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
},
|
||||
};
|
||||
|
||||
describe('DrawerNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MyDrawerNavigator = DrawerNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MyDrawerNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import StackNavigator from '../createStackNavigator';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
class HomeScreen extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: `Welcome ${
|
||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
||||
}`,
|
||||
gesturesEnabled: true,
|
||||
headerStyle: [{ backgroundColor: 'red' }, styles.header],
|
||||
});
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const routeConfig = {
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
},
|
||||
};
|
||||
|
||||
describe('StackNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MyStackNavigator = StackNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('applies correct values when headerRight is present', () => {
|
||||
const MyStackNavigator = StackNavigator({
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
navigationOptions: {
|
||||
headerRight: <View />,
|
||||
},
|
||||
},
|
||||
});
|
||||
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
18
src/navigators/__tests__/SwitchNavigator-test.js
Normal file
18
src/navigators/__tests__/SwitchNavigator-test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import SwitchNavigator from '../createContainedSwitchNavigator';
|
||||
|
||||
const A = () => <View />;
|
||||
const B = () => <View />;
|
||||
const routeConfig = { A, B };
|
||||
|
||||
describe('SwitchNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MySwitchNavigator = SwitchNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MySwitchNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,9 @@ import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import TabNavigator from '../createTabNavigator';
|
||||
const {
|
||||
createTabNavigator,
|
||||
} = require('react-navigation-deprecated-tab-navigator');
|
||||
|
||||
class HomeScreen extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
@@ -25,7 +27,7 @@ const routeConfig = {
|
||||
|
||||
describe('TabNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MyTabNavigator = TabNavigator(routeConfig);
|
||||
const MyTabNavigator = createTabNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MyTabNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DrawerNavigator renders successfully 1`] = `
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"zIndex": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
accessibilityComponentType={undefined}
|
||||
accessibilityLabel={undefined}
|
||||
accessibilityTraits={undefined}
|
||||
accessible={true}
|
||||
collapsable={undefined}
|
||||
hitSlop={undefined}
|
||||
nativeID={undefined}
|
||||
onLayout={undefined}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
pointerEvents="none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"zIndex": 1000,
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
/>
|
||||
<View
|
||||
accessibilityViewIsModal={false}
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "white",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": null,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": -320,
|
||||
},
|
||||
],
|
||||
"width": 320,
|
||||
"zIndex": 1001,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<RCTScrollView
|
||||
DEPRECATED_sendUpdatedChildFrames={false}
|
||||
alwaysBounceHorizontal={undefined}
|
||||
alwaysBounceVertical={false}
|
||||
onContentSizeChange={null}
|
||||
onMomentumScrollBegin={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={undefined}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
onScrollShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
onTouchCancel={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchMove={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
scrollEventThrottle={undefined}
|
||||
sendMomentumEvents={false}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"flexGrow": 1,
|
||||
"flexShrink": 1,
|
||||
"overflow": "scroll",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<RCTScrollContentView
|
||||
collapsable={false}
|
||||
removeClippedSubviews={undefined}
|
||||
style={
|
||||
Array [
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"paddingVertical": 4,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessibilityComponentType={undefined}
|
||||
accessibilityLabel={undefined}
|
||||
accessibilityTraits={undefined}
|
||||
accessible={true}
|
||||
collapsable={undefined}
|
||||
hasTVPreferredFocus={undefined}
|
||||
hitSlop={undefined}
|
||||
isTVSelectable={true}
|
||||
nativeID={undefined}
|
||||
onLayout={undefined}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
tvParallaxProperties={undefined}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0, 0, 0, .04)",
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontWeight": "bold",
|
||||
"margin": 16,
|
||||
},
|
||||
Object {
|
||||
"color": "#2196f3",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollContentView>
|
||||
</RCTScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -1,355 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StackNavigator applies correct values when headerRight is present 1`] = `
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 70,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 70,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`StackNavigator renders successfully 1`] = `
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwitchNavigator renders successfully 1`] = `<View />`;
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
exports[`TabNavigator renders successfully 1`] = `
|
||||
<View
|
||||
loaded={
|
||||
Array [
|
||||
0,
|
||||
]
|
||||
}
|
||||
onLayout={[Function]}
|
||||
collapsable={false}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
@@ -21,56 +16,65 @@ exports[`TabNavigator renders successfully 1`] = `
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "stretch",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"alignItems": "stretch",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
collapsable={false}
|
||||
removeClippedSubviews={false}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"overflow": "hidden",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
collapsable={false}
|
||||
removeClippedSubviews={false}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -137,9 +141,15 @@ exports[`TabNavigator renders successfully 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"height": 29,
|
||||
},
|
||||
false,
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -150,6 +160,7 @@ exports[`TabNavigator renders successfully 1`] = `
|
||||
"alignSelf": "center",
|
||||
"height": "100%",
|
||||
"justifyContent": "center",
|
||||
"minWidth": 30,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
@@ -164,6 +175,7 @@ exports[`TabNavigator renders successfully 1`] = `
|
||||
"alignSelf": "center",
|
||||
"height": "100%",
|
||||
"justifyContent": "center",
|
||||
"minWidth": 30,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
|
||||
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;
|
||||
57
src/navigators/createKeyboardAwareNavigator.js
Normal file
57
src/navigators/createKeyboardAwareNavigator.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
|
||||
export default (Navigator, navigatorConfig) =>
|
||||
class KeyboardAwareNavigator extends React.Component {
|
||||
static router = Navigator.router;
|
||||
_previouslyFocusedTextInput = null;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Navigator
|
||||
{...this.props}
|
||||
onGestureBegin={this._handleGestureBegin}
|
||||
onGestureCanceled={this._handleGestureCanceled}
|
||||
onGestureFinish={this._handleGestureFinish}
|
||||
onTransitionStart={this._handleTransitionStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_handleGestureBegin = () => {
|
||||
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
|
||||
if (this._previouslyFocusedTextInput) {
|
||||
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
|
||||
}
|
||||
this.props.onGestureBegin && this.props.onGestureBegin();
|
||||
};
|
||||
|
||||
_handleGestureCanceled = () => {
|
||||
if (this._previouslyFocusedTextInput) {
|
||||
TextInput.State.focusTextInput(this._previouslyFocusedTextInput);
|
||||
}
|
||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
||||
};
|
||||
|
||||
_handleGestureFinish = () => {
|
||||
this._previouslyFocusedTextInput = null;
|
||||
this.props.onGestureFinish && this.props.onGestureFinish();
|
||||
};
|
||||
|
||||
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
|
||||
// TODO: We should not even have received the transition start event
|
||||
// in the case where the index did not change, I believe. We
|
||||
// should revisit this after 2.0 release.
|
||||
if (transitionProps.index !== prevTransitionProps.index) {
|
||||
const currentField = TextInput.State.currentlyFocusedField();
|
||||
if (currentField) {
|
||||
TextInput.State.blurTextInput(currentField);
|
||||
}
|
||||
}
|
||||
|
||||
const onTransitionStart =
|
||||
this.props.onTransitionStart || navigatorConfig.onTransitionStart;
|
||||
onTransitionStart &&
|
||||
onTransitionStart(transitionProps, prevTransitionProps);
|
||||
};
|
||||
};
|
||||
@@ -1,60 +1,46 @@
|
||||
import React from 'react';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
|
||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||
import addNavigationHelpers from '../addNavigationHelpers';
|
||||
|
||||
function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
class Navigator extends React.Component {
|
||||
static router = router;
|
||||
static navigationOptions = null;
|
||||
|
||||
childEventSubscribers = {};
|
||||
|
||||
// Cleanup subscriptions for routes that no longer exist
|
||||
componentDidUpdate() {
|
||||
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
|
||||
Object.keys(this.childEventSubscribers).forEach(key => {
|
||||
if (!activeKeys.includes(key)) {
|
||||
this.childEventSubscribers[key].removeAll();
|
||||
delete this.childEventSubscribers[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all subscriptions
|
||||
componentWillUnmount() {
|
||||
Object.values(this.childEventSubscribers).map(s => s.removeAll());
|
||||
}
|
||||
|
||||
_isRouteFocused = route => () => {
|
||||
const { state } = this.props.navigation;
|
||||
const focusedRoute = state.routes[state.index];
|
||||
return route === focusedRoute;
|
||||
state = {
|
||||
descriptors: {},
|
||||
screenProps: this.props.screenProps,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation, screenProps } = this.props;
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const prevDescriptors = prevState.descriptors;
|
||||
const { navigation, screenProps } = nextProps;
|
||||
const { dispatch, state, addListener } = navigation;
|
||||
const { routes } = state;
|
||||
if (typeof routes === 'undefined') {
|
||||
throw new TypeError(
|
||||
'No "routes" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop'
|
||||
);
|
||||
}
|
||||
|
||||
const descriptors = {};
|
||||
|
||||
routes.forEach(route => {
|
||||
const getComponent = () =>
|
||||
router.getComponentForRouteName(route.routeName);
|
||||
|
||||
if (!this.childEventSubscribers[route.key]) {
|
||||
this.childEventSubscribers[route.key] = getChildEventSubscriber(
|
||||
addListener,
|
||||
route.key
|
||||
);
|
||||
if (
|
||||
prevDescriptors &&
|
||||
prevDescriptors[route.key] &&
|
||||
route === prevDescriptors[route.key].state &&
|
||||
screenProps === prevState.screenProps
|
||||
) {
|
||||
descriptors[route.key] = prevDescriptors[route.key];
|
||||
return;
|
||||
}
|
||||
|
||||
const childNavigation = addNavigationHelpers({
|
||||
dispatch,
|
||||
state: route,
|
||||
addListener: this.childEventSubscribers[route.key].addListener,
|
||||
isFocused: this._isRouteFocused.bind(this, route),
|
||||
});
|
||||
const getComponent = router.getComponentForRouteName.bind(
|
||||
null,
|
||||
route.routeName
|
||||
);
|
||||
const childNavigation = navigation.getChildNavigation(route.key);
|
||||
const options = router.getScreenOptions(childNavigation, screenProps);
|
||||
descriptors[route.key] = {
|
||||
key: route.key,
|
||||
@@ -65,17 +51,23 @@ function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
};
|
||||
});
|
||||
|
||||
return { descriptors, screenProps };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigatorView
|
||||
screenProps={screenProps}
|
||||
navigation={navigation}
|
||||
{...this.props}
|
||||
screenProps={this.state.screenProps}
|
||||
navigation={this.props.navigation}
|
||||
navigationConfig={navigationConfig}
|
||||
descriptors={descriptors}
|
||||
descriptors={this.state.descriptors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return Navigator;
|
||||
|
||||
return polyfill(Navigator);
|
||||
}
|
||||
|
||||
export default createNavigator;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import createNavigator from './createNavigator';
|
||||
import StackView from '../views/StackView/StackView2';
|
||||
import StackRouter from '../routers/StackRouter';
|
||||
|
||||
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
||||
const {
|
||||
initialRouteName,
|
||||
initialRouteParams,
|
||||
paths,
|
||||
navigationOptions,
|
||||
} = stackConfig;
|
||||
|
||||
const stackRouterConfig = {
|
||||
initialRouteName,
|
||||
initialRouteParams,
|
||||
paths,
|
||||
navigationOptions,
|
||||
};
|
||||
|
||||
const router = StackRouter(routeConfigMap, stackRouterConfig);
|
||||
|
||||
// Create a navigator with StackView as the view
|
||||
const Navigator = createNavigator(StackView, router, stackConfig);
|
||||
|
||||
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
|
||||
return createNavigationContainer(Navigator);
|
||||
}
|
||||
|
||||
export default createStackNavigator;
|
||||
12
src/navigators/createSwitchNavigator.js
Normal file
12
src/navigators/createSwitchNavigator.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import createNavigator from '../navigators/createNavigator';
|
||||
import SwitchRouter from '../routers/SwitchRouter';
|
||||
import SwitchView from '../views/SwitchView/SwitchView';
|
||||
|
||||
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
||||
const router = SwitchRouter(routeConfigMap, switchConfig);
|
||||
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
||||
return Navigator;
|
||||
}
|
||||
|
||||
export default createSwitchNavigator;
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import createNavigator from './createNavigator';
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import TabRouter from '../routers/TabRouter';
|
||||
import TabView from '../views/TabView/TabView';
|
||||
import TabBarTop from '../views/TabView/TabBarTop';
|
||||
import TabBarBottom from '../views/TabView/TabBarBottom';
|
||||
|
||||
const TabNavigator = (routeConfigs, config = {}) => {
|
||||
// Use the look native to the platform by default
|
||||
const tabsConfig = { ...TabNavigator.Presets.Default, ...config };
|
||||
|
||||
const router = TabRouter(routeConfigs, tabsConfig);
|
||||
|
||||
const navigator = createNavigator(TabView, router, tabsConfig);
|
||||
|
||||
return createNavigationContainer(navigator);
|
||||
};
|
||||
|
||||
const Presets = {
|
||||
iOSBottomTabs: {
|
||||
tabBarComponent: TabBarBottom,
|
||||
tabBarPosition: 'bottom',
|
||||
swipeEnabled: false,
|
||||
animationEnabled: false,
|
||||
initialLayout: undefined,
|
||||
},
|
||||
AndroidTopTabs: {
|
||||
tabBarComponent: TabBarTop,
|
||||
tabBarPosition: 'top',
|
||||
swipeEnabled: true,
|
||||
animationEnabled: true,
|
||||
initialLayout: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use these to get Android-style top tabs even on iOS or vice versa.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* const HomeScreenTabNavigator = TabNavigator({
|
||||
* Chat: {
|
||||
* screen: ChatScreen,
|
||||
* },
|
||||
* ...
|
||||
* }, {
|
||||
* ...TabNavigator.Presets.AndroidTopTabs,
|
||||
* tabBarOptions: {
|
||||
* ...
|
||||
* },
|
||||
* });
|
||||
*```
|
||||
*/
|
||||
TabNavigator.Presets = {
|
||||
iOSBottomTabs: Presets.iOSBottomTabs,
|
||||
AndroidTopTabs: Presets.AndroidTopTabs,
|
||||
Default:
|
||||
Platform.OS === 'ios' ? Presets.iOSBottomTabs : Presets.AndroidTopTabs,
|
||||
};
|
||||
|
||||
export default TabNavigator;
|
||||
152
src/react-navigation.js
vendored
152
src/react-navigation.js
vendored
@@ -8,25 +8,80 @@ module.exports = {
|
||||
get StateUtils() {
|
||||
return require('./StateUtils').default;
|
||||
},
|
||||
get addNavigationHelpers() {
|
||||
return require('./addNavigationHelpers').default;
|
||||
},
|
||||
get NavigationActions() {
|
||||
return require('./NavigationActions').default;
|
||||
get getNavigation() {
|
||||
return require('./getNavigation').default;
|
||||
},
|
||||
|
||||
// Navigators
|
||||
get createNavigator() {
|
||||
return require('./navigators/createNavigator').default;
|
||||
},
|
||||
get StackNavigator() {
|
||||
return require('./navigators/createStackNavigator').default;
|
||||
get createKeyboardAwareNavigator() {
|
||||
return require('./navigators/createKeyboardAwareNavigator').default;
|
||||
},
|
||||
get TabNavigator() {
|
||||
return require('./navigators/createTabNavigator').default;
|
||||
get NavigationProvider() {
|
||||
return require('./views/NavigationContext').default.NavigationProvider;
|
||||
},
|
||||
get NavigationConsumer() {
|
||||
return require('./views/NavigationContext').default.NavigationConsumer;
|
||||
},
|
||||
get createStackNavigator() {
|
||||
return require('react-navigation-stack').createStackNavigator;
|
||||
},
|
||||
get StackNavigator() {
|
||||
console.warn(
|
||||
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
|
||||
);
|
||||
return require('react-navigation-stack').createStackNavigator;
|
||||
},
|
||||
get createSwitchNavigator() {
|
||||
return require('./navigators/createContainedSwitchNavigator').default;
|
||||
},
|
||||
get SwitchNavigator() {
|
||||
console.warn(
|
||||
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
|
||||
);
|
||||
return require('./navigators/createContainedSwitchNavigator').default;
|
||||
},
|
||||
get createDrawerNavigator() {
|
||||
return require('react-navigation-drawer').createDrawerNavigator;
|
||||
},
|
||||
get DrawerNavigator() {
|
||||
return require('./navigators/DrawerNavigator').default;
|
||||
console.warn(
|
||||
'The DrawerNavigator function name is deprecated, please use createDrawerNavigator instead'
|
||||
);
|
||||
return require('react-navigation-drawer').createDrawerNavigator;
|
||||
},
|
||||
get createTabNavigator() {
|
||||
console.warn(
|
||||
'createTabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator')
|
||||
.createTabNavigator;
|
||||
},
|
||||
get TabNavigator() {
|
||||
console.warn(
|
||||
'TabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator')
|
||||
.createTabNavigator;
|
||||
},
|
||||
get createBottomTabNavigator() {
|
||||
return require('react-navigation-tabs').createBottomTabNavigator;
|
||||
},
|
||||
get createMaterialTopTabNavigator() {
|
||||
return require('react-navigation-tabs').createMaterialTopTabNavigator;
|
||||
},
|
||||
|
||||
// Actions
|
||||
get NavigationActions() {
|
||||
return require('./NavigationActions').default;
|
||||
},
|
||||
get StackActions() {
|
||||
return require('./routers/StackActions').default;
|
||||
},
|
||||
get DrawerActions() {
|
||||
return require('react-navigation-drawer').DrawerActions;
|
||||
},
|
||||
|
||||
// Routers
|
||||
@@ -36,49 +91,101 @@ module.exports = {
|
||||
get TabRouter() {
|
||||
return require('./routers/TabRouter').default;
|
||||
},
|
||||
get DrawerRouter() {
|
||||
return require('react-navigation-drawer').DrawerRouter;
|
||||
},
|
||||
get SwitchRouter() {
|
||||
return require('./routers/SwitchRouter').default;
|
||||
},
|
||||
get createConfigGetter() {
|
||||
return require('./routers/createConfigGetter').default;
|
||||
},
|
||||
get getScreenForRouteName() {
|
||||
return require('./routers/getScreenForRouteName').default;
|
||||
},
|
||||
get validateRouteConfigMap() {
|
||||
return require('./routers/validateRouteConfigMap').default;
|
||||
},
|
||||
get pathUtils() {
|
||||
return require('./routers/pathUtils').default;
|
||||
},
|
||||
|
||||
// Views
|
||||
get Transitioner() {
|
||||
return require('./views/Transitioner').default;
|
||||
return require('react-navigation-stack').Transitioner;
|
||||
},
|
||||
get StackView() {
|
||||
return require('./views/StackView/StackView').default;
|
||||
return require('react-navigation-stack').StackView;
|
||||
},
|
||||
get StackViewCard() {
|
||||
return require('./views/StackView/StackViewCard').default;
|
||||
return require('react-navigation-stack').StackViewCard;
|
||||
},
|
||||
get StackViewTransitionConfigs() {
|
||||
return require('react-navigation-stack').StackViewTransitionConfigs;
|
||||
},
|
||||
get SafeAreaView() {
|
||||
return require('react-native-safe-area-view').default;
|
||||
},
|
||||
get SceneView() {
|
||||
return require('./views/SceneView').default;
|
||||
},
|
||||
get ResourceSavingSceneView() {
|
||||
return require('./views/ResourceSavingSceneView').default;
|
||||
},
|
||||
|
||||
// Header
|
||||
get Header() {
|
||||
return require('./views/Header/Header').default;
|
||||
return require('react-navigation-stack').Header;
|
||||
},
|
||||
get HeaderTitle() {
|
||||
return require('./views/Header/HeaderTitle').default;
|
||||
return require('react-navigation-stack').HeaderTitle;
|
||||
},
|
||||
get HeaderBackButton() {
|
||||
return require('./views/Header/HeaderBackButton').default;
|
||||
return require('react-navigation-stack').HeaderBackButton;
|
||||
},
|
||||
get HeaderStyleInterpolator() {
|
||||
return require('react-navigation-stack').HeaderStyleInterpolator;
|
||||
},
|
||||
|
||||
// DrawerView
|
||||
get DrawerView() {
|
||||
return require('./views/Drawer/DrawerView').default;
|
||||
return require('react-navigation-drawer').DrawerView;
|
||||
},
|
||||
get DrawerItems() {
|
||||
return require('./views/Drawer/DrawerNavigatorItems').default;
|
||||
return require('react-navigation-drawer').DrawerNavigatorItems;
|
||||
},
|
||||
get DrawerSidebar() {
|
||||
return require('react-navigation-drawer').DrawerSidebar;
|
||||
},
|
||||
|
||||
// TabView
|
||||
get TabView() {
|
||||
return require('./views/TabView/TabView').default;
|
||||
console.warn(
|
||||
'TabView is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator').TabView;
|
||||
},
|
||||
get TabBarTop() {
|
||||
return require('./views/TabView/TabBarTop').default;
|
||||
console.warn(
|
||||
'TabBarTop is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator').TabBarTop;
|
||||
},
|
||||
get TabBarBottom() {
|
||||
return require('./views/TabView/TabBarBottom').default;
|
||||
console.warn(
|
||||
'TabBarBottom is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator').TabBarBottom;
|
||||
},
|
||||
|
||||
// SwitchView
|
||||
get SwitchView() {
|
||||
return require('./views/SwitchView/SwitchView').default;
|
||||
},
|
||||
|
||||
// NavigationEvents
|
||||
get NavigationEvents() {
|
||||
return require('./views/NavigationEvents').default;
|
||||
},
|
||||
|
||||
// HOCs
|
||||
@@ -88,4 +195,7 @@ module.exports = {
|
||||
get withNavigationFocus() {
|
||||
return require('./views/withNavigationFocus').default;
|
||||
},
|
||||
get withOrientation() {
|
||||
return require('./views/withOrientation').default;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,17 +8,28 @@ module.exports = {
|
||||
get StateUtils() {
|
||||
return require('./StateUtils').default;
|
||||
},
|
||||
get addNavigationHelpers() {
|
||||
return require('./addNavigationHelpers').default;
|
||||
},
|
||||
get NavigationActions() {
|
||||
return require('./NavigationActions').default;
|
||||
get getNavigation() {
|
||||
return require('./getNavigation').default;
|
||||
},
|
||||
|
||||
// Navigators
|
||||
get createNavigator() {
|
||||
return require('./navigators/createNavigator').default;
|
||||
},
|
||||
get createSwitchNavigator() {
|
||||
return require('./navigators/createSwitchNavigator').default;
|
||||
},
|
||||
|
||||
// Actions
|
||||
get NavigationActions() {
|
||||
return require('./NavigationActions').default;
|
||||
},
|
||||
get StackActions() {
|
||||
return require('./routers/StackActions').default;
|
||||
},
|
||||
get DrawerActions() {
|
||||
return require('./routers/DrawerActions').default;
|
||||
},
|
||||
|
||||
// Routers
|
||||
get StackRouter() {
|
||||
@@ -27,6 +38,14 @@ module.exports = {
|
||||
get TabRouter() {
|
||||
return require('./routers/TabRouter').default;
|
||||
},
|
||||
get SwitchRouter() {
|
||||
return require('./routers/SwitchRouter').default;
|
||||
},
|
||||
|
||||
// NavigationEvents
|
||||
get NavigationEvents() {
|
||||
return require('./views/NavigationEvents').default;
|
||||
},
|
||||
|
||||
// HOCs
|
||||
get withNavigation() {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import invariant from '../utils/invariant';
|
||||
import TabRouter from './TabRouter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
|
||||
export default (routeConfigs, config = {}) => {
|
||||
const tabRouter = TabRouter(routeConfigs, config);
|
||||
return {
|
||||
...tabRouter,
|
||||
|
||||
getStateForAction(action, lastState) {
|
||||
const state = lastState || {
|
||||
...tabRouter.getStateForAction(action, undefined),
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
|
||||
// Handle explicit drawer actions
|
||||
if (
|
||||
state.isDrawerOpen &&
|
||||
action.type === NavigationActions.CLOSE_DRAWER
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
!state.isDrawerOpen &&
|
||||
action.type === NavigationActions.OPEN_DRAWER
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: true,
|
||||
};
|
||||
}
|
||||
if (action.type === NavigationActions.TOGGLE_DRAWER) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: !state.isDrawerOpen,
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back on tab router for screen switching logic
|
||||
const tabState = tabRouter.getStateForAction(action, state);
|
||||
if (tabState !== null && tabState !== state) {
|
||||
// If the tabs have changed, make sure to close the drawer
|
||||
return {
|
||||
...tabState,
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
},
|
||||
};
|
||||
};
|
||||
52
src/routers/StackActions.js
Normal file
52
src/routers/StackActions.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const POP = 'Navigation/POP';
|
||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
||||
const PUSH = 'Navigation/PUSH';
|
||||
const RESET = 'Navigation/RESET';
|
||||
const REPLACE = 'Navigation/REPLACE';
|
||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
|
||||
const pop = payload => ({
|
||||
type: POP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const popToTop = payload => ({
|
||||
type: POP_TO_TOP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const push = payload => ({
|
||||
type: PUSH,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const reset = payload => ({
|
||||
type: RESET,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const replace = payload => ({
|
||||
type: REPLACE,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const completeTransition = payload => ({
|
||||
type: COMPLETE_TRANSITION,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export default {
|
||||
POP,
|
||||
POP_TO_TOP,
|
||||
PUSH,
|
||||
RESET,
|
||||
REPLACE,
|
||||
COMPLETE_TRANSITION,
|
||||
|
||||
pop,
|
||||
popToTop,
|
||||
push,
|
||||
reset,
|
||||
replace,
|
||||
completeTransition,
|
||||
};
|
||||
@@ -1,28 +1,26 @@
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import StackActions from './StackActions';
|
||||
import createConfigGetter from './createConfigGetter';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import StateUtils from '../StateUtils';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import invariant from '../utils/invariant';
|
||||
import { generateKey } from './KeyGenerator';
|
||||
|
||||
function isEmpty(obj) {
|
||||
if (!obj) return true;
|
||||
for (let key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
import { createPathParser } from './pathUtils';
|
||||
|
||||
function behavesLikePushAction(action) {
|
||||
return (
|
||||
action.type === NavigationActions.NAVIGATE ||
|
||||
action.type === NavigationActions.PUSH
|
||||
action.type === StackActions.PUSH
|
||||
);
|
||||
}
|
||||
|
||||
const defaultActionCreators = (route, navStateKey) => ({});
|
||||
|
||||
function isResetToRootStack(action) {
|
||||
return action.type === StackActions.RESET && action.key === null;
|
||||
}
|
||||
|
||||
export default (routeConfigs, stackConfig = {}) => {
|
||||
// Fail fast on invalid route definitions
|
||||
validateRouteConfigMap(routeConfigs);
|
||||
@@ -43,12 +41,12 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
});
|
||||
|
||||
const { initialRouteParams } = stackConfig;
|
||||
const getCustomActionCreators =
|
||||
stackConfig.getCustomActionCreators || defaultActionCreators;
|
||||
|
||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
||||
|
||||
const initialChildRouter = childRouters[initialRouteName];
|
||||
const pathsByRouteNames = { ...stackConfig.paths } || {};
|
||||
let paths = [];
|
||||
|
||||
function getInitialState(action) {
|
||||
let route = {};
|
||||
@@ -65,7 +63,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
}
|
||||
return {
|
||||
key: 'StackRouterRoot',
|
||||
transitioningFromKey: null,
|
||||
isTransitioning: false,
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
@@ -100,45 +98,20 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
};
|
||||
return {
|
||||
key: 'StackRouterRoot',
|
||||
transitioningFromKey: false,
|
||||
isTransitioning: false,
|
||||
index: 0,
|
||||
routes: [route],
|
||||
};
|
||||
}
|
||||
|
||||
// Build paths for each route
|
||||
routeNames.forEach(routeName => {
|
||||
let pathPattern =
|
||||
pathsByRouteNames[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 {
|
||||
// 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: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
|
||||
const {
|
||||
getPathAndParamsForRoute,
|
||||
getActionForPathAndParams,
|
||||
} = createPathParser(childRouters, routeConfigs, stackConfig.paths);
|
||||
|
||||
return {
|
||||
childRouters,
|
||||
|
||||
getComponentForState(state) {
|
||||
const activeChildRoute = state.routes[state.index];
|
||||
const { routeName } = activeChildRoute;
|
||||
@@ -152,33 +125,121 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getActionCreators(route, navStateKey) {
|
||||
return {
|
||||
...getCustomActionCreators(route, navStateKey),
|
||||
pop: (n, params) =>
|
||||
StackActions.pop({
|
||||
n,
|
||||
...params,
|
||||
}),
|
||||
popToTop: params => StackActions.popToTop(params),
|
||||
push: (routeName, params, action) =>
|
||||
StackActions.push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
}),
|
||||
replace: (replaceWith, params, action, newKey) => {
|
||||
if (typeof replaceWith === 'string') {
|
||||
return StackActions.replace({
|
||||
routeName: replaceWith,
|
||||
params,
|
||||
action,
|
||||
key: route.key,
|
||||
newKey,
|
||||
});
|
||||
}
|
||||
invariant(
|
||||
typeof replaceWith === 'object',
|
||||
'Must replaceWith an object or a string'
|
||||
);
|
||||
invariant(
|
||||
params == null,
|
||||
'Params must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
action == null,
|
||||
'Child action must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
newKey == null,
|
||||
'Child action must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
return StackActions.replace(replaceWith);
|
||||
},
|
||||
reset: (actions, index) =>
|
||||
StackActions.reset({
|
||||
actions,
|
||||
index: index == null ? actions.length - 1 : index,
|
||||
key: navStateKey,
|
||||
}),
|
||||
dismiss: () =>
|
||||
NavigationActions.back({
|
||||
key: navStateKey,
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
getStateForAction(action, state) {
|
||||
// Set up the initial state if needed
|
||||
if (!state) {
|
||||
return getInitialState(action);
|
||||
}
|
||||
const lastRouteKey = state.routes[state.index].key;
|
||||
|
||||
// Check if the focused child scene wants to handle the action, as long as
|
||||
// it is not a reset to the root stack
|
||||
if (action.type !== NavigationActions.RESET || action.key !== null) {
|
||||
const keyIndex = action.key
|
||||
? StateUtils.indexOf(state, action.key)
|
||||
: -1;
|
||||
const childIndex = keyIndex >= 0 ? keyIndex : state.index;
|
||||
const childRoute = state.routes[childIndex];
|
||||
invariant(
|
||||
childRoute,
|
||||
`StateUtils erroneously thought index ${childIndex} exists`
|
||||
);
|
||||
const childRouter = childRouters[childRoute.routeName];
|
||||
if (childRouter) {
|
||||
const route = childRouter.getStateForAction(action, childRoute);
|
||||
if (route === null) {
|
||||
return state;
|
||||
const activeChildRoute = state.routes[state.index];
|
||||
|
||||
if (
|
||||
!isResetToRootStack(action) &&
|
||||
action.type !== NavigationActions.NAVIGATE
|
||||
) {
|
||||
// Let the active child router handle the action
|
||||
const activeChildRouter = childRouters[activeChildRoute.routeName];
|
||||
if (activeChildRouter) {
|
||||
const route = activeChildRouter.getStateForAction(
|
||||
action,
|
||||
activeChildRoute
|
||||
);
|
||||
if (route !== null && route !== activeChildRoute) {
|
||||
return StateUtils.replaceAt(
|
||||
state,
|
||||
activeChildRoute.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
|
||||
);
|
||||
}
|
||||
if (route && route !== childRoute) {
|
||||
return StateUtils.replaceAt(state, childRoute.key, route);
|
||||
}
|
||||
} else if (action.type === NavigationActions.NAVIGATE) {
|
||||
// Traverse routes from the top of the stack to the bottom, so the
|
||||
// active route has the first opportunity, then the one before it, etc.
|
||||
for (let childRoute of state.routes.slice().reverse()) {
|
||||
let childRouter = childRouters[childRoute.routeName];
|
||||
let childAction =
|
||||
action.routeName === childRoute.routeName && action.action
|
||||
? action.action
|
||||
: action;
|
||||
|
||||
if (childRouter) {
|
||||
const nextRouteState = childRouter.getStateForAction(
|
||||
childAction,
|
||||
childRoute
|
||||
);
|
||||
|
||||
if (nextRouteState === null || nextRouteState !== childRoute) {
|
||||
const newState = StateUtils.replaceAndPrune(
|
||||
state,
|
||||
nextRouteState ? nextRouteState.key : childRoute.key,
|
||||
nextRouteState ? nextRouteState : childRoute
|
||||
);
|
||||
return {
|
||||
...newState,
|
||||
isTransitioning:
|
||||
state.index !== newState.index
|
||||
? action.immediate !== true
|
||||
: state.isTransitioning,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,46 +254,50 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
let route;
|
||||
|
||||
invariant(
|
||||
action.type !== NavigationActions.PUSH || action.key == null,
|
||||
action.type !== StackActions.PUSH || action.key == null,
|
||||
'StackRouter does not support key on the push action'
|
||||
);
|
||||
|
||||
// With the navigate action, the key may be provided for pushing, or to navigate back to the key
|
||||
if (action.key) {
|
||||
const lastRouteIndex = state.routes.findIndex(
|
||||
r => r.key === action.key
|
||||
);
|
||||
if (lastRouteIndex !== -1) {
|
||||
// If index is unchanged and params are not being set, leave state identity intact
|
||||
if (state.index === lastRouteIndex && !action.params) {
|
||||
return state;
|
||||
}
|
||||
// Before pushing a new route we first try to find one in the existing route stack
|
||||
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
|
||||
const lastRouteIndex = state.routes.findIndex(r => {
|
||||
if (action.key) {
|
||||
return r.key === action.key;
|
||||
} else {
|
||||
return r.routeName === action.routeName;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the now unused routes at the tail of the routes array
|
||||
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
||||
if (action.type !== StackActions.PUSH && lastRouteIndex !== -1) {
|
||||
// If index is unchanged and params are not being set, leave state identity intact
|
||||
if (state.index === lastRouteIndex && !action.params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply params if provided, otherwise leave route identity intact
|
||||
if (action.params) {
|
||||
const route = state.routes.find(r => r.key === action.key);
|
||||
routes[lastRouteIndex] = {
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...action.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
// Return state with new index. Change transitioningFromKey only if index has changed
|
||||
return {
|
||||
...state,
|
||||
transitioningFromKey:
|
||||
state.index !== lastRouteIndex
|
||||
? action.immediate !== true ? lastRouteKey : null
|
||||
: null,
|
||||
index: lastRouteIndex,
|
||||
routes,
|
||||
// Remove the now unused routes at the tail of the routes array
|
||||
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
||||
|
||||
// Apply params if provided, otherwise leave route identity intact
|
||||
if (action.params) {
|
||||
const route = state.routes[lastRouteIndex];
|
||||
routes[lastRouteIndex] = {
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...action.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
// Return state with new index. Change isTransitioning only if index has changed
|
||||
return {
|
||||
...state,
|
||||
isTransitioning:
|
||||
state.index !== lastRouteIndex
|
||||
? action.immediate !== true
|
||||
: state.isTransitioning,
|
||||
index: lastRouteIndex,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
|
||||
if (childRouter) {
|
||||
@@ -254,18 +319,14 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
}
|
||||
return {
|
||||
...StateUtils.push(state, route),
|
||||
transitioningFromKey: action.immediate !== true ? lastRouteKey : null,
|
||||
isTransitioning: action.immediate !== true,
|
||||
};
|
||||
} else if (
|
||||
action.type === NavigationActions.PUSH &&
|
||||
action.type === StackActions.PUSH &&
|
||||
childRouters[action.routeName] === undefined
|
||||
) {
|
||||
// If we've made it this far with a push action, we return the
|
||||
// state with a new identity to prevent the action from bubbling
|
||||
// back up.
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
// Return the state identity to bubble the action up
|
||||
return state;
|
||||
}
|
||||
|
||||
// Handle navigation to other child routers that are not yet pushed
|
||||
@@ -298,14 +359,17 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
routeName: childRouterName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
return StateUtils.push(state, route);
|
||||
return {
|
||||
...StateUtils.push(state, route),
|
||||
isTransitioning: action.immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
||||
if (action.type === NavigationActions.POP_TO_TOP) {
|
||||
if (action.type === StackActions.POP_TO_TOP) {
|
||||
// Refuse to handle pop to top if a key is given that doesn't correspond
|
||||
// to this router
|
||||
if (action.key && state.key !== action.key) {
|
||||
@@ -314,14 +378,10 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
// If we're already at the top, then we return the state with a new
|
||||
// identity so that the action is handled by this router.
|
||||
if (state.index === 0) {
|
||||
if (state.index > 0) {
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
lastRouteKey: action.immediate !== true ? lastRouteKey : null,
|
||||
isTransitioning: action.immediate !== true,
|
||||
index: 0,
|
||||
routes: [state.routes[0]],
|
||||
};
|
||||
@@ -330,8 +390,16 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
}
|
||||
|
||||
// Handle replace action
|
||||
if (action.type === NavigationActions.REPLACE) {
|
||||
const routeIndex = state.routes.findIndex(r => r.key === action.key);
|
||||
if (action.type === StackActions.REPLACE) {
|
||||
let routeIndex;
|
||||
|
||||
// If the key param is undefined, set the index to the last route in the stack
|
||||
if (action.key === undefined && state.routes.length) {
|
||||
routeIndex = state.routes.length - 1;
|
||||
} else {
|
||||
routeIndex = state.routes.findIndex(r => r.key === action.key);
|
||||
}
|
||||
|
||||
// Only replace if the key matches one of our routes
|
||||
if (routeIndex !== -1) {
|
||||
const childRouter = childRouters[action.routeName];
|
||||
@@ -356,13 +424,13 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
// Update transitioning state
|
||||
if (
|
||||
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
||||
action.type === StackActions.COMPLETE_TRANSITION &&
|
||||
(action.key == null || action.key === state.key) &&
|
||||
state.transitioningFromKey
|
||||
state.isTransitioning
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
transitioningFromKey: null,
|
||||
isTransitioning: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -386,7 +454,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === NavigationActions.RESET) {
|
||||
if (action.type === StackActions.RESET) {
|
||||
// Only handle reset actions that are unspecified or match this state key
|
||||
if (action.key != null && action.key != state.key) {
|
||||
// Deliberately use != instead of !== so we can match null with
|
||||
@@ -423,11 +491,11 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
if (
|
||||
action.type === NavigationActions.BACK ||
|
||||
action.type === NavigationActions.POP
|
||||
action.type === StackActions.POP
|
||||
) {
|
||||
const { key, n, immediate } = action;
|
||||
let backRouteIndex = state.index;
|
||||
if (action.type === NavigationActions.POP && n != null) {
|
||||
if (action.type === StackActions.POP && n != null) {
|
||||
// determine the index to go back *from*. In this case, n=1 means to go
|
||||
// back from state.index, as if it were a normal "BACK" action
|
||||
backRouteIndex = Math.max(1, state.index - n + 1);
|
||||
@@ -441,136 +509,57 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
...state,
|
||||
routes: state.routes.slice(0, backRouteIndex),
|
||||
index: backRouteIndex - 1,
|
||||
transitioningFromKey: immediate !== true ? lastRouteKey : null,
|
||||
};
|
||||
} else if (
|
||||
backRouteIndex === 0 &&
|
||||
action.type === NavigationActions.POP
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
getPathAndParamsForState(state) {
|
||||
const route = state.routes[state.index];
|
||||
const routeName = route.routeName;
|
||||
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,
|
||||
};
|
||||
return getPathAndParamsForRoute(route);
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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 || {};
|
||||
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 } : {}),
|
||||
});
|
||||
getActionForPathAndParams(path, params) {
|
||||
return getActionForPathAndParams(path, params);
|
||||
},
|
||||
|
||||
getScreenOptions: createConfigGetter(
|
||||
|
||||
327
src/routers/SwitchRouter.js
Normal file
327
src/routers/SwitchRouter.js
Normal file
@@ -0,0 +1,327 @@
|
||||
import invariant from '../utils/invariant';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import createConfigGetter from './createConfigGetter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import StackActions from './StackActions';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import { createPathParser } from './pathUtils';
|
||||
|
||||
const defaultActionCreators = (route, navStateKey) => ({});
|
||||
|
||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||
return [
|
||||
NavigationActions.SET_PARAMS,
|
||||
// Todo: make SwitchRouter not depend on StackActions..
|
||||
StackActions.COMPLETE_TRANSITION,
|
||||
].includes(actionType);
|
||||
}
|
||||
|
||||
export default (routeConfigs, config = {}) => {
|
||||
// Fail fast on invalid route definitions
|
||||
validateRouteConfigMap(routeConfigs);
|
||||
|
||||
const order = config.order || Object.keys(routeConfigs);
|
||||
|
||||
const getCustomActionCreators =
|
||||
config.getCustomActionCreators || defaultActionCreators;
|
||||
|
||||
const initialRouteParams = config.initialRouteParams;
|
||||
const initialRouteName = config.initialRouteName || order[0];
|
||||
const backBehavior = config.backBehavior || 'none';
|
||||
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
||||
? config.resetOnBlur
|
||||
: true;
|
||||
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||
const childRouters = {};
|
||||
order.forEach(routeName => {
|
||||
const routeConfig = routeConfigs[routeName];
|
||||
childRouters[routeName] = null;
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
if (screen.router) {
|
||||
childRouters[routeName] = screen.router;
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
getPathAndParamsForRoute,
|
||||
getActionForPathAndParams,
|
||||
} = createPathParser(childRouters, routeConfigs, config.paths);
|
||||
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
function resetChildRoute(routeName) {
|
||||
const params =
|
||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||
const childRouter = childRouters[routeName];
|
||||
if (childRouter) {
|
||||
const childAction = NavigationActions.init();
|
||||
return {
|
||||
...childRouter.getStateForAction(childAction),
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
childRouters,
|
||||
|
||||
getActionCreators(route, stateKey) {
|
||||
return getCustomActionCreators(route, stateKey);
|
||||
},
|
||||
|
||||
getStateForAction(action, inputState) {
|
||||
let prevState = inputState ? { ...inputState } : inputState;
|
||||
let state = inputState || getInitialState();
|
||||
let activeChildIndex = state.index;
|
||||
|
||||
if (action.type === NavigationActions.INIT) {
|
||||
// NOTE(brentvatne): this seems weird... why are we merging these
|
||||
// params into child routes?
|
||||
// ---------------------------------------------------------------
|
||||
// Merge any params from the action into all the child routes
|
||||
const { params } = action;
|
||||
if (params) {
|
||||
state.routes = state.routes.map(route => ({
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...params,
|
||||
...(route.routeName === initialRouteName
|
||||
? initialRouteParams
|
||||
: null),
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Let the current child handle it
|
||||
const activeChildLastState = state.routes[state.index];
|
||||
const activeChildRouter = childRouters[order[state.index]];
|
||||
if (activeChildRouter) {
|
||||
const activeChildState = activeChildRouter.getStateForAction(
|
||||
action,
|
||||
activeChildLastState
|
||||
);
|
||||
if (!activeChildState && inputState) {
|
||||
return null;
|
||||
}
|
||||
if (activeChildState && activeChildState !== activeChildLastState) {
|
||||
const routes = [...state.routes];
|
||||
routes[state.index] = activeChildState;
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tab changing. Do this after letting the current tab try to
|
||||
// handle the action, to allow inner children to change first
|
||||
const isBackEligible =
|
||||
action.key == null || action.key === activeChildLastState.key;
|
||||
if (action.type === NavigationActions.BACK) {
|
||||
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||
activeChildIndex = initialRouteIndex;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
let didNavigate = false;
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
didNavigate = !!order.find((childId, i) => {
|
||||
if (childId === action.routeName) {
|
||||
activeChildIndex = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (didNavigate) {
|
||||
const childState = state.routes[activeChildIndex];
|
||||
const childRouter = childRouters[action.routeName];
|
||||
let newChildState;
|
||||
|
||||
if (action.action) {
|
||||
newChildState = childRouter
|
||||
? childRouter.getStateForAction(action.action, childState)
|
||||
: null;
|
||||
} else if (!action.action && action.params) {
|
||||
newChildState = {
|
||||
...childState,
|
||||
params: {
|
||||
...(childState.params || {}),
|
||||
...action.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (newChildState && newChildState !== childState) {
|
||||
const routes = [...state.routes];
|
||||
routes[activeChildIndex] = newChildState;
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
index: activeChildIndex,
|
||||
});
|
||||
} else if (
|
||||
!newChildState &&
|
||||
state.index === activeChildIndex &&
|
||||
prevState
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === NavigationActions.SET_PARAMS) {
|
||||
const key = action.key;
|
||||
const lastRoute = state.routes.find(route => route.key === key);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.params,
|
||||
...action.params,
|
||||
};
|
||||
const routes = [...state.routes];
|
||||
routes[state.routes.indexOf(lastRoute)] = {
|
||||
...lastRoute,
|
||||
params,
|
||||
};
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (activeChildIndex !== state.index) {
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
index: activeChildIndex,
|
||||
});
|
||||
} else if (didNavigate && !inputState) {
|
||||
return state;
|
||||
} else if (didNavigate) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
// Let other children handle it and switch to the first child that returns a new state
|
||||
let index = state.index;
|
||||
let routes = state.routes;
|
||||
order.find((childId, i) => {
|
||||
const childRouter = childRouters[childId];
|
||||
if (i === index) {
|
||||
return false;
|
||||
}
|
||||
let childState = routes[i];
|
||||
if (childRouter) {
|
||||
childState = childRouter.getStateForAction(action, childState);
|
||||
}
|
||||
if (!childState) {
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
if (childState !== routes[i]) {
|
||||
routes = [...routes];
|
||||
routes[i] = childState;
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Nested routers can be updated after switching children with actions such as SET_PARAMS
|
||||
// and COMPLETE_TRANSITION.
|
||||
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
||||
// that can be handled by child routers without automatically changing index.
|
||||
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
||||
index = state.index;
|
||||
}
|
||||
|
||||
if (index !== state.index || routes !== state.routes) {
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
getComponentForState(state) {
|
||||
const routeName = state.routes[state.index].routeName;
|
||||
invariant(
|
||||
routeName,
|
||||
`There is no route defined for index ${state.index}. Check that
|
||||
that you passed in a navigation state with a valid tab/screen index.`
|
||||
);
|
||||
const childRouter = childRouters[routeName];
|
||||
if (childRouter) {
|
||||
return childRouter.getComponentForState(state.routes[state.index]);
|
||||
}
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getComponentForRouteName(routeName) {
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getPathAndParamsForState(state) {
|
||||
const route = state.routes[state.index];
|
||||
return getPathAndParamsForRoute(route);
|
||||
},
|
||||
|
||||
getActionForPathAndParams(path, params) {
|
||||
return getActionForPathAndParams(path, params);
|
||||
},
|
||||
|
||||
getScreenOptions: createConfigGetter(
|
||||
routeConfigs,
|
||||
config.navigationOptions
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -1,326 +1,11 @@
|
||||
import invariant from '../utils/invariant';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import createConfigGetter from './createConfigGetter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
|
||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||
return [
|
||||
NavigationActions.SET_PARAMS,
|
||||
NavigationActions.COMPLETE_TRANSITION,
|
||||
].includes(actionType);
|
||||
}
|
||||
import SwitchRouter from './SwitchRouter';
|
||||
import withDefaultValue from '../utils/withDefaultValue';
|
||||
|
||||
export default (routeConfigs, config = {}) => {
|
||||
// Fail fast on invalid route definitions
|
||||
validateRouteConfigMap(routeConfigs);
|
||||
config = { ...config };
|
||||
config = withDefaultValue(config, 'resetOnBlur', false);
|
||||
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
|
||||
|
||||
const order = config.order || Object.keys(routeConfigs);
|
||||
const paths = config.paths || {};
|
||||
const initialRouteParams = config.initialRouteParams;
|
||||
const initialRouteName = config.initialRouteName || order[0];
|
||||
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||
const backBehavior = config.backBehavior || 'initialRoute';
|
||||
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||
const tabRouters = {};
|
||||
order.forEach(routeName => {
|
||||
const routeConfig = routeConfigs[routeName];
|
||||
paths[routeName] =
|
||||
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
||||
tabRouters[routeName] = null;
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
if (screen.router) {
|
||||
tabRouters[routeName] = screen.router;
|
||||
}
|
||||
});
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}' for TabRouter. ` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
return {
|
||||
getStateForAction(action, inputState) {
|
||||
// Establish a default state
|
||||
let state = inputState;
|
||||
if (!state) {
|
||||
const routes = order.map(routeName => {
|
||||
const params =
|
||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||
const tabRouter = tabRouters[routeName];
|
||||
if (tabRouter) {
|
||||
const childAction = NavigationActions.init();
|
||||
return {
|
||||
...tabRouter.getStateForAction(childAction),
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
});
|
||||
state = {
|
||||
routes,
|
||||
index: initialRouteIndex,
|
||||
transitioningFromKey: null,
|
||||
};
|
||||
// console.log(`${order.join('-')}: Initial state`, {state});
|
||||
}
|
||||
|
||||
if (action.type === NavigationActions.INIT) {
|
||||
// Merge any params from the action into all the child routes
|
||||
const { params } = action;
|
||||
if (params) {
|
||||
state.routes = state.routes.map(route => ({
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...params,
|
||||
...(route.routeName === initialRouteName
|
||||
? initialRouteParams
|
||||
: null),
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Let the current tab handle it
|
||||
const activeTabLastState = state.routes[state.index];
|
||||
const activeTabRouter = tabRouters[order[state.index]];
|
||||
if (activeTabRouter) {
|
||||
const activeTabState = activeTabRouter.getStateForAction(
|
||||
action,
|
||||
activeTabLastState
|
||||
);
|
||||
if (!activeTabState && inputState) {
|
||||
return null;
|
||||
}
|
||||
if (activeTabState && activeTabState !== activeTabLastState) {
|
||||
const routes = [...state.routes];
|
||||
routes[state.index] = activeTabState;
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tab changing. Do this after letting the current tab try to
|
||||
// handle the action, to allow inner tabs to change first
|
||||
let activeTabIndex = state.index;
|
||||
const isBackEligible =
|
||||
action.key == null || action.key === activeTabLastState.key;
|
||||
if (action.type === NavigationActions.BACK) {
|
||||
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||
activeTabIndex = initialRouteIndex;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
let didNavigate = false;
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
const navigateAction = action;
|
||||
didNavigate = !!order.find((tabId, i) => {
|
||||
if (tabId === navigateAction.routeName) {
|
||||
activeTabIndex = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (didNavigate) {
|
||||
const childState = state.routes[activeTabIndex];
|
||||
let newChildState;
|
||||
|
||||
const tabRouter = tabRouters[action.routeName];
|
||||
|
||||
if (action.action) {
|
||||
newChildState = tabRouter
|
||||
? tabRouter.getStateForAction(action.action, childState)
|
||||
: null;
|
||||
} else if (!tabRouter && action.params) {
|
||||
newChildState = {
|
||||
...childState,
|
||||
params: {
|
||||
...(childState.params || {}),
|
||||
...action.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (newChildState && newChildState !== childState) {
|
||||
const routes = [...state.routes];
|
||||
routes[activeTabIndex] = newChildState;
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
index: activeTabIndex,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.type === NavigationActions.SET_PARAMS) {
|
||||
const key = action.key;
|
||||
const lastRoute = state.routes.find(route => route.key === key);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.params,
|
||||
...action.params,
|
||||
};
|
||||
const routes = [...state.routes];
|
||||
routes[state.routes.indexOf(lastRoute)] = {
|
||||
...lastRoute,
|
||||
params,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (activeTabIndex !== state.index) {
|
||||
return {
|
||||
...state,
|
||||
index: activeTabIndex,
|
||||
};
|
||||
} else if (didNavigate && !inputState) {
|
||||
return state;
|
||||
} else if (didNavigate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Let other tabs handle it and switch to the first tab that returns a new state
|
||||
let index = state.index;
|
||||
let routes = state.routes;
|
||||
order.find((tabId, i) => {
|
||||
const tabRouter = tabRouters[tabId];
|
||||
if (i === index) {
|
||||
return false;
|
||||
}
|
||||
let tabState = routes[i];
|
||||
if (tabRouter) {
|
||||
// console.log(`${order.join('-')}: Processing child router:`, {action, tabState});
|
||||
tabState = tabRouter.getStateForAction(action, tabState);
|
||||
}
|
||||
if (!tabState) {
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
if (tabState !== routes[i]) {
|
||||
routes = [...routes];
|
||||
routes[i] = tabState;
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
|
||||
|
||||
// Nested routers can be updated after switching tabs with actions such as SET_PARAMS
|
||||
// and COMPLETE_TRANSITION.
|
||||
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
||||
// that can be handled by child routers without automatically changing index.
|
||||
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
||||
index = state.index;
|
||||
}
|
||||
|
||||
if (index !== state.index || routes !== state.routes) {
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
getComponentForState(state) {
|
||||
const routeName = state.routes[state.index].routeName;
|
||||
invariant(
|
||||
routeName,
|
||||
`There is no route defined for index ${state.index}. Check that
|
||||
that you passed in a navigation state with a valid tab/screen index.`
|
||||
);
|
||||
const childRouter = tabRouters[routeName];
|
||||
if (childRouter) {
|
||||
return childRouter.getComponentForState(state.routes[state.index]);
|
||||
}
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getComponentForRouteName(routeName) {
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getPathAndParamsForState(state) {
|
||||
const route = state.routes[state.index];
|
||||
const routeName = order[state.index];
|
||||
const subPath = paths[routeName];
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
let path = subPath;
|
||||
let params = route.params;
|
||||
if (screen && screen.router) {
|
||||
const stateRoute = route;
|
||||
// If it has a router it's a navigator.
|
||||
// If it doesn't have router it's an ordinary React component.
|
||||
const child = screen.router.getPathAndParamsForState(stateRoute);
|
||||
path = subPath ? `${subPath}/${child.path}` : child.path;
|
||||
params = child.params ? { ...params, ...child.params } : params;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an optional action, based on a relative path and query params.
|
||||
*
|
||||
* This will return null if there is no action matched
|
||||
*/
|
||||
getActionForPathAndParams(path, params) {
|
||||
return (
|
||||
order
|
||||
.map(tabId => {
|
||||
const parts = path.split('/');
|
||||
const pathToTest = paths[tabId];
|
||||
if (parts[0] === pathToTest) {
|
||||
const tabRouter = tabRouters[tabId];
|
||||
const action = NavigationActions.navigate({
|
||||
routeName: tabId,
|
||||
});
|
||||
if (tabRouter && tabRouter.getActionForPathAndParams) {
|
||||
action.action = tabRouter.getActionForPathAndParams(
|
||||
parts.slice(1).join('/'),
|
||||
params
|
||||
);
|
||||
} else if (params) {
|
||||
action.params = params;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.find(action => !!action) ||
|
||||
order
|
||||
.map(tabId => {
|
||||
const tabRouter = tabRouters[tabId];
|
||||
return (
|
||||
tabRouter && tabRouter.getActionForPathAndParams(path, params)
|
||||
);
|
||||
})
|
||||
.find(action => !!action) ||
|
||||
null
|
||||
);
|
||||
},
|
||||
|
||||
getScreenOptions: createConfigGetter(
|
||||
routeConfigs,
|
||||
config.navigationOptions
|
||||
),
|
||||
};
|
||||
const switchRouter = SwitchRouter(routeConfigs, config);
|
||||
return switchRouter;
|
||||
};
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/* eslint react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import DrawerRouter from '../DrawerRouter';
|
||||
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
|
||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
||||
|
||||
describe('DrawerRouter', () => {
|
||||
test('Handles basic tab logic', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
const router = DrawerRouter({
|
||||
Foo: { screen: ScreenA },
|
||||
Bar: { screen: ScreenB },
|
||||
});
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
const expectedState = {
|
||||
index: 0,
|
||||
transitioningFromKey: null,
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
],
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
expect(state).toEqual(expectedState);
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
||||
state
|
||||
);
|
||||
const expectedState2 = {
|
||||
index: 1,
|
||||
transitioningFromKey: null,
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
],
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
expect(state2).toEqual(expectedState2);
|
||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||
});
|
||||
|
||||
test('Drawer opens closes and toggles', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
const router = DrawerRouter({
|
||||
Foo: { screen: ScreenA },
|
||||
Bar: { screen: ScreenB },
|
||||
});
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
expect(state.isDrawerOpen).toEqual(false);
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.OPEN_DRAWER },
|
||||
state
|
||||
);
|
||||
expect(state2.isDrawerOpen).toEqual(true);
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.CLOSE_DRAWER },
|
||||
state2
|
||||
);
|
||||
expect(state3.isDrawerOpen).toEqual(false);
|
||||
const state4 = router.getStateForAction(
|
||||
{ type: NavigationActions.TOGGLE_DRAWER },
|
||||
state3
|
||||
);
|
||||
expect(state4.isDrawerOpen).toEqual(true);
|
||||
});
|
||||
});
|
||||
596
src/routers/__tests__/PathHandling-test.js
Normal file
596
src/routers/__tests__/PathHandling-test.js
Normal file
@@ -0,0 +1,596 @@
|
||||
/* 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 performRouterTest = createTestRouter => {
|
||||
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 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 with empty action', () => {
|
||||
const router = createTestRouter(
|
||||
{
|
||||
Foo: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
Bar: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
},
|
||||
{ initialRouteName: 'Bar', initialRouteParams: { foo: 42 } }
|
||||
);
|
||||
const action = router.getActionForPathAndParams('');
|
||||
expect(action).toEqual(null);
|
||||
const state = router.getStateForAction(action || NavigationActions.init());
|
||||
expect(state.routes[state.index]).toEqual(
|
||||
expect.objectContaining({
|
||||
routeName: 'Bar',
|
||||
params: { foo: 42 },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('Handles paths with several params', () => {
|
||||
const router = createTestRouter({
|
||||
Person: {
|
||||
path: 'people/:person',
|
||||
screen: () => <div />,
|
||||
},
|
||||
Task: {
|
||||
path: 'people/:person/tasks/:task',
|
||||
screen: () => <div />,
|
||||
},
|
||||
ThingA: {
|
||||
path: 'things/:good',
|
||||
screen: () => <div />,
|
||||
},
|
||||
Thing: {
|
||||
path: 'things/:good/:thing',
|
||||
screen: () => <div />,
|
||||
},
|
||||
});
|
||||
const action = router.getActionForPathAndParams(
|
||||
'people/brent/tasks/everything'
|
||||
);
|
||||
expect(action).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Task',
|
||||
params: { person: 'brent', task: 'everything' },
|
||||
});
|
||||
|
||||
const action1 = router.getActionForPathAndParams('people/lucy');
|
||||
expect(action1).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Person',
|
||||
params: { person: 'lucy' },
|
||||
});
|
||||
|
||||
const action2 = router.getActionForPathAndParams('things/foo/bar');
|
||||
expect(action2).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Thing',
|
||||
params: { good: 'foo', thing: 'bar' },
|
||||
});
|
||||
|
||||
const action3 = router.getActionForPathAndParams('things/foo');
|
||||
expect(action3).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'ThingA',
|
||||
params: { good: 'foo' },
|
||||
});
|
||||
});
|
||||
|
||||
test('Handles empty path configuration', () => {
|
||||
const router = createTestRouter({
|
||||
Foo: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
Bar: {
|
||||
screen: () => <div />,
|
||||
path: '',
|
||||
},
|
||||
});
|
||||
const action = router.getActionForPathAndParams('');
|
||||
expect(action).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('Handles wildcard path configuration', () => {
|
||||
const router = createTestRouter({
|
||||
Foo: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
Bar: {
|
||||
screen: () => <div />,
|
||||
path: ':something',
|
||||
},
|
||||
});
|
||||
const action = router.getActionForPathAndParams('');
|
||||
expect(action).toEqual(null);
|
||||
|
||||
const action1 = router.getActionForPathAndParams('Foo');
|
||||
expect(action1).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Foo',
|
||||
params: {},
|
||||
});
|
||||
const action2 = router.getActionForPathAndParams('asdf');
|
||||
expect(action2).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
params: { something: 'asdf' },
|
||||
});
|
||||
});
|
||||
|
||||
test('Null path behavior', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const router = createTestRouter({
|
||||
Bar: {
|
||||
screen: ScreenA,
|
||||
},
|
||||
Foo: {
|
||||
path: null,
|
||||
screen: ScreenA,
|
||||
},
|
||||
Baz: {
|
||||
path: '',
|
||||
screen: ScreenA,
|
||||
},
|
||||
});
|
||||
const action0 = router.getActionForPathAndParams('test/random', {});
|
||||
expect(action0).toBe(null);
|
||||
|
||||
const action1 = router.getActionForPathAndParams('', {});
|
||||
expect(action1.routeName).toBe('Baz');
|
||||
const state1 = router.getStateForAction(action1);
|
||||
expect(state1.routes[state1.index].routeName).toBe('Baz');
|
||||
});
|
||||
|
||||
test('Multiple null path sub routers path behavior', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
ScreenB.router = createTestRouter({
|
||||
Foo: ScreenA,
|
||||
});
|
||||
const ScreenC = () => <div />;
|
||||
ScreenC.router = createTestRouter({
|
||||
Bar: {
|
||||
path: 'bar/:id',
|
||||
screen: ScreenA,
|
||||
},
|
||||
Empty: {
|
||||
path: '',
|
||||
screen: ScreenA,
|
||||
},
|
||||
});
|
||||
const router = createTestRouter({
|
||||
A: {
|
||||
screen: ScreenA,
|
||||
},
|
||||
B: {
|
||||
path: null,
|
||||
screen: ScreenB,
|
||||
},
|
||||
C: {
|
||||
path: null,
|
||||
screen: ScreenC,
|
||||
},
|
||||
});
|
||||
const action0 = router.getActionForPathAndParams('Foo', {});
|
||||
expect(action0.routeName).toBe('B');
|
||||
expect(action0.action.routeName).toBe('Foo');
|
||||
|
||||
const action1 = router.getActionForPathAndParams('', {});
|
||||
expect(action1.routeName).toBe('C');
|
||||
expect(action1.action.routeName).toBe('Empty');
|
||||
|
||||
const action2 = router.getActionForPathAndParams('A', {});
|
||||
expect(action2.routeName).toBe('A');
|
||||
|
||||
const action3 = router.getActionForPathAndParams('bar/asdf', {});
|
||||
expect(action3.routeName).toBe('C');
|
||||
expect(action3.action.routeName).toBe('Bar');
|
||||
expect(action3.action.params.id).toBe('asdf');
|
||||
});
|
||||
|
||||
test('Null and empty string path sub routers behavior', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
ScreenB.router = createTestRouter({
|
||||
Foo: ScreenA,
|
||||
Baz: {
|
||||
screen: ScreenA,
|
||||
path: '',
|
||||
},
|
||||
});
|
||||
const ScreenC = () => <div />;
|
||||
ScreenC.router = createTestRouter({
|
||||
Boo: ScreenA,
|
||||
Bar: ScreenA,
|
||||
Baz: {
|
||||
screen: ScreenA,
|
||||
path: '',
|
||||
},
|
||||
});
|
||||
const router = createTestRouter({
|
||||
B: {
|
||||
path: null,
|
||||
screen: ScreenB,
|
||||
},
|
||||
C: {
|
||||
path: '',
|
||||
screen: ScreenC,
|
||||
},
|
||||
});
|
||||
const action0 = router.getActionForPathAndParams('', {});
|
||||
expect(action0.routeName).toBe('C');
|
||||
expect(action0.action.routeName).toBe('Baz');
|
||||
|
||||
const action1 = router.getActionForPathAndParams('Foo', {});
|
||||
expect(action1.routeName).toBe('B');
|
||||
expect(action1.action.routeName).toBe('Foo');
|
||||
|
||||
const action2 = router.getActionForPathAndParams('Bar', {});
|
||||
expect(action2.routeName).toBe('C');
|
||||
expect(action2.action.routeName).toBe('Bar');
|
||||
|
||||
const action3 = router.getActionForPathAndParams('unknown', {});
|
||||
expect(action3).toBe(null);
|
||||
});
|
||||
|
||||
test('Empty path acts as wildcard for nested router', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const Foo = () => <div />;
|
||||
const ScreenC = () => <div />;
|
||||
ScreenC.router = createTestRouter({
|
||||
Boo: ScreenA,
|
||||
Bar: ScreenA,
|
||||
});
|
||||
Foo.router = createTestRouter({
|
||||
Quo: ScreenA,
|
||||
Qux: {
|
||||
screen: ScreenC,
|
||||
path: '',
|
||||
},
|
||||
});
|
||||
const router = createTestRouter({
|
||||
Bar: {
|
||||
screen: ScreenA,
|
||||
},
|
||||
Foo,
|
||||
});
|
||||
const action0 = router.getActionForPathAndParams('Foo/Bar', {});
|
||||
expect(action0.routeName).toBe('Foo');
|
||||
expect(action0.action.routeName).toBe('Qux');
|
||||
expect(action0.action.action.routeName).toBe('Bar');
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
{
|
||||
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('URI encoded path param gets parsed and correctly printed', () => {
|
||||
const router = createTestRouter({
|
||||
main: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
person: {
|
||||
path: 'people/:name',
|
||||
screen: () => <div />,
|
||||
},
|
||||
});
|
||||
|
||||
const action = testRouter.getActionForPathAndParams('people/Henry%20L');
|
||||
expect(action).toEqual({
|
||||
routeName: 'person',
|
||||
params: {
|
||||
id: 'Henry L',
|
||||
},
|
||||
type: NavigationActions.NAVIGATE,
|
||||
});
|
||||
const s = testRouter.getStateForAction(action);
|
||||
const out = testRouter.getPathAndParamsForState(s);
|
||||
expect(out.path).toEqual('people/Henry%20L');
|
||||
expect(out.params).toEqual({});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
test('paths option set as null on router overrides path from route config', () => {
|
||||
const router = createTestRouter(
|
||||
{
|
||||
main: {
|
||||
screen: MainNavigator,
|
||||
},
|
||||
baz: {
|
||||
path: 'bazPath',
|
||||
screen: FooNavigator,
|
||||
},
|
||||
},
|
||||
{ paths: { baz: null } }
|
||||
);
|
||||
const action = router.getActionForPathAndParams('b/noBaz', {});
|
||||
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);
|
||||
});
|
||||
|
||||
test('Handles nested switch routers', () => {
|
||||
const AScreen = () => <div />;
|
||||
const DocsNavigator = () => <div />;
|
||||
DocsNavigator.router = SwitchRouter({
|
||||
A: AScreen,
|
||||
B: AScreen,
|
||||
C: AScreen,
|
||||
});
|
||||
DocsNavigator.path = 'docs';
|
||||
const router = SwitchRouter({
|
||||
Docs: DocsNavigator,
|
||||
D: AScreen,
|
||||
});
|
||||
const action = router.getActionForPathAndParams('docs/B', {});
|
||||
|
||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
||||
expect(action.routeName).toEqual('Docs');
|
||||
expect(action.action.type).toEqual(NavigationActions.NAVIGATE);
|
||||
expect(action.action.routeName).toEqual('B');
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint react/no-multi-comp:0 */
|
||||
/* eslint react/no-multi-comp:0, react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import StackRouter from '../StackRouter';
|
||||
import TabRouter from '../TabRouter';
|
||||
import SwitchRouter from '../SwitchRouter';
|
||||
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -16,6 +16,7 @@ beforeEach(() => {
|
||||
const ROUTERS = {
|
||||
TabRouter,
|
||||
StackRouter,
|
||||
SwitchRouter,
|
||||
};
|
||||
|
||||
const dummyEventSubscriber = (name, handler) => ({
|
||||
@@ -26,7 +27,7 @@ Object.keys(ROUTERS).forEach(routerName => {
|
||||
const Router = ROUTERS[routerName];
|
||||
|
||||
describe(`General router features - ${routerName}`, () => {
|
||||
test('title is configurable using navigationOptions and getScreenOptions', () => {
|
||||
test(`title is configurable using navigationOptions and getScreenOptions - ${routerName}`, () => {
|
||||
class FooView extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
@@ -58,38 +59,132 @@ Object.keys(ROUTERS).forEach(routerName => {
|
||||
];
|
||||
expect(
|
||||
router.getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[0],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual(undefined);
|
||||
expect(
|
||||
router.getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[1],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('BarTitle');
|
||||
expect(
|
||||
router.getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[2],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('Baz-123');
|
||||
});
|
||||
|
||||
test(`set params works in ${routerName}`, () => {
|
||||
class FooView extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
const router = Router({
|
||||
Foo: { screen: FooView },
|
||||
Bar: { screen: FooView },
|
||||
});
|
||||
|
||||
const initState = router.getStateForAction(NavigationActions.init());
|
||||
const initRoute = initState.routes[initState.index];
|
||||
expect(initRoute.params).toEqual(undefined);
|
||||
|
||||
const state0 = router.getStateForAction(
|
||||
NavigationActions.setParams({
|
||||
params: { foo: 42 },
|
||||
key: initRoute.key,
|
||||
}),
|
||||
initState
|
||||
);
|
||||
expect(state0.routes[state0.index].params.foo).toEqual(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Nested navigate behavior test', () => {
|
||||
const Leaf = () => <div />;
|
||||
|
||||
const First = () => <div />;
|
||||
First.router = StackRouter({
|
||||
First1: Leaf,
|
||||
First2: Leaf,
|
||||
});
|
||||
|
||||
const Second = () => <div />;
|
||||
Second.router = StackRouter({
|
||||
Second1: Leaf,
|
||||
Second2: Leaf,
|
||||
});
|
||||
|
||||
const Main = () => <div />;
|
||||
Main.router = StackRouter({
|
||||
First,
|
||||
Second,
|
||||
});
|
||||
const TestRouter = SwitchRouter({
|
||||
Login: Leaf,
|
||||
Main,
|
||||
});
|
||||
|
||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||
|
||||
const state2 = TestRouter.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'First' },
|
||||
state1
|
||||
);
|
||||
expect(state2.index).toEqual(1);
|
||||
expect(state2.routes[1].index).toEqual(0);
|
||||
expect(state2.routes[1].routes[0].index).toEqual(0);
|
||||
|
||||
const state3 = TestRouter.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'Second2' },
|
||||
state2
|
||||
);
|
||||
expect(state3.index).toEqual(1);
|
||||
expect(state3.routes[1].index).toEqual(1); // second
|
||||
expect(state3.routes[1].routes[1].index).toEqual(1); //second.second2
|
||||
|
||||
const state4 = TestRouter.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'First',
|
||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First2' },
|
||||
},
|
||||
state3,
|
||||
true
|
||||
);
|
||||
expect(state4.index).toEqual(1); // main
|
||||
expect(state4.routes[1].index).toEqual(0); // first
|
||||
expect(state4.routes[1].routes[0].index).toEqual(1); // first2
|
||||
|
||||
const state5 = TestRouter.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'First',
|
||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First1' },
|
||||
},
|
||||
state3 // second.second2 is active on state3
|
||||
);
|
||||
expect(state5.index).toEqual(1); // main
|
||||
expect(state5.routes[1].index).toEqual(0); // first
|
||||
expect(state5.routes[1].routes[0].index).toEqual(0); // first.first1
|
||||
});
|
||||
|
||||
test('Handles no-op actions with tabs within stack router', () => {
|
||||
const BarView = () => <div />;
|
||||
const FooTabNavigator = () => <div />;
|
||||
@@ -135,7 +230,7 @@ test('Handles deep action', () => {
|
||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||
const expectedState = {
|
||||
index: 0,
|
||||
transitioningFromKey: false,
|
||||
isTransitioning: false,
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
@@ -158,6 +253,70 @@ test('Handles deep action', () => {
|
||||
expect(state2 && state2.routes[1].index).toEqual(1);
|
||||
});
|
||||
|
||||
test('Handles the navigate action with params', () => {
|
||||
const FooTabNavigator = () => <div />;
|
||||
FooTabNavigator.router = TabRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
Boo: { screen: () => <div /> },
|
||||
});
|
||||
|
||||
const TestRouter = StackRouter({
|
||||
Foo: { screen: () => <div /> },
|
||||
Bar: { screen: FooTabNavigator },
|
||||
});
|
||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = TestRouter.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
immediate: true,
|
||||
routeName: 'Bar',
|
||||
params: { foo: '42' },
|
||||
},
|
||||
state
|
||||
);
|
||||
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
|
||||
expect(state2 && state2.routes[1].routes).toEqual([
|
||||
{
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
params: { foo: '42' },
|
||||
},
|
||||
{
|
||||
key: 'Boo',
|
||||
routeName: 'Boo',
|
||||
params: { foo: '42' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Handles the setParams action', () => {
|
||||
const FooTabNavigator = () => <div />;
|
||||
FooTabNavigator.router = TabRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
});
|
||||
const TestRouter = StackRouter({
|
||||
Foo: { screen: FooTabNavigator },
|
||||
Bar: { screen: () => <div /> },
|
||||
});
|
||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = TestRouter.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.SET_PARAMS,
|
||||
params: { name: 'foobar' },
|
||||
key: 'Baz',
|
||||
},
|
||||
state
|
||||
);
|
||||
expect(state2 && state2.index).toEqual(0);
|
||||
expect(state2 && state2.routes[0].routes).toEqual([
|
||||
{
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
params: { name: 'foobar' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Supports lazily-evaluated getScreen', () => {
|
||||
const BarView = () => <div />;
|
||||
const FooTabNavigator = () => <div />;
|
||||
@@ -250,3 +409,62 @@ test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION
|
||||
expect(stateAfterCompleteTransition.index).toEqual(1);
|
||||
expect(stateAfterSetParams.index).toEqual(1);
|
||||
});
|
||||
|
||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
||||
const PlainScreen = () => <div />;
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
ScreenB.router = StackRouter({
|
||||
Baz: { screen: PlainScreen },
|
||||
Zoo: { screen: PlainScreen },
|
||||
});
|
||||
ScreenA.router = StackRouter({
|
||||
Bar: { screen: PlainScreen },
|
||||
Boo: { screen: ScreenB },
|
||||
});
|
||||
const TestRouter = TabRouter({
|
||||
Foo: { screen: ScreenA },
|
||||
});
|
||||
const screenApreState = {
|
||||
index: 0,
|
||||
key: 'Init',
|
||||
isTransitioning: false,
|
||||
routeName: 'Foo',
|
||||
routes: [{ key: 'Init', routeName: 'Bar' }],
|
||||
};
|
||||
const preState = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
routes: [screenApreState],
|
||||
};
|
||||
|
||||
const comparable = state => {
|
||||
let result = {};
|
||||
if (typeof state.routeName === 'string') {
|
||||
result = { ...result, routeName: state.routeName };
|
||||
}
|
||||
if (state.routes instanceof Array) {
|
||||
result = {
|
||||
...result,
|
||||
routes: state.routes.map(comparable),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const action = NavigationActions.navigate({
|
||||
routeName: 'Boo',
|
||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
||||
});
|
||||
|
||||
const expectedState = ScreenA.router.getStateForAction(
|
||||
action,
|
||||
screenApreState
|
||||
);
|
||||
const state = TestRouter.getStateForAction(action, preState);
|
||||
const innerState = state ? state.routes[0] : state;
|
||||
|
||||
expect(expectedState && comparable(expectedState)).toEqual(
|
||||
innerState && comparable(innerState)
|
||||
);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
202
src/routers/__tests__/SwitchRouter-test.js
Normal file
202
src/routers/__tests__/SwitchRouter-test.js
Normal file
@@ -0,0 +1,202 @@
|
||||
/* eslint react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import SwitchRouter from '../SwitchRouter';
|
||||
import StackRouter from '../StackRouter';
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
|
||||
describe('SwitchRouter', () => {
|
||||
test('resets the route when unfocusing a tab by default', () => {
|
||||
const router = getExampleRouter();
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
||||
state
|
||||
);
|
||||
expect(state2.routes[0].index).toEqual(1);
|
||||
expect(state2.routes[0].routes.length).toEqual(2);
|
||||
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||
state2
|
||||
);
|
||||
|
||||
expect(state3.routes[0].index).toEqual(0);
|
||||
expect(state3.routes[0].routes.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('does not reset the route on unfocus if resetOnBlur is false', () => {
|
||||
const router = getExampleRouter({ resetOnBlur: false });
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
||||
state
|
||||
);
|
||||
expect(state2.routes[0].index).toEqual(1);
|
||||
expect(state2.routes[0].routes.length).toEqual(2);
|
||||
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||
state2
|
||||
);
|
||||
|
||||
expect(state3.routes[0].index).toEqual(1);
|
||||
expect(state3.routes[0].routes.length).toEqual(2);
|
||||
});
|
||||
|
||||
test('ignores back by default', () => {
|
||||
const router = getExampleRouter();
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||
state
|
||||
);
|
||||
expect(state2.index).toEqual(1);
|
||||
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.BACK },
|
||||
state2
|
||||
);
|
||||
|
||||
expect(state3.index).toEqual(1);
|
||||
});
|
||||
|
||||
test('handles back if given a backBehavior', () => {
|
||||
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||
state
|
||||
);
|
||||
expect(state2.index).toEqual(1);
|
||||
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.BACK },
|
||||
state2
|
||||
);
|
||||
|
||||
expect(state3.index).toEqual(0);
|
||||
});
|
||||
|
||||
test('order of handling navigate action is correct for nested switchrouters', () => {
|
||||
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
||||
// if we are focused on Other and navigate to Bar, what should happen?
|
||||
|
||||
const Screen = () => <div />;
|
||||
const NestedSwitch = () => <div />;
|
||||
const OtherNestedSwitch = () => <div />;
|
||||
|
||||
let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
|
||||
let otherNestedRouter = SwitchRouter({ Foo: Screen });
|
||||
NestedSwitch.router = nestedRouter;
|
||||
OtherNestedSwitch.router = otherNestedRouter;
|
||||
|
||||
let router = SwitchRouter(
|
||||
{
|
||||
NestedSwitch,
|
||||
OtherNestedSwitch,
|
||||
Bar: Screen,
|
||||
},
|
||||
{
|
||||
initialRouteName: 'OtherNestedSwitch',
|
||||
}
|
||||
);
|
||||
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
|
||||
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
},
|
||||
state
|
||||
);
|
||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
||||
|
||||
const state3 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'NestedSwitch',
|
||||
},
|
||||
state2
|
||||
);
|
||||
const state4 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
},
|
||||
state3
|
||||
);
|
||||
let activeState4 = state4.routes[state4.index];
|
||||
expect(activeState4.routeName).toEqual('NestedSwitch');
|
||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
||||
});
|
||||
|
||||
// https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
|
||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
||||
const Screen = () => <div />;
|
||||
const MainStack = () => <div />;
|
||||
const LoginStack = () => <div />;
|
||||
MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
|
||||
LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
|
||||
|
||||
let router = SwitchRouter(
|
||||
{
|
||||
Home: Screen,
|
||||
Login: LoginStack,
|
||||
Main: MainStack,
|
||||
},
|
||||
{
|
||||
initialRouteName: 'Login',
|
||||
}
|
||||
);
|
||||
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
expect(state.routes[state.index].routeName).toEqual('Login');
|
||||
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Home',
|
||||
},
|
||||
state
|
||||
);
|
||||
expect(state2.routes[state2.index].routeName).toEqual('Home');
|
||||
});
|
||||
});
|
||||
|
||||
const getExampleRouter = (config = {}) => {
|
||||
const PlainScreen = () => <div />;
|
||||
const StackA = () => <div />;
|
||||
const StackB = () => <div />;
|
||||
|
||||
StackA.router = StackRouter({
|
||||
A1: PlainScreen,
|
||||
A2: PlainScreen,
|
||||
});
|
||||
|
||||
StackB.router = StackRouter({
|
||||
B1: PlainScreen,
|
||||
B2: PlainScreen,
|
||||
});
|
||||
|
||||
const router = SwitchRouter(
|
||||
{
|
||||
A: {
|
||||
screen: StackA,
|
||||
path: '',
|
||||
},
|
||||
B: {
|
||||
screen: StackB,
|
||||
path: 'great/path',
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'A',
|
||||
...config,
|
||||
}
|
||||
);
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import React from 'react';
|
||||
import TabRouter from '../TabRouter';
|
||||
import StackRouter from '../StackRouter';
|
||||
|
||||
import StackActions from '../../routers/StackActions';
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
|
||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
||||
@@ -140,6 +140,46 @@ describe('TabRouter', () => {
|
||||
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
||||
});
|
||||
|
||||
test('Handles the SetParams action for inactive routes', () => {
|
||||
const router = TabRouter(
|
||||
{
|
||||
Foo: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
Bar: {
|
||||
screen: () => <div />,
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'Bar',
|
||||
}
|
||||
);
|
||||
const initialState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
key: 'RouteA',
|
||||
routeName: 'Foo',
|
||||
params: { name: 'InitialParam', other: 'Unchanged' },
|
||||
},
|
||||
{ key: 'RouteB', routeName: 'Bar', params: {} },
|
||||
],
|
||||
};
|
||||
const state = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.SET_PARAMS,
|
||||
params: { name: 'NewParam' },
|
||||
key: 'RouteA',
|
||||
},
|
||||
initialState
|
||||
);
|
||||
expect(state.index).toEqual(1);
|
||||
expect(state.routes[0].params).toEqual({
|
||||
name: 'NewParam',
|
||||
other: 'Unchanged',
|
||||
});
|
||||
});
|
||||
|
||||
test('getStateForAction returns null when navigating to same tab', () => {
|
||||
const router = TabRouter(
|
||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
||||
@@ -181,6 +221,7 @@ describe('TabRouter', () => {
|
||||
const navAction = {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Baz',
|
||||
params: { foo: '42' },
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
@@ -351,7 +392,7 @@ describe('TabRouter', () => {
|
||||
});
|
||||
const MidNavigator = () => <div />;
|
||||
MidNavigator.router = TabRouter({
|
||||
Foo: { screen: ChildNavigator0 },
|
||||
Fee: { screen: ChildNavigator0 },
|
||||
Bar: { screen: ChildNavigator1 },
|
||||
});
|
||||
const router = TabRouter({
|
||||
@@ -371,8 +412,8 @@ describe('TabRouter', () => {
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
@@ -410,8 +451,8 @@ describe('TabRouter', () => {
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
@@ -444,7 +485,10 @@ describe('TabRouter', () => {
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
action: { type: NavigationActions.NAVIGATE, routeName: 'Zap' },
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Zap',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(state4).toEqual({
|
||||
@@ -459,8 +503,8 @@ describe('TabRouter', () => {
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
@@ -484,7 +528,7 @@ describe('TabRouter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Handles path configuration', () => {
|
||||
test.only('Handles path configuration', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
const router = TabRouter({
|
||||
@@ -493,14 +537,17 @@ describe('TabRouter', () => {
|
||||
screen: ScreenA,
|
||||
},
|
||||
Bar: {
|
||||
path: 'b',
|
||||
path: 'b/:great',
|
||||
screen: ScreenB,
|
||||
},
|
||||
});
|
||||
const params = { foo: '42' };
|
||||
const action = router.getActionForPathAndParams('b/anything', params);
|
||||
const expectedAction = {
|
||||
params,
|
||||
params: {
|
||||
foo: '42',
|
||||
great: 'anything',
|
||||
},
|
||||
routeName: 'Bar',
|
||||
type: NavigationActions.NAVIGATE,
|
||||
};
|
||||
@@ -521,15 +568,21 @@ describe('TabRouter', () => {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar', params },
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{
|
||||
key: 'Bar',
|
||||
routeName: 'Bar',
|
||||
params: { foo: '42', great: 'anything' },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(state2).toEqual(expectedState2);
|
||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||
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', () => {
|
||||
@@ -690,56 +743,15 @@ describe('TabRouter', () => {
|
||||
expect(state2).toEqual(state0);
|
||||
});
|
||||
|
||||
test('pop action works as expected', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
bar: { screen: () => <div /> },
|
||||
});
|
||||
|
||||
const state = {
|
||||
index: 3,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'A', routeName: 'foo' },
|
||||
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
||||
{ key: 'C', routeName: 'foo' },
|
||||
{ key: 'D', routeName: 'bar' },
|
||||
],
|
||||
};
|
||||
const poppedState = TestRouter.getStateForAction(
|
||||
NavigationActions.pop(),
|
||||
state
|
||||
);
|
||||
expect(poppedState.routes.length).toBe(3);
|
||||
expect(poppedState.index).toBe(2);
|
||||
expect(poppedState.isTransitioning).toBe(true);
|
||||
|
||||
const poppedState2 = TestRouter.getStateForAction(
|
||||
NavigationActions.pop({ n: 2, immediate: true }),
|
||||
state
|
||||
);
|
||||
expect(poppedState2.routes.length).toBe(2);
|
||||
expect(poppedState2.index).toBe(1);
|
||||
expect(poppedState2.isTransitioning).toBe(false);
|
||||
|
||||
const poppedState3 = TestRouter.getStateForAction(
|
||||
NavigationActions.pop({ n: 5 }),
|
||||
state
|
||||
);
|
||||
expect(poppedState3.routes.length).toBe(1);
|
||||
expect(poppedState3.index).toBe(0);
|
||||
expect(poppedState3.isTransitioning).toBe(true);
|
||||
});
|
||||
|
||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
||||
const PlainScreen = () => <div />;
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
ScreenB.router = StackRouter({
|
||||
ScreenB.router = TabRouter({
|
||||
Baz: { screen: PlainScreen },
|
||||
Zoo: { screen: PlainScreen },
|
||||
});
|
||||
ScreenA.router = StackRouter({
|
||||
ScreenA.router = TabRouter({
|
||||
Bar: { screen: PlainScreen },
|
||||
Boo: { screen: ScreenB },
|
||||
});
|
||||
@@ -748,10 +760,10 @@ describe('TabRouter', () => {
|
||||
});
|
||||
const screenApreState = {
|
||||
index: 0,
|
||||
key: 'Init',
|
||||
key: 'Foo',
|
||||
isTransitioning: false,
|
||||
routeName: 'Foo',
|
||||
routes: [{ key: 'Init', routeName: 'Bar' }],
|
||||
routes: [{ key: 'Bar', routeName: 'Bar' }],
|
||||
};
|
||||
const preState = {
|
||||
index: 0,
|
||||
@@ -777,7 +789,6 @@ describe('TabRouter', () => {
|
||||
routeName: 'Boo',
|
||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
||||
});
|
||||
|
||||
const expectedState = ScreenA.router.getStateForAction(
|
||||
action,
|
||||
screenApreState
|
||||
@@ -785,8 +796,25 @@ describe('TabRouter', () => {
|
||||
const state = router.getStateForAction(action, preState);
|
||||
const innerState = state ? state.routes[0] : state;
|
||||
|
||||
expect(innerState.routes[1].index).toEqual(1);
|
||||
expect(expectedState && comparable(expectedState)).toEqual(
|
||||
innerState && comparable(innerState)
|
||||
);
|
||||
|
||||
const noMatchAction = NavigationActions.navigate({
|
||||
routeName: 'Qux',
|
||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
||||
});
|
||||
const expectedState2 = ScreenA.router.getStateForAction(
|
||||
noMatchAction,
|
||||
screenApreState
|
||||
);
|
||||
const state2 = router.getStateForAction(noMatchAction, preState);
|
||||
const innerState2 = state2 ? state2.routes[0] : state2;
|
||||
|
||||
expect(innerState2.routes[1].index).toEqual(0);
|
||||
expect(expectedState2 && comparable(expectedState2)).toEqual(
|
||||
innerState2 && comparable(innerState2)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Component } from 'react';
|
||||
import createConfigGetter from '../createConfigGetter';
|
||||
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||
|
||||
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
||||
remove: () => {},
|
||||
@@ -67,81 +66,81 @@ test('should get config for screen', () => {
|
||||
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[0],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('Welcome anonymous');
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[1],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('Welcome jane');
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[0],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).gesturesEnabled
|
||||
).toEqual(true);
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[2],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('Settings!!!');
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[2],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).gesturesEnabled
|
||||
).toEqual(false);
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[3],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).title
|
||||
).toEqual('10 new notifications');
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[3],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).gesturesEnabled
|
||||
).toEqual(true);
|
||||
expect(
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[4],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
).gesturesEnabled
|
||||
).toEqual(false);
|
||||
@@ -164,11 +163,11 @@ test('should throw if the route does not exist', () => {
|
||||
|
||||
expect(() =>
|
||||
getScreenOptions(
|
||||
addNavigationHelpers({
|
||||
{
|
||||
state: routes[0],
|
||||
dispatch: () => false,
|
||||
addListener: dummyEventSubscriber,
|
||||
}),
|
||||
},
|
||||
{}
|
||||
)
|
||||
).toThrowError(
|
||||
|
||||
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' });
|
||||
});
|
||||
@@ -38,7 +38,8 @@ export default (routeConfigs, navigatorScreenConfig) => (
|
||||
|
||||
const routeConfig = routeConfigs[route.routeName];
|
||||
|
||||
const routeScreenConfig = routeConfig.navigationOptions;
|
||||
const routeScreenConfig =
|
||||
routeConfig === Component ? null : routeConfig.navigationOptions;
|
||||
const componentScreenConfig = Component.navigationOptions;
|
||||
|
||||
const configOptions = { navigation, screenProps: screenProps || {} };
|
||||
|
||||
46
src/routers/getNavigationActionCreators.js
Normal file
46
src/routers/getNavigationActionCreators.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import invariant from '../utils/invariant';
|
||||
|
||||
const getNavigationActionCreators = route => {
|
||||
return {
|
||||
goBack: key => {
|
||||
let actualizedKey = key;
|
||||
if (key === undefined && route.key) {
|
||||
invariant(typeof route.key === 'string', 'key should be a string');
|
||||
actualizedKey = route.key;
|
||||
}
|
||||
return NavigationActions.back({ key: actualizedKey });
|
||||
},
|
||||
navigate: (navigateTo, params, action) => {
|
||||
if (typeof navigateTo === 'string') {
|
||||
return NavigationActions.navigate({
|
||||
routeName: navigateTo,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
invariant(
|
||||
typeof navigateTo === 'object',
|
||||
'Must navigateTo an object or a string'
|
||||
);
|
||||
invariant(
|
||||
params == null,
|
||||
'Params must not be provided to .navigate() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
action == null,
|
||||
'Child action must not be provided to .navigate() when specifying an object'
|
||||
);
|
||||
return NavigationActions.navigate(navigateTo);
|
||||
},
|
||||
setParams: params => {
|
||||
invariant(
|
||||
route.key && typeof route.key === 'string',
|
||||
'setParams cannot be called by root navigator'
|
||||
);
|
||||
return NavigationActions.setParams({ params, key: route.key });
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default getNavigationActionCreators;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user