Compare commits

..

234 Commits
1.0.0 ... 2.0.2

Author SHA1 Message Date
Brent Vatne
d168ab26f9 Release 2.0.2 2018-05-21 17:56:24 -07:00
Brent Vatne
99916328a1 Don't warn about multiple navigation containers on Android
This really is not ideal but it's an upstream bug on react-native Android
that we can't work around. Users may experience other unexpected behavior as a
result of this upstream bug.
2018-05-21 17:55:05 -07:00
Eric Vicenti
08e1b53f2e Update playground yarn lockfile 2018-05-12 10:48:45 -07:00
Eric Vicenti
2243528e97 Cache panResponder in StackView (#4183)
* fix swipe gesture bug

* panresponder as instance prop
2018-05-09 17:36:57 -07:00
Steven Kabbes
931271198b Remove unused code addNavigationHelpers (#4179) 2018-05-09 14:53:06 -07:00
Brent Vatne
1876706bad Release 2.0.1 2018-05-08 17:45:02 -07:00
Eric Vicenti
e97d6b26a8 Fix dispatch implementation (#4168)
Previously we had been emiting an action event with a null state when the router handles the action by returning null from getStateForAction.

- Fixes the case of null state getting emitted
- Renames a few things for clarity
- Refactors conditionals to read easier
- Comments to explain intent
2018-05-08 17:41:08 -07:00
Brent Vatne
ec1694f909 Release 2.0.0 2018-05-07 13:47:13 -07:00
Brent Vatne
93db7d0c95 Release 2.0.0-rc.14 2018-05-07 13:03:21 -07:00
Brent Vatne
589b80b2fa Update drawer example 2018-05-07 13:01:22 -07:00
Eric Vicenti
364144d639 Navigation Action Helpers at root (#4151) 2018-05-07 12:56:35 -07:00
Brent Vatne
2fd7284fe2 Release candidate lucky number 13 2018-05-07 11:54:50 -07:00
Brent Vatne
6499f02157 Only blur input when index changes on transition start (Fixes #4148) 2018-05-07 11:47:56 -07:00
Brent Vatne
39d5714ac0 Release 2.0.0-rc.12 2018-05-07 11:14:03 -07:00
Brent Vatne
1ceda5f04d Update react-navigation-deprecated-tab-navigator. Closes #4147 2018-05-07 11:13:34 -07:00
Brent Vatne
4533883ba7 Release 2.0.0-rc.11 2018-05-06 13:41:53 -07:00
spaceye
8ec2466fef Fix for broken swipe back gesture in RTL mode (#4119)
* 3127: Fixed gestureDirectionInverted variable declaration and condition in onPanResponderMove method in regards to swipe back action in RTL mode.

* - 4118: Put axis check first;
- Fixed default BackButton mask in RTL mode.
2018-05-06 12:42:22 -07:00
Brent Vatne
1bd6593ede Release 2.0.0-rc.10 2018-05-06 12:00:02 -07:00
Eric Vicenti
4e8d8ce12f Fixes to drawer router handling of child routers (#4131)
Addresses #4129, and also lets child routers swallow actions with null. Tests added
2018-05-06 11:59:40 -07:00
Eric Vicenti
9f95a7f10b Fix CompletionAction trigger (#4115) 2018-05-06 11:57:51 -07:00
Serge Lebedev
f6bd3e4306 Fix setting undefined value to isTransitioning (#4124) 2018-05-06 10:04:57 -07:00
Eric Vicenti
c1a94895f5 Fix drawer stack reset-to-top behavior (#4132)
“reset” is not a NavigationAction anymore.

The correct fix, for v3, to this is to navigate to the first screen inside the stack. With the less-pushy navigate behavior in v2, this will result in the first route getting selected if you specify it by routeName.
2018-05-06 09:51:18 -07:00
Brent Vatne
ad7cde9eb9 Release 2.0.0-rc.9 2018-05-04 11:16:34 -07:00
Brent Vatne
2643f690a9 Pull createMaterialBottomTabNavigator into separate repo 2018-05-04 11:06:51 -07:00
Brent Vatne
8e52995ef3 Release 2.0.0-rc.8 2018-05-04 10:14:34 -07:00
Eric Vicenti
8ed3817c90 Fix event unsubscription logic (#4116)
Events were getting unsubscribed too early, so that the inner willBlur event was getting skipped when trying to “dismiss” a deep navigation stack.
2018-05-04 10:14:22 -07:00
Brent Vatne
eda9bfd567 Update react-lifecycles-compat 2018-05-04 10:10:28 -07:00
Eric Vicenti
723c5f2149 Fix drawer navigation dispatch (#4121) 2018-05-04 09:48:45 -07:00
Brent Vatne
ab5481a290 Add context around a test in switchrouter 2018-05-03 15:43:37 -07:00
Brent Vatne
df281cfed0 Add router tests to clarify route resolution 2018-05-03 15:42:23 -07:00
Eric Vicenti
e0df3cf74a Fix issue with nested navigation actions in stack (#4114) 2018-05-03 10:13:43 -07:00
Vojtech Novak
2440af66e4 add a test so that #2856 can be closed (#4102)
* add test for 2856

* rename variables
2018-05-02 20:02:03 -07:00
simonbuerger
47fe858d4e StateUtils: Use Array.prototype.findIndex instead of map => indexOf (#4106)
* Use Array.prototype.findIndex instead of map => indexOf

Creating a new array and iterating over all the routes is inefficient compared to findIndex, which does not create a new array and exits as soon as it finds a match. Since the indexOf method is used extensively this should provide a minor performance improvement

* reverted yarn.lock edits
2018-05-02 20:01:20 -07:00
Eric Vicenti
c641bee11b Fix initial action dispatch for nonPersisted apps (#4104) 2018-05-02 09:17:00 -07:00
Brent Vatne
4b39e2db3c Release 2.0.0-rc.7 2018-04-30 17:08:23 -07:00
Eric Vicenti
f7533a790f Add tests to confirm setParam behavior (#4099)
Adding these tests because reports from this issue make me nervous:

https://github.com/react-navigation/react-navigation/issues/1274
2018-04-30 16:37:41 -07:00
Brent Vatne
c56122466f Release 2.0.0-rc.6 2018-04-30 16:19:28 -07:00
Brent Vatne
7fc992dc58 Container state can be out of date if we have async functions between getting it and using it (#4098) 2018-04-30 15:59:49 -07:00
Rob Allsopp
32922cdd7d go back to React$, and fix real error (#4095) 2018-04-30 12:04:48 -07:00
Rob Allsopp
eda51b3b79 Fix missing/incorrect flow types (#4085)
* don’t use private global

* add missing method ‘getParam’ to navigation screen prop

* correct return type for ‘withNavigation’

* add usage of `getParam` method
2018-04-27 14:30:23 -07:00
Jed Mao
921ee09587 Fix "npm test" on Windows 10 (#4066)
* Fix Windows

* update prettier

* Explain need for assetsTransformer in comment
2018-04-27 08:57:07 -07:00
Brent Vatne
7ae4c60eb8 Release 2.0.0-rc.5 2018-04-25 17:49:17 -07:00
Brent Vatne
5fff7ef5c6 Give inactive routes in stack opportunity to handle action (#4064) 2018-04-26 00:48:55 +00:00
Brent Vatne
42bb1cc317 Release 2.0.0-rc.4 2018-04-25 17:04:15 -07:00
Brent Vatne
337fd89ad5 Bump react-navigation-deprecated-tab-navigator 2018-04-25 16:57:42 -07:00
Brent Vatne
acf9b92ff7 Bump react-navigation-tabs 2018-04-25 16:49:57 -07:00
Rich Gilbank
5072130d6f Typo in deprecation notice (#4051) 2018-04-24 11:10:48 -07:00
Brent Vatne
20bbbd62ff Release 2.0.0-rc.3 2018-04-20 17:40:02 +03:00
Adam Miskiewicz
0890896824 Make StackNavigator keyboard aware (#3951)
* Make StackNavigator keyboard aware

One thing that has always annoyed me in React Navigation is the handling of the keyboard. When a keyboard is visible on screen and a navigation action occurs (either by tapping a button or using a gesture), the keyboard tends to stay on screen until the transition completes. This feels janky and broken. On native iOS, for instance, the keyboard hides immediately when the navigation starts, and if the transition is cancelled (say, when the user releases the gesture), the keyboard reappears.

This PR introduces a "KeyboardAwareNavigator" higher order component that is enabled on the StackNavigator, unless a `disableKeyboardHandling` prop is passed into the StackNavigator's configuration.

* Set status bar in keyboard handling example

* Call gesture props in keyboard aware navigator if available

* Fix formatting
2018-04-20 17:03:25 +03:00
Serhii Palash
0cf14f8e1e Fix TabNavigator export ( Issue #3962 ) (#3979) 2018-04-20 16:40:18 +03:00
Brent Vatne
e5e434c9e2 Fix _isRouteFocused so it takes a route and returns bool rather than fn 2018-04-20 16:39:03 +03:00
Janic Duplessis
e5d8d2c216 Fix header hardcoded height to accound for iPhone X and orientation changes (#4017) 2018-04-20 16:34:33 +03:00
Eric Vicenti
abd5200739 Fix header ModularLeftComponent to goBack from child navigation (#4023) 2018-04-20 16:33:36 +03:00
Brent Vatne
202609d9cf Release 2.0.0-rc.2 2018-04-09 17:26:32 -07:00
Yao Hui Chua
7b4dd98255 Shift tests to isolate routers (#3876) 2018-04-09 13:00:01 -07:00
Janic Duplessis
70c644f522 Fix transition between 2 screens with no header (#3939) 2018-04-09 12:32:27 -07:00
Janic Duplessis
5274d16e3b Use Header.HEIGHT instead of measuring to avoid flicker (#3940) 2018-04-09 12:31:43 -07:00
Janic Duplessis
e5e2cbb86d Fix header transition when mode is set to screen (#3927) 2018-04-09 11:22:51 -07:00
Brent Vatne
a8caa0d93c Release 2.0.0-rc.1 2018-04-06 15:14:32 -07:00
Eric Vicenti
f70a25a6a8 drawer router key (#3925) 2018-04-06 15:13:55 -07:00
emision
6cde6e2558 add isFocused helper definition (#3912) 2018-04-06 12:43:51 -07:00
Brent Vatne
0794c0faaa Fix typo in SwitchRouter 2018-04-06 12:42:42 -07:00
Brent Vatne
ea28e84e5a Fix stackConfig typo 2018-04-06 12:39:44 -07:00
Eric Vicenti
419ee5318d custom action creators api (#3923) 2018-04-06 12:12:39 -07:00
Brent Vatne
fbbf00875b Release 2.0.0-beta.10 2018-04-05 14:46:33 -07:00
Brent Vatne
22e09f7186 Prevent double application of navigationOptions function when using RouteName: ScreenName route config 2018-04-05 14:45:41 -07:00
Brent Vatne
ece6297e8e Release 2.0.0-beta.9 2018-04-05 12:09:55 -07:00
Eric Vicenti
ad52caf57b Immediate transition fix, avoid stale render (#3901) 2018-04-05 11:35:16 -07:00
Eric Vicenti
11f5e6e8e5 container initialization fix (#3899) 2018-04-05 11:29:59 -07:00
Eric Vicenti
1764b21f34 fix header back (#3900) 2018-04-05 11:29:15 -07:00
Eric Vicenti
bbacabeba3 avoid unnecessary navigation completion dispatches (#3902) 2018-04-05 11:27:32 -07:00
Eric Vicenti
b140b70555 no more component will mount (#3903) 2018-04-05 11:25:26 -07:00
Eric Vicenti
356646cbfa Enhance replace action creator (#3906) 2018-04-05 11:25:04 -07:00
Brent Vatne
6234b5661e Release 2.0.0-beta.8 2018-04-04 19:34:09 -07:00
Brent Vatne
270c3f0754 Bump react-navigation-tabs dependency 2018-04-04 19:33:51 -07:00
Brent Vatne
6f04bdffa6 Update NavigationPlayground dependencies 2018-04-04 19:33:01 -07:00
Brent Vatne
8415378784 Release 2.0.0-beta.7 2018-04-04 18:37:41 -07:00
Brent Vatne
9e47092d56 Fix isFocused and example 2018-04-04 18:37:08 -07:00
Brent Vatne
029d6ac4d2 Update react-navigation-tabs 2018-04-04 18:17:57 -07:00
Gaëtan Renaudeau
d57d118fda Update Flowtype to support navigation.navigate({}) (#3843) 2018-03-26 17:17:36 -04:00
Brent Vatne
2232e394bb Fix refactoring errors 2018-03-25 19:04:15 -07:00
Eric Vicenti
670d48366b Actions creators overhaul (#3619) 2018-03-25 18:31:59 -07:00
Brent Vatne
99ac5b6c08 Release 2.0.0-beta.6 2018-03-25 12:33:30 -07:00
Brent Vatne
68a2a106f3 Warn when users have multiple stateful navigation containers (#3819)
* First pass at warning when users explicitly render nested navigators

* Clean up tests around warnings

* Update comment

* Update comment again
2018-03-25 12:33:11 -07:00
Gianfrancø Palumbo
b7c6d072a5 fix(redux example readme): link to doc (#3828) 2018-03-25 10:49:22 -07:00
Eric Vicenti
ecd9fd71e9 withNavigation improvement (#3834)
The navigation prop should also pass through, and be prioritized over context because it is more explicit

This also fixes an incorrect warning/invariant
2018-03-25 10:49:09 -07:00
Adam Miskiewicz
cfc9690326 Smoothly transition header visibility in Stack
This closes #2732 (which also happens to be the top issue on canny.io).
2018-03-25 10:27:59 -07:00
Adam Miskiewicz
828e7f2d43 Update react-native-scripts in NavigationPlayground (#3820)
We were pretty far behind in react-native-script versions for the playground, so I updated them.

Test Plan:

Run `yarn`.
2018-03-23 12:25:53 -07:00
Tom Klaver
9c3fffa47f Fix broken link behind PRs Welcome badge (#3824) 2018-03-23 11:23:10 -04:00
Brent Vatne
be524e4224 Release 2.0.0-beta.5
- Update react-navigation-tabs
2018-03-22 20:52:47 -07:00
Brent Vatne
095814230b Release 2.0.0-beta.4 2018-03-22 19:43:47 -07:00
Eric Vicenti
9cf557bba0 State persistence (#3716)
Adds a new state persistence mechanisms to all of the navigators via createNavigationContainer

    There are two new props that you can provide to a navigation container: `storageKey` and `renderLoading`.

    `storageKey` is the string key to be used with AsyncStorage while saving and persisting navigation state. To disable persistence, set to null.

    `renderLoading` allows you to render something while the navigator re-hydrates its state and loads its initial URL. By default this returns null, but Expo users will want to render a <AppLoading /> component for smooth app launches

    There is also functionality in this PR to observe errors that come from re-hydrating state, and gracefully recover by dispatching an init action.

    Also this revises the init action to *reset* the navigation state, rather than preserve the previous state.
2018-03-22 22:42:37 -04:00
Eric Vicenti
5e4512f3eb Implement paths on SwitchRouter (#3806)
* Fix paths overriding in SwitchRouter
2018-03-22 22:41:27 -04:00
Brent Vatne
ee1b5972ce Release 2.0.0-beta.3 2018-03-19 16:53:34 -04:00
Brent Vatne
2233d0e1d8 Add switch example 2018-03-19 16:52:12 -04:00
Eric Vicenti
577d99c165 StackRouter to return null on idempotent navigation (#3793)
This new behavior indicates that the action has been handled, but the state has not changed.
2018-03-19 16:46:57 -04:00
Brent Vatne
aa362ea776 Release 2.0.0-beta.2 2018-03-19 13:06:49 -04:00
Brent Vatne
864908a49c Bump version to 2.0.0-beta.1 2018-03-19 01:36:37 -04:00
Brent Vatne
5cab55b8c9 Release 2.0.0-beta.0 2018-03-19 00:13:27 -04:00
Brent Vatne
9b9db86bde Release 2.0.7 2018-03-18 22:22:26 -04:00
Eric Vicenti
4def39c0f7 Improve path matching for SwitchRouter and empty paths (#3784) 2018-03-18 22:10:15 -04:00
Brent Vatne
e6559f5878 Add react-native-tabs as export from react-navigation 2018-03-18 17:50:02 -04:00
Eric Vicenti
a9d8f2e03e Temporarily disable broken test in NavigationPlayground 2018-03-18 12:22:56 -04:00
Brent Vatne
84a070b9d5 Release 2.0.0-alpha.6 2018-03-17 16:27:52 -04:00
Brent Vatne
ee984943c7 Fix push action -- regressed when making navigate "less pushy" 2018-03-17 16:27:19 -04:00
Brent Vatne
9fdfec18f6 Remove yarn link in test script 2018-03-16 20:08:47 -04:00
Eric Vicenti
aee16b91a4 Improve consistency of jest version
Hopefully will fix build on Circle
2018-03-16 16:21:17 -07:00
Jakob Murko
191439f79a Prevent duplicate drawer events (#3763) 2018-03-16 15:36:31 -07:00
Brent Vatne
b1ac152fec Rename 'carefullyGetParent' to 'dangerouslyGetParent'
- It is potentially dangerous because screen components may or may not have the
expected parent, so depending on anything related to the parent may lead to
bugs. You should use carefully because it is dangerous ;)
2018-03-15 16:01:58 -07:00
Brent Vatne
c588ab9f9d Fix TabNavigator related tests 2018-03-15 13:40:29 -07:00
Brent Vatne
ae8cd41396 Deprecate TabNavigator in favor of react-navigation-tabs 2018-03-15 13:31:41 -07:00
Michał Pierzchała
5038ee2360 Fix memory leak in TabView-test.js (#3742) 2018-03-15 12:25:08 -07:00
Brent Vatne
5bf071e3ee Release 2.0.0-alpha.5 2018-03-15 10:57:33 -07:00
Michał Pierzchała
fcbf78e658 feat(context): refactor passing navigation context (#3668)
* feat(context): refactor passing navigation context

* remove commented code in example

* adjust src/views/withNavigationFocus.js

* refactor stuff

* extract scene to variable

* Add test

* Apply CR comments

* remove junk

* bring back screen mode header
2018-03-15 10:55:00 -07:00
KUBO
fd75e9c14c Remove unuseful SafeAreaView (#3721) 2018-03-15 10:35:02 -07:00
Brent Vatne
7d36a3b137 Update snapshots 2018-03-15 10:34:18 -07:00
Brent Vatne
175387b22f Another fix for frustrating tab bar icon layout issue 2018-03-15 10:34:18 -07:00
Brent Vatne
0dd7daecc0 Rename exports for navigators from XNavigator to createXNavigator (#3753) 2018-03-14 23:23:04 -07:00
Brent Vatne
42230634fd Missed a case where we should not have flexGrow 2018-03-14 21:48:48 -07:00
Brent Vatne
a9943e9b2e Update snapshot 2018-03-14 21:42:39 -07:00
Brent Vatne
6475e32dba Apply horizontal icon style whenever we should use horizontal tabs in icon 2018-03-14 21:34:53 -07:00
Brent Vatne
f67872d8f1 Release 2.0.0-alpha.4 2018-03-14 15:22:14 -07:00
Brent Vatne
2c7187b22a Rename exports for navigators from XNavigator to createXNavigator 2018-03-14 15:21:38 -07:00
Brent Vatne
160d44f58e Fix back button on modular header 2018-03-14 15:19:03 -07:00
spaceye
d017ed01b3 Reworked headerBackImage navigation prop and rendering (#3680)
* Added possibility to provide custom header back button's image, introduced API changes: removed old prop "headerBackImage", added new prop "backImage".

* Code style fixes.

* Renamed showcase file to for more clarity; updated the prop's name as well.

* Removed listeners code from showcase screens.
2018-03-13 13:15:11 -07:00
Vojtech Novak
c2e197f8d3 fix contribution docs link (#3707) 2018-03-13 13:14:02 -07:00
Vojtech Novak
6b3968b601 Playground facelift (#3708)
* wip

* fix imports

* use stylesheet for margin

stylesheet now correct

* also remove old headerLeft

* improve header buttons color

* remove ios button margin
2018-03-13 13:13:19 -07:00
Vojtech Novak
b575200879 reuse styles (#3725) 2018-03-13 13:05:21 -07:00
Brent Vatne
cd5bd8882e Release 2.0.0-alpha.3 2018-03-12 16:19:48 -07:00
Brent Vatne
3f837c895e Add lifecycle polyfill and change away from componentWillMount where possible without too much refactoring 2018-03-12 16:05:43 -07:00
Brent Vatne
bc5d35aba3 Release 2.0.0-alpha.2 2018-03-12 16:01:57 -07:00
Brent Vatne
9a6e0bbd98 Add dismiss helper, made possible by also adding carefullyGetParent (#3669)
* Add dismiss action, made possible by getParentState

* Add dismiss to flow interface

* Don't dispatch an action on dismiss helper if no parent state

* carefullyGetParent instead of getParentState
2018-03-12 16:01:29 -07:00
Brent Vatne
052d22804c Release 2.0.0-alpha.1 2018-03-12 15:23:38 -07:00
Brent Vatne
7a978b1087 Implement "less pushy navigate" RFC 2018-03-12 15:22:51 -07:00
Brent Vatne
b06fb7e432 Release 2.0.0-alpha.0 2018-03-12 15:06:47 -07:00
Brent Vatne
a92ed83302 Fix tab icon height on horizontal / ipad 2018-03-12 15:06:47 -07:00
Brent Vatne
0c31bc44ea Make push, pop, and popToTop bubble like navigate (#3617) 2018-03-12 15:06:38 -07:00
Brent Vatne
8e5ee4d312 Use arrow function for isFocused 2018-03-12 09:56:39 -07:00
Brandon Smith
4bb8987ab7 Pass initialRouteKey into StackRouter (#3540) (#3702) 2018-03-09 11:32:01 -08:00
Brent Vatne
81a86fa091 Swap addListener out for isFocused prop on ResourceSavingSceneView (#3700) 2018-03-09 10:55:00 -08:00
Brent Vatne
47f357f332 Fix TabRouter-test 2018-03-09 10:46:05 -08:00
Brent Vatne
bdda6fa5be Add SwitchNavigator to 2.x 2018-03-07 17:35:15 -08:00
Ashoat Tevosyan
b097136f74 [Flow] Some updates, mostly from flow-typed (#3682)
1. Remove `NavigationComponent` from `NavigationScreenRouteConfig`. The only context `NavigationScreenRouteConfig` is used in is as an intersection with an object, and as such the only relevant portions of `NavigationScreenRouteConfig` are the object parts.
2. Add static `HEIGHT` variable to `Header` type.
3. In `NavigationContainerProps`, make `onNavigationStateChange` property value nullable.

PS: if in the future you guys would prefer that I separate these sort of PRs into their constituent parts, let me know.
2018-03-07 11:29:25 -08:00
Ben Styles
c9b0219cec Allow passing null to onNavigationStateChange prop (#3683)
As seen here: react-navigation/react-navigation#360, we need to be able to pass null to turn off logging.
2018-03-07 14:19:00 -05:00
Eric Vicenti
ac83cf804c Fix issue in drawer actions (#3667)
* Fix issue in drawer actions

* Update DrawerView.js
2018-03-06 14:38:03 -08:00
Sirui Li
cf63521516 Flow type: SafeAreaView doesn't require children (#3670)
`children` prop should be optional.
2018-03-06 14:19:46 -08:00
corupta
7c488c8d49 Add activeLabelStyle and inactiveLabelStyle for DrawerItem (#3559)
* Add activeLabelStyle and inactiveLabelStyle

Adding activeLabelStyle, so that active items can be customized more.
My use case: Change font of the active item to bold.
Also, added inactiveLabelStyle which can be used for a similar purpose.

* prettier fix

* Update react-navigation.js

* prettier fix

* Update jest snapshot for DrawerNavigator - for adding a new style property to the styles array
2018-03-05 17:17:08 -08:00
Edward Drapkin
9005494e64 More specific injected Navigation props (#3645) 2018-03-05 16:33:42 -08:00
Brent Vatne
0daab8c55b Add isFocused helper to navigation and fix withNavigationFocus 2018-03-05 12:28:42 -08:00
Brent Vatne
ae98089337 Cache event subscribers and clean up after unmounting (#3648)
* This caches "child event subscribers" by key and removes them when they're not needed anymore. Once a navigator unmounts we also remove upstream subscribers.

* Fix tests
2018-03-05 11:27:57 -08:00
Yordis Prieto
57e37a8783 Fix typespec of back action creator (#3659) 2018-03-05 11:10:39 -08:00
Ashoat Tevosyan
b7994d28db Fix NavigationEventPayload.lastState type (#3664)
Should be nullable, since it is initially called as `null` in `src/createNavigationContainer.js` (and in `react-navigation-redux-helpers`, where it is causing a Flow error)
2018-03-05 11:04:29 -08:00
Vishwesh Jainkuniya
f1bfdeee46 Fix: tabBar icons are not visible. (#3650)
* Fix: tabBar icons are not visible.

* Fix: tests.
2018-03-04 21:21:54 -08:00
Nicolas Beck
c6301abaed Add initialRouteKey for StackRouter (#3540)
* use initialRouteName as key when initializing StackRouter

* fix null headerLeft

* merge back

* fixed tests

* use config flag

* fixed snapshots

* implemented requested changes
2018-03-02 16:06:27 -08:00
Brent Vatne
4569ad49f9 Fix regression in error message for route config validation 2018-03-02 12:18:11 -08:00
Arseny Yankovsky
214eeb13fb Allow modification of SafeAreaView props (#3496)
* SafeAreaView fix

* Updated to only allow modification of forceInset property of SafeAreaView
2018-03-01 13:42:19 -08:00
Michał Pierzchała
416fe58cab Move contributing guide to CONTRIBUTING.md (#3631) 2018-03-01 10:11:30 -08:00
Eric Vicenti
2e47cbb3cb Drawer Router (#3618) 2018-02-27 18:34:05 -08:00
Brent Vatne
cd99dc8054 Update snapshots 2018-02-27 17:32:13 -08:00
Eric Vicenti
e27ad22c57 [BREAKING] New createNavigator API (#3392)
* New createNavigator and View API

See the RFC here:
https://github.com/react-navigation/rfcs/blob/master/text/0002-navigator-view-api.md

* shattered dreams of flow

* fix export

* Fix tab view issues found by brent
2018-02-27 17:27:58 -08:00
Brent Vatne
6785729fb5 Fix snapshots 2018-02-26 16:02:38 -08:00
Brent Vatne
d3b6e70d16 Clarify that people should not report Redux or MobX related integration issues here 2018-02-26 15:53:14 -08:00
Brent Vatne
5f32a48c16 Release 1.2.1 2018-02-26 14:04:44 -08:00
Brent Vatne
d5a0b5912b Apply StyleSheet.flatten properly to headerStyle. Fixes #3608 2018-02-26 14:04:36 -08:00
Yordis Prieto
13fd8acb20 fix typespec (#3583) 2018-02-26 12:25:13 -08:00
Serhii Palash
8fc8f3b8f7 Fix bug #3279 “Action within RESET doesn't trigger” (#3593)
https://github.com/react-navigation/react-navigation/issues/3279
2018-02-26 12:14:29 -08:00
Brent Vatne
a37473c5e4 Release 1.2.0 2018-02-25 17:46:44 -08:00
Brent Vatne
23eb0839cb Improve crossfade configuration for header 2018-02-25 17:44:16 -08:00
Brent Vatne
e84473db78 Use a closer-to-correct spring animation when releasing a gesture in CardStack (#3601) 2018-02-25 17:20:39 -08:00
Brent Vatne
c8531d0f38 Pop to top on TabNavigator when tapping already focused tab icon (#3589)
* Pop to top when tapping the tab icon for an already focused tab

* Dispatch popToTop with key to scope the action properly

* Add test for POP_TO_TOP with key
2018-02-25 16:33:20 -08:00
Vojtech Novak
276fb8f7b3 remove support for deprecated navigation actions (#3595)
* remove support for deprecated navigation actions

* remove deprecated action from flow definitions
2018-02-24 12:08:14 -08:00
Johan Ruokangas
ab758bcaaa Fix RFC link (#3594) 2018-02-24 12:50:31 +01:00
Matt Hamil
2bb91a6740 Updated README (#3567)
Changed wording in the Web Support section of README to reflect priority for previous 1.0 release.
2018-02-22 21:52:09 -08:00
Brent Vatne
ffa3a92534 First crack at publish from circle (#3574)
* First crack at publish from circle

* s/publish/deploy

* Update snapshots

* Try installing with yarn

* Nice one

* Shhh

* Comment some things out and add a webhook

* Add proper webhook url and add back build steps
2018-02-22 16:39:41 -08:00
Brent Vatne
6dda8c30a9 Fix colors for card and header border on iOS 2018-02-22 12:58:33 -08:00
Brent Vatne
065fdf61d8 Several improvements to StackNavigation Header (#3568)
* Refactor to remove unused variables, styles, and outer Animated view

* Style cleanup

* Proof of concept blur background

* Clean it up and add flow interface

* Update snapshots
2018-02-21 18:29:43 -08:00
Brent Vatne
371a714b57 Release 1.1.2 2018-02-20 16:00:44 -08:00
Ron Arts
1d33b95c5f Bump react-native-safe-area-view dep to 0.7.0 for react-native-web support (#3553) 2018-02-20 15:44:20 -08:00
Mike Grabowski
593bc8a648 Improve Header performance a bit (#3556)
* Improve `Header` performance a bit

I have been investigating `<Header />` component performance once again today with `render` cycles in particular. I have observed that during `push` phase, a typical Header in NavigationPlayground re-renders 3 times on iOS and 2 times on Android.

The first time is obvious on both platforms since that's when we add a new scene to an array. Second on iOS was also self-explanatory - we measure title width and other layout parameters in order to provide a better animation. 

What got me thinking was the last render cycle that didn't have a clear origin. After digging around I've found it is caused by a `scenes` array changing when transition finishes. What is surprising, it's just the reference to the object that changes, the content (items inside) stay the same.

I have found out that this is caused by `Transitioner` looping through an array of scenes attempting to remove stale scenes. Since scene becomes stale when you `pop` from it, this is obviously a noop when `pushing` a new route. That, obviously, causes an extra render cycle since `filter` produces same, but with a different pointer, object.

* Update Transitioner.js

* Update Transitioner.js
2018-02-20 15:44:10 -08:00
Brent Vatne
174a6e4175 Release 1.1.1 2018-02-19 18:18:38 -08:00
Brent Vatne
af991e5512 Release 1.1.0 2018-02-19 18:16:23 -08:00
Brent Vatne
0b0e9e9df5 Fix TabRouter to support shorthand route config 2018-02-19 18:16:23 -08:00
Brent Vatne
42b0ccca79 Release 1.1.0-rc.5 2018-02-19 18:16:23 -08:00
Brent Vatne
da6a960bb1 Fix regression in modular back button 2018-02-19 18:16:23 -08:00
Ron Arts
3ca47ec778 Fix react-native-web support for #3526 (#3546)
* MaskedViewIOS use broke react-native-web support, this fixes it.

* Fallback more gracefully.

* Actually return the value ...
2018-02-19 13:48:12 +01:00
Marcin Raburski
14ee56a20d getComponentForState made more generic (#2498) 2018-02-19 04:14:13 -08:00
Matthieu Lemoine
9d36daf48e Pass prevTransitionsProps to transitionConfig (#3304)
* Pass prevTransitionsProps to transitionConfig

* Remove flow type
2018-02-19 02:29:46 -08:00
Brent Vatne
055fdb2ffc Release 1.1.0-rc.4 2018-02-17 12:50:26 -08:00
Brent Vatne
b3bf80634d Move the back button handler subscription to the NavigationContainer constructor (#3542)
* Move the back button handler subscription to the constructor to fix precedence
- See 07d92947a1/src/Navigation.js (L190-L193)

* Return the boolean result of dispatch in global back handler
2018-02-17 12:50:04 -08:00
Brent Vatne
fafd0e8702 Cleanup around StackRouter (#3487)
* Make StackRouter a bit easier to read, update and add some comments, add an explicit test around stack and navigate

* Address feedback for clarity
2018-02-17 11:14:03 -08:00
Brent Vatne
d94045817c Release 1.1.0-rc.3 2018-02-17 09:42:45 -08:00
Brent Vatne
f0acaddf05 Fix headerLeft on first screen regression 2018-02-17 09:40:59 -08:00
Brent Vatne
9beb32ecac Update snapshots 2018-02-16 18:14:07 -08:00
Brent Vatne
29a29936bf Remove withNavigationFocus example until it's fixed 2018-02-16 18:10:33 -08:00
Brent Vatne
8961fef00b Bump version 2018-02-16 18:09:40 -08:00
Brent Vatne
021c7e54be Do not use hide views when swipe or animations are enabled. Improve withNavigationFocus example 2018-02-16 18:05:39 -08:00
Brent Vatne
27538cad94 Properly thread through childNavigation 2018-02-16 17:26:01 -08:00
Brent Vatne
9a8a50ef1d Release 1.1.0-rc.0 2018-02-16 17:07:28 -08:00
Brent Vatne
2c6dc35723 Revert "Revert "Add getParam navigation helper (#3510)" (#3532)"
This reverts commit 5febb81a1c.
2018-02-16 17:06:16 -08:00
Brent Vatne
3600ecbd9b Add back support for lazy tabs and use removeClippedSubviews (#3538)
* Lazy initialization of tabs and move contents off-screen when not active

* Make subview clipping and lazy both configurable

* Record snapshots again

* Update type definition

* Remove unused log
2018-02-16 16:59:19 -08:00
Sébastien Lorber
c74f001b1c add withNavigationFocus HOC (#3512)
* add withNavigationFocus HOC

See:
- https://github.com/react-navigation/react-navigation/issues/51#issuecomment-276003658
- https://github.com/react-navigation/react-navigation/issues/51#issuecomment-278761705
- https://github.com/react-navigation/react-navigation/pull/3345#issuecomment-360260067

* typos

* remove unused import

* Add withNavigationFocus export

* add example TabsWithNavigationFocus

* add example TabsWithNavigationFocus

* withNavigationFocus: get navigation from context or props

* subs => subscriptions

* fix flow issues
2018-02-16 16:57:33 -08:00
Brent Vatne
7f4706e4cc Update snapshots 2018-02-16 12:45:57 -08:00
Brent Vatne
d0ef33b12f Several small changes related to header style commit 2018-02-16 12:42:44 -08:00
Brent Vatne
3c3668c952 Header transition presets with support for standard iOS transition style (#3526)
Header transition presets with approximate support for UIKit transition style
2018-02-16 12:41:59 -08:00
Brent Vatne
5febb81a1c Revert "Add getParam navigation helper (#3510)" (#3532)
This reverts commit 50dcb37cd7.
2018-02-15 13:24:40 -08:00
Peter Piekarczyk
50dcb37cd7 Add getParam navigation helper (#3510)
* add getParam helper

* passing tests

* check for existence of param instead of just or

* fix spacing

* use in instead of checking for null

* add test for null
2018-02-15 10:14:08 -08:00
Ron Arts
69bca191a7 The Linking and BackHandler components are now both supported by react-native-web (#3494)
so there's no need to supply dummy replacements.
2018-02-13 12:23:26 -08:00
Sébastien Lorber
4df9198979 fix doc link (#3513) 2018-02-13 10:30:20 -08:00
Ashoat Tevosyan
0fc7f87173 Run Prettier on Flow libdef on precommit (#3495) 2018-02-12 14:45:50 -08:00
Brent Vatne
5b4d11ab5d Better orientation layout on playground 2018-02-12 11:53:23 -08:00
Brent Vatne
4d1cc285b9 Slightly improved margins on the iOS header back button 2018-02-09 21:19:02 -08:00
Brent Vatne
41725afa8a Use the correct iOS back icon 2018-02-09 20:08:56 -08:00
Brent Vatne
58b77d44ae Make Router({ RouteName: Component }) a valid way to configure a router (#3486)
* Make Router({routeName: Component}) a valid way to instantiate a route

* Update App.js in NavigationPlayground

* Fix route config flow type
2018-02-09 18:20:01 -08:00
Kazato Sugimoto
1d49b6e3fe Decode URI encoded string of deep link (#3455) 2018-02-09 16:26:20 -08:00
Brent Vatne
58b8a08af6 Update snapshots for TabView/TabNavigator 2018-02-09 14:22:31 -08:00
Maxime Florent Fankam
b11ea8a1d5 Allow swipeEnabled be a function (#3378)
* Allow swipeEnabled be a function with state param
2018-02-09 14:04:17 -08:00
Brent Vatne
3c2b977eca Number of lines in label for TabBarBottom is always 1 2018-02-09 13:51:04 -08:00
Brent Vatne
95565487ec Make it possible to go back to menu from key example and improve banner on menu screen (#3479)
* Make it possible to go back to menu from key example and improve banner on menu screen

* Fix NavigationPlayground flow error
2018-02-08 19:10:54 -08:00
Brent Vatne
a26d2acfca Bump to patch version 1.0.3 2018-02-08 18:24:20 -08:00
Brent Vatne
a1b3d2213d No need to have a conditional around slicing the routes array 2018-02-08 18:22:02 -08:00
Brent Vatne
eb39df507e Prevent navigation from getting in bad state when navigating back to route by key (#3478) 2018-02-08 18:20:14 -08:00
Brent Vatne
cca06bb530 Do not use contentInsetAdjustmentBehavior on iOS in ModalStack example 2018-02-08 16:14:45 -08:00
Eric Vicenti
2187d8fe66 Consistent treatment of route keys (#3474)
This problem was found and fixed by @matthargett and @jayphelps in #3397. I’m just rebasing and cleaning a few things up
2018-02-08 15:28:27 -08:00
Eric Vicenti
67f98b69eb Simpler implementation of withNavigation (#3476)
This will allow for refs with onRef (fixes #3461), and will avoid all these warnings from throwing during our tests
2018-02-08 14:20:52 -08:00
Brent Vatne
c0c5c86f63 Bump to patch version 1.0.2 2018-02-08 12:49:04 -08:00
Brent Vatne
4388b6403c Remove console logs from published version of navigation playground 2018-02-08 12:48:28 -08:00
Brent Vatne
2cb740fbf6 Only initialize the CardStack PanResponder if gestures are enabled 2018-02-08 12:36:26 -08:00
Brent Vatne
ac741a703b Remove extra scene in floating header if it hasn't been evicted due to transition yet 2018-02-08 12:32:09 -08:00
Brent Vatne
5641b42975 Revert "StackRouter block actions while transitioning (#3469)"
This reverts commit 858a0d7a53.
2018-02-08 11:49:25 -08:00
Brent Vatne
ea19ceaa6a Bump to minor version 1.0.1 2018-02-08 10:47:48 -08:00
Brent Vatne
57ae6e4736 Make TabRouter handle COMPLETE_TRANSITION in a child router without switching active index (#3473) 2018-02-08 10:46:12 -08:00
Eric Vicenti
858a0d7a53 StackRouter block actions while transitioning (#3469)
The most straightforward fix for two issues is to block all navigation actions while mid-transition of a stack navigator. This will fix:

The double-navigate on double tap issue, because the first navigation will start the transition and the second action will be ignored.

Will fix the buggy header experience that you can see when going back and forward to a different route quickly. This happens because the next navigate action happens before the completion action. After the fix, the navigate action will be ignored, the user will tap again, and will see a good transition
2018-02-08 09:02:47 -08:00
Dave Pack
cf36f22e68 Sync and switch SafeAreaView with standalone (#3452)
* add react-native-safe-area-view npm package

* remove local SafeAreaView, import from package in views

* update to latest react-native-safe-area-view

* update snapshots
2018-02-07 17:32:06 -08:00
Brent Vatne
7385c244b7 Add custom back button example 2018-02-07 10:42:06 -08:00
139 changed files with 12243 additions and 7017 deletions

View File

@@ -15,9 +15,26 @@ jobs:
- v3-react-navigation-master
- run: yarn # install root deps
- run: ./scripts/test.sh # run tests
- setup_remote_docker
- deploy:
command: |
set -x
VER="17.03.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
yarn global add exp
set +x
exp login -u "$EXPO_USERNAME" -p "$EXPO_PASSWORD"
set -x
cd examples/NavigationPlayground && yarn && exp publish --release-channel "${CIRCLE_SHA1}"
- save_cache:
key: v3-react-navigation-{{ .Branch }}-{{ checksum "package.json" }}
paths:
- ~/.cache/yarn
- ~/react-navigation/examples/NavigationPlayground/node_modules
- ~/react-navigation/examples/ReduxExample/node_modules
notify:
webhooks:
- url: https://react-navigation-ci.now.sh

View File

@@ -42,9 +42,7 @@
"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,

View File

@@ -9,6 +9,8 @@ If you have a question, feature request, or an idea for improving the library or
- [Get help on Discord chat (#react-navigation on Reactiflux)](https://discord.gg/4xEK3nD) or [on StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation)
- Search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue.
Bugs with react-navigation must be reproducible *without any external libraries that operate on it*. This means that if you are attempting to use Redux or MobX with it and you think you have found a bug, you must be able to reproduce it without Redux or MobX in this report. Redux related issues belong in [react-navigation-redux-helpers](https://github.com/react-navigation/react-navigation-redux-helpers), and we do not have any first-class integration with MobX at the moment.
---
### Current Behavior

13
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,13 @@
# Contributing to React Navigation
This library is a community effort: it can only be great if we all help out in one way or another! If you feel like you aren't experienced enough using React Navigation to contribute, you can still make an impact by:
* Responding to one of the open [issues](https://github.com/react-community/react-navigation/issues). Even if you can't resolve or fully answer a question, asking for more information or clarity on an issue is extremely beneficial for someone to come after you to resolve the issue.
* Creating public example repositories or [Snacks](https://snack.expo.io/) of navigation problems you have solved and sharing the links in [Community Resources](https://github.com/react-navigation/react-navigation/blob/master/COMMUNITY_RESOURCES.md).
* Answering questions on [Stack Overflow](https://stackoverflow.com/search?q=react-navigation).
* Answering questions in our [Reactiflux](https://www.reactiflux.com/) channel.
* Providing feedback on the open [PRs](https://github.com/react-navigation/react-navigation/pulls).
* Providing feedback on the open [RFCs](https://github.com/react-navigation/rfcs).
* Improving the [website](https://github.com/react-navigation/react-navigation.github.io).
If you would like to submit a pull request, please follow the [Contributors guide](https://reactnavigation.org/docs/contributing.html) to find out how. If you don't know where to start, check the ones with the label [`good first issue`](https://github.com/react-community/react-navigation/labels/good%20first%20issue) - even [fixing a typo in the documentation](https://github.com/react-community/react-navigation/pull/2727) is a worthy contribution!

View File

@@ -1,6 +1,6 @@
# React Navigation
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![codecov](https://codecov.io/gh/react-community/react-navigation/branch/master/graph/badge.svg)](https://codecov.io/gh/react-community/react-navigation) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/guides/contributors)
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![codecov](https://codecov.io/gh/react-community/react-navigation/branch/master/graph/badge.svg)](https://codecov.io/gh/react-community/react-navigation) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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.
@@ -39,17 +39,7 @@ See [the help page](https://reactnavigation.org/en/help.html).
#### How can I help?
This library is a community effort: it can only be great if we all help out in one way or another! If you feel like you aren't experienced enough using React Navigation to contribute, you can still make an impact by:
* Responding to one of the open [issues](https://github.com/react-community/react-navigation/issues). Even if you can't resolve or fully answer a question, asking for more information or clarity on an issue is extremely beneficial for someone to come after you to resolve the issue.
* Creating public example repositories or [Snacks](https://snack.expo.io/) of navigation problems you have solved and sharing the links in [Community Resources](https://github.com/react-navigation/react-navigation/blob/master/COMMUNITY_RESOURCES.md).
* Answering questions on [Stack Overflow](https://stackoverflow.com/search?q=react-navigation).
* Answering questions in our [Reactiflux](https://www.reactiflux.com/) channel.
* Providing feedback on the open [PRs](https://github.com/react-community/react-navigation/pulls).
* Providing feedback on the open [RFCs](https://github.com/react-community/rfcs/pulls).
* Improving the [website](https://github.com/react-navigation/react-navigation.github.io).
If you would like to submit a pull request, please follow the [Contributors guide](https://reactnavigation.org/docs/contributing.html) to find out how. If you don't know where to start, check the ones with the label [`good first issue`](https://github.com/react-community/react-navigation/labels/good%20first%20issue) - even [fixing a typo in the documentation](https://github.com/react-community/react-navigation/pull/2727) is a worthy contribution!
See our [Contributing Guide](CONTRIBUTING.md)!
#### Is this the only library available for navigation?
@@ -57,7 +47,7 @@ Certainly not! There other libraries - which, depending on your needs, can be be
#### Can I use this library for web?
Currently this is [not a priority](https://github.com/react-community/react-navigation/issues/2585#issuecomment-330338793), but the architecture of this library allows for it (and it has worked in the past). If you would like to lead this charge, please reach out with your ideas for how to move forward on the [RFCs repository](https://github.com/react-navigation/rfcs) and we would be happy to discuss.
Web support was [not a priority for the 1.0 release](https://github.com/react-community/react-navigation/issues/2585#issuecomment-330338793), but the architecture of this library allows for it (and it has worked in the past). If you would like to lead this charge, please reach out with your ideas for how to move forward on the [RFCs repository](https://github.com/react-navigation/rfcs) and we would be happy to discuss.
## Code of conduct

12
assetsTransformer.js Normal file
View 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)) + ';';
},
};

View File

@@ -6,6 +6,11 @@
"development": {
"plugins": [
"transform-react-jsx-source"
],
},
"production": {
"plugins": [
"transform-remove-console"
]
}
}

View File

@@ -65,11 +65,13 @@ module.file_ext=.jsx
module.file_ext=.json
module.file_ext=.native.js
suppress_type=$FlowIgnore
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy

View File

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

View File

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

View File

@@ -11,7 +11,7 @@
"splash": {
"image": "./assets/icons/splash.png"
},
"sdkVersion": "25.0.0",
"sdkVersion": "26.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"packagerOpts": {
"assetExts": [

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
/* @flow */
import React from 'react';
import { Constants, ScreenOrientation } from 'expo';
import { Asset, Constants, ScreenOrientation } from 'expo';
ScreenOrientation.allow(ScreenOrientation.Orientation.ALL);
import {
Animated,
Image,
Platform,
ScrollView,
StyleSheet,
@@ -14,9 +16,8 @@ import {
StatusBar,
View,
} from 'react-native';
import { SafeAreaView, StackNavigator } from 'react-navigation';
import { SafeAreaView, createStackNavigator } from 'react-navigation';
import Banner from './Banner';
import CustomTabs from './CustomTabs';
import CustomTransitioner from './CustomTransitioner';
import Drawer from './Drawer';
@@ -25,15 +26,35 @@ import TabsInDrawer from './TabsInDrawer';
import ModalStack from './ModalStack';
import StacksInTabs from './StacksInTabs';
import StacksOverTabs from './StacksOverTabs';
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 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',
@@ -42,6 +63,14 @@ const ExampleInfo = {
name: 'Drawer Example',
description: 'Android-style drawer navigation',
},
StackWithHeaderPreset: {
name: 'UIKit-style Header Transitions',
description: 'Masked back button and sliding header items. iOS only.',
},
StackWithTranslucentHeader: {
name: 'Translucent Header',
description: 'Render arbitrary translucent content in header background.',
},
// MultipleDrawer: {
// name: 'Multiple Drawer Example',
// description: 'Add any drawer you need',
@@ -76,6 +105,10 @@ const ExampleInfo = {
name: 'Stacks over Tabs',
description: 'Nested stack navigation that pushes on top of tabs',
},
StacksWithKeys: {
name: 'Link in Stack with keys',
description: 'Use keys to link between screens',
},
LinkStack: {
name: 'Link in Stack',
description: 'Deep linking into a route in stack',
@@ -84,42 +117,35 @@ 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',
},
KeyboardHandlingExample: {
name: 'Keyboard Handling Example',
description:
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
},
};
const ExampleRoutes = {
SimpleStack: {
screen: SimpleStack,
},
SimpleTabs: {
screen: SimpleTabs,
},
Drawer: {
screen: Drawer,
},
SimpleStack,
SwitchWithStacks,
SimpleTabs: SimpleTabs,
Drawer: Drawer,
// MultipleDrawer: {
// screen: MultipleDrawer,
// },
TabsInDrawer: {
screen: TabsInDrawer,
},
CustomTabs: {
screen: CustomTabs,
},
CustomTransitioner: {
screen: CustomTransitioner,
},
ModalStack: {
screen: ModalStack,
},
StacksInTabs: {
screen: StacksInTabs,
},
StacksOverTabs: {
screen: StacksOverTabs,
},
StackWithCustomHeaderBackImage: StackWithCustomHeaderBackImage,
StackWithHeaderPreset: StackWithHeaderPreset,
StackWithTranslucentHeader: StackWithTranslucentHeader,
TabsInDrawer: TabsInDrawer,
CustomTabs: CustomTabs,
CustomTransitioner: CustomTransitioner,
ModalStack: ModalStack,
StacksWithKeys: StacksWithKeys,
StacksInTabs: StacksInTabs,
StacksOverTabs: StacksOverTabs,
LinkStack: {
screen: SimpleStack,
path: 'people/Jordan',
@@ -128,54 +154,156 @@ const ExampleRoutes = {
screen: SimpleTabs,
path: 'settings',
},
TabAnimations: {
screen: TabAnimations,
},
TabsWithNavigationFocus,
KeyboardHandlingExample,
// This is commented out because it's rarely useful
// InactiveStack,
};
class MainScreen extends React.Component<*> {
type State = {
scrollY: Animated.Value,
};
class MainScreen extends React.Component<any, State> {
state = {
scrollY: new Animated.Value(0),
};
componentDidMount() {
Asset.fromModule(
require('react-navigation/src/views/assets/back-icon-mask.png')
).downloadAsync();
Asset.fromModule(
require('react-navigation/src/views/assets/back-icon.png')
).downloadAsync();
}
render() {
const { navigation } = this.props;
const scale = this.state.scrollY.interpolate({
inputRange: [-450, 0, 100],
outputRange: [2, 1, 0.8],
extrapolate: 'clamp',
});
const translateY = this.state.scrollY.interpolate({
inputRange: [-450, 0, 100],
outputRange: [-150, 0, 40],
});
const opacity = this.state.scrollY.interpolate({
inputRange: [0, 50],
outputRange: [1, 0],
extrapolate: 'clamp',
});
const underlayOpacity = this.state.scrollY.interpolate({
inputRange: [0, 50],
outputRange: [0, 1],
extrapolate: 'clamp',
});
const backgroundScale = this.state.scrollY.interpolate({
inputRange: [-450, 0],
outputRange: [3, 1],
extrapolate: 'clamp',
});
const backgroundTranslateY = this.state.scrollY.interpolate({
inputRange: [-450, 0],
outputRange: [0, 0],
});
return (
<View style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }}>
<Banner />
{Object.keys(ExampleRoutes).map((routeName: string) => (
<TouchableOpacity
key={routeName}
onPress={() => {
const { path, params, screen } = ExampleRoutes[routeName];
const { router } = screen;
const action =
path && router.getActionForPathAndParams(path, params);
navigation.navigate(routeName, {}, action);
}}
<Animated.ScrollView
style={{ flex: 1 }}
scrollEventThrottle={1}
onScroll={Animated.event(
[
{
nativeEvent: { contentOffset: { y: this.state.scrollY } },
},
],
{ useNativeDriver: true }
)}
>
<Animated.View
style={[
styles.backgroundUnderlay,
{
transform: [
{ scale: backgroundScale },
{ translateY: backgroundTranslateY },
],
},
]}
/>
<Animated.View
style={{ opacity, transform: [{ scale }, { translateY }] }}
>
<SafeAreaView
style={styles.bannerContainer}
forceInset={{ top: 'always', bottom: 'never' }}
>
<SafeAreaView
style={styles.itemContainer}
forceInset={{ vertical: 'never' }}
>
<View style={styles.item}>
<Text style={styles.title}>
{ExampleInfo[routeName].name}
</Text>
<Text style={styles.description}>
{ExampleInfo[routeName].description}
</Text>
</View>
</SafeAreaView>
</TouchableOpacity>
))}
</ScrollView>
<View style={styles.banner}>
<Image
source={require('./assets/NavLogo.png')}
style={styles.bannerImage}
/>
<Text style={styles.bannerTitle}>
React Navigation Examples
</Text>
</View>
</SafeAreaView>
</Animated.View>
<SafeAreaView forceInset={{ bottom: 'always', horizontal: 'never' }}>
<View style={{ backgroundColor: '#fff' }}>
{Object.keys(ExampleRoutes).map((routeName: string) => (
<TouchableOpacity
key={routeName}
onPress={() => {
let route = ExampleRoutes[routeName];
if (route.screen || route.path || route.params) {
const { path, params, screen } = route;
const { router } = screen;
const action =
path && router.getActionForPathAndParams(path, params);
navigation.navigate(routeName, {}, action);
} else {
navigation.navigate(routeName);
}
}}
>
<SafeAreaView
style={styles.itemContainer}
forceInset={{ veritcal: 'never', bottom: 'never' }}
>
<View style={styles.item}>
<Text style={styles.title}>
{ExampleInfo[routeName].name}
</Text>
<Text style={styles.description}>
{ExampleInfo[routeName].description}
</Text>
</View>
</SafeAreaView>
</TouchableOpacity>
))}
</View>
</SafeAreaView>
</Animated.ScrollView>
<StatusBar barStyle="light-content" />
<View style={styles.statusBarUnderlay} />
<Animated.View
style={[styles.statusBarUnderlay, { opacity: underlayOpacity }]}
/>
</View>
);
}
}
const AppNavigator = StackNavigator(
const AppNavigator = createStackNavigator(
{
...ExampleRoutes,
Index: {
@@ -194,7 +322,7 @@ const AppNavigator = StackNavigator(
}
);
export default () => <AppNavigator />;
export default AppNavigator;
const styles = StyleSheet.create({
item: {
@@ -230,4 +358,35 @@ const styles = StyleSheet.create({
fontSize: 13,
color: '#999',
},
backgroundUnderlay: {
backgroundColor: '#673ab7',
position: 'absolute',
top: -100,
height: 300,
left: 0,
right: 0,
},
bannerContainer: {
// backgroundColor: '#673ab7',
alignItems: 'center',
},
banner: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
},
bannerImage: {
width: 36,
height: 36,
resizeMode: 'contain',
tintColor: '#fff',
margin: 8,
},
bannerTitle: {
fontSize: 18,
fontWeight: '200',
color: '#fff',
marginVertical: 8,
marginRight: 5,
},
});

View File

@@ -4,7 +4,6 @@
import React from 'react';
import {
Button,
Platform,
ScrollView,
StyleSheet,
@@ -18,9 +17,9 @@ import {
createNavigationContainer,
SafeAreaView,
TabRouter,
addNavigationHelpers,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -66,19 +65,14 @@ const CustomTabBar = ({ navigation }) => {
);
};
const CustomTabView = ({ router, navigation }) => {
const CustomTabView = ({ descriptors, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForRouteName(routes[index].routeName);
const descriptor = descriptors[routes[index].key];
const ActiveScreen = descriptor.getComponent();
return (
<SafeAreaView forceInset={{ top: 'always' }}>
<CustomTabBar navigation={navigation} />
<ActiveScreen
navigation={addNavigationHelpers({
dispatch: navigation.dispatch,
state: routes[index],
})}
screenProps={{}}
/>
<ActiveScreen navigation={descriptor.navigation} />
</SafeAreaView>
);
};
@@ -105,7 +99,7 @@ const CustomTabRouter = TabRouter(
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
createNavigator(CustomTabView, CustomTabRouter, {})
);
const styles = StyleSheet.create({

View File

@@ -1,7 +1,6 @@
import React, { Component, PropTypes } from 'react';
import {
Animated,
Button,
Easing,
Image,
Platform,
@@ -14,10 +13,10 @@ import {
SafeAreaView,
StackRouter,
createNavigationContainer,
addNavigationHelpers,
createNavigator,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<SafeAreaView forceInset={{ top: 'always' }}>
@@ -45,11 +44,12 @@ const MySettingsScreen = ({ navigation }) => (
class CustomNavigationView extends Component {
render() {
const { navigation, router } = this.props;
const { navigation, router, descriptors } = this.props;
return (
<Transitioner
configureTransition={this._configureTransition}
descriptors={descriptors}
navigation={navigation}
render={this._render}
/>
@@ -86,16 +86,10 @@ class CustomNavigationView extends Component {
transform: [{ scale: animatedValue }],
};
// The prop `router` is populated when we call `createNavigator`.
const Scene = router.getComponentForRouteName(scene.route.routeName);
const Scene = scene.descriptor.getComponent();
return (
<Animated.View key={index} style={[styles.view, animation]}>
<Scene
navigation={addNavigationHelpers({
...navigation,
state: routes[index],
})}
/>
<Scene navigation={scene.descriptor.navigation} />
</Animated.View>
);
};
@@ -107,7 +101,7 @@ const CustomRouter = StackRouter({
});
const CustomTransitioner = createNavigationContainer(
createNavigator(CustomRouter)(CustomNavigationView)
createNavigator(CustomNavigationView, CustomRouter, {})
);
export default CustomTransitioner;

View File

@@ -3,19 +3,21 @@
*/
import React from 'react';
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, DrawerNavigator, SafeAreaView } from 'react-navigation';
import { Platform, ScrollView, StatusBar } from 'react-native';
import {
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>
<SafeAreaView forceInset={{ top: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('DrawerOpen')}
title="Open drawer"
/>
<Button onPress={() => navigation.openDrawer()} title="Open drawer" />
<Button
onPress={() => navigation.navigate('Email')}
title="Open other screen"
@@ -30,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 }) => (
@@ -48,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: '/',
@@ -76,9 +86,6 @@ const DrawerExample = DrawerNavigator(
},
},
{
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
initialRouteName: 'Drafts',
contentOptions: {
activeTintColor: '#e91e63',

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

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

View File

@@ -3,12 +3,13 @@
*/
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 contentInsetAdjustmentBehavior="automatic">
<ScrollView>
<SafeAreaView
forceInset={{
top: navigation.state.routeName === 'HeaderTest' ? 'always' : 'never',
@@ -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,

View File

@@ -3,18 +3,16 @@
*/
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}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('DrawerOpen')}
title="Open drawer"
/>
<Button onPress={() => navigation.openDrawer()} title="Open drawer" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</ScrollView>
);
@@ -43,7 +41,7 @@ DraftsScreen.navigationOptions = {
),
};
const DrawerExample = DrawerNavigator(
const DrawerExample = createDrawerNavigator(
{
Inbox: {
path: '/',
@@ -55,9 +53,6 @@ const DrawerExample = DrawerNavigator(
},
},
{
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
initialRouteName: 'Drafts',
contentOptions: {
activeTintColor: '#e91e63',
@@ -65,14 +60,10 @@ const DrawerExample = DrawerNavigator(
}
);
const MainDrawerExample = DrawerNavigator({
const MainDrawerExample = createDrawerNavigator({
Drafts: {
screen: DrawerExample,
},
}, {
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
});
const styles = StyleSheet.create({

View File

@@ -4,19 +4,47 @@
import type {
NavigationScreenProp,
NavigationState,
NavigationStateRoute,
NavigationEventSubscription,
} 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,
withNavigation,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
navigation: NavigationScreenProp<NavigationState>,
banner: React.Node,
};
type BackButtonProps = {
navigation: NavigationScreenProp<NavigationStateRoute>,
};
class MyBackButton extends React.Component<BackButtonProps, any> {
render() {
return (
<HeaderButtons>
<HeaderButtons.Item title="Back" onPress={this._navigateBack} />
</HeaderButtons>
);
}
_navigateBack = () => {
this.props.navigation.goBack(null);
};
}
const MyBackButtonWithNavigation = withNavigation(MyBackButton);
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
@@ -37,7 +65,8 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
/>
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<Button onPress={() => navigation.goBack()} title="Go back" />
<Button onPress={() => navigation.dismiss()} title="Dismiss" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
@@ -45,7 +74,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<*>,
navigation: NavigationScreenProp<NavigationState>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
@@ -89,11 +118,12 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<*>,
navigation: NavigationScreenProp<NavigationState>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = {
title: 'Photos',
headerLeft: <MyBackButtonWithNavigation />,
};
_s0: NavigationEventSubscription;
_s1: NavigationEventSubscription;
@@ -129,7 +159,7 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
const { navigation } = this.props;
return (
<MyNavScreen
banner={`${navigation.state.params.name}'s Photos`}
banner={`${navigation.getParam('name')}'s Photos`}
navigation={navigation}
/>
);
@@ -138,9 +168,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}
/>
);
@@ -155,17 +185,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 SimpleStack = StackNavigator({
const SimpleStack = createStackNavigator({
Home: {
screen: MyHomeScreen,
},

View File

@@ -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' }}>
@@ -143,7 +144,7 @@ MySettingsScreen.navigationOptions = {
),
};
const SimpleTabs = TabNavigator(
const SimpleTabs = createBottomTabNavigator(
{
Home: {
screen: MyHomeScreen,

View File

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

View File

@@ -0,0 +1,119 @@
/**
* @flow
*/
import type { NavigationScreenProp } from 'react-navigation';
import * as React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import { createStackNavigator, SafeAreaView } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
type NavScreenProps = {
navigation: NavigationScreenProp<*>,
};
class HomeScreen extends React.Component<NavScreenProps> {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigation } = this.props;
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button
onPress={() => navigation.push('ScreenWithNoHeader')}
title="Push screen with no header"
/>
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
class OtherScreen extends React.Component<NavScreenProps> {
static navigationOptions = {
title: 'Your title here',
};
render() {
const { navigation } = this.props;
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button
onPress={() => navigation.push('ScreenWithLongTitle')}
title="Push another screen"
/>
<Button
onPress={() => navigation.push('ScreenWithNoHeader')}
title="Push screen with no header"
/>
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
class ScreenWithLongTitle extends React.Component<NavScreenProps> {
static navigationOptions = {
title: "Another title that's kind of long",
};
render() {
const { navigation } = this.props;
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => navigation.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;
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button onPress={() => navigation.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',
}
);
export default StackWithHeaderPreset;

View File

@@ -0,0 +1,237 @@
/**
* @flow
*/
import type {
NavigationScreenProp,
NavigationEventSubscription,
} from 'react-navigation';
import { isIphoneX } from 'react-native-iphone-x-helper';
import * as React from 'react';
import { BlurView, Constants } from 'expo';
import {
Dimensions,
Platform,
ScrollView,
StatusBar,
View,
} from 'react-native';
import { Header, createStackNavigator } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
banner: React.Node,
};
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
return (
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.push('Profile', { name: 'Jane' })}
title="Push a profile screen"
/>
<Button
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
title="Navigate to a photos screen"
/>
<Button
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
title="Replace with profile"
/>
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</ScrollView>
);
}
// Inset to compensate for navigation bar being transparent.
// And improved abstraction for this will be built in to react-navigation
// at some point.
getHeaderInset() {
const NOTCH_HEIGHT = isIphoneX() ? 25 : 0;
// $FlowIgnore: we will remove the HEIGHT static soon enough
const BASE_HEADER_HEIGHT = Header.HEIGHT;
const HEADER_HEIGHT =
Platform.OS === 'ios'
? BASE_HEADER_HEIGHT + NOTCH_HEIGHT
: BASE_HEADER_HEIGHT + Constants.statusBarHeight;
return Platform.select({
ios: {
contentInset: { top: HEADER_HEIGHT },
contentOffset: { y: -HEADER_HEIGHT },
},
android: {
contentContainerStyle: {
paddingTop: HEADER_HEIGHT,
},
},
});
}
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
static navigationOptions = {
title: 'Welcome',
};
_s0: NavigationEventSubscription;
_s1: NavigationEventSubscription;
_s2: NavigationEventSubscription;
_s3: NavigationEventSubscription;
componentDidMount() {
this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
}
componentWillUnmount() {
this._s0.remove();
this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onWF = a => {
console.log('_willFocus HomeScreen', a);
};
_onDF = a => {
console.log('_didFocus HomeScreen', a);
};
_onWB = a => {
console.log('_willBlur HomeScreen', a);
};
_onDB = a => {
console.log('_didBlur HomeScreen', a);
};
render() {
const { navigation } = this.props;
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
}
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = {
title: 'Photos',
};
_s0: NavigationEventSubscription;
_s1: NavigationEventSubscription;
_s2: NavigationEventSubscription;
_s3: NavigationEventSubscription;
componentDidMount() {
this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
}
componentWillUnmount() {
this._s0.remove();
this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onWF = a => {
console.log('_willFocus PhotosScreen', a);
};
_onDF = a => {
console.log('_didFocus PhotosScreen', a);
};
_onWB = a => {
console.log('_willBlur PhotosScreen', a);
};
_onDB = a => {
console.log('_didBlur PhotosScreen', a);
};
render() {
const { navigation } = this.props;
return (
<MyNavScreen
banner={`${navigation.state.params.name}'s Photos`}
navigation={navigation}
/>
);
}
}
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
navigation.state.params.name
}'s Profile`}
navigation={navigation}
/>
);
MyProfileScreen.navigationOptions = props => {
const { navigation } = props;
const { state, setParams } = navigation;
const { params } = state;
return {
headerBackImage: params.headerBackImage,
headerTitle: `${params.name}'s Profile!`,
// Render a button on the right side of the header.
// When pressed switches the screen to edit mode.
headerRight: (
<HeaderButtons>
<HeaderButtons.Item
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
</HeaderButtons>
),
};
};
const StackWithTranslucentHeader = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
},
{
headerTransitionPreset: 'uikit',
navigationOptions: {
headerTransparent: true,
headerBackground: Platform.select({
ios: <BlurView style={{ flex: 1 }} intensity={98} />,
android: (
<View style={{ flex: 1, backgroundColor: 'rgba(255,255,255,0.7)' }} />
),
}),
},
}
);
export default StackWithTranslucentHeader;

View File

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

View File

@@ -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,7 @@ const TabNav = TabNavigator(
}
);
const StacksOverTabs = StackNavigator({
const StacksOverTabs = createStackNavigator({
Root: {
screen: TabNav,
},

View File

@@ -0,0 +1,102 @@
import React from 'react';
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() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home</Text>
<Button
title="Navigate to 'Profile' with key 'A'"
onPress={() =>
this.props.navigation.navigate({
routeName: 'Profile',
key: 'A',
params: { homeKey: this.props.navigation.state.key },
})
}
/>
<Button
title="Go back to other examples"
onPress={() => this.props.navigation.goBack(null)}
/>
<StatusBar barStyle="default" />
</View>
);
}
}
class ProfileScreen extends React.Component<any, any> {
render() {
const { homeKey } = this.props.navigation.state.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile</Text>
<Button
title="Navigate to 'Settings' with key 'B'"
onPress={() =>
this.props.navigation.navigate({
routeName: 'Settings',
key: 'B',
params: { homeKey },
})
}
/>
<Button
title={`Navigate back to 'Home' with key ${homeKey}`}
onPress={() =>
this.props.navigation.navigate({ routeName: 'Home', key: homeKey })
}
/>
</View>
);
}
}
class SettingsScreen extends React.Component<any, any> {
render() {
const { homeKey } = this.props.navigation.state.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Settings</Text>
<Button
title={`Navigate back to 'Home' with key ${homeKey}`}
onPress={() =>
this.props.navigation.navigate({ routeName: 'Home', key: homeKey })
}
/>
<Button
title="Navigate back to 'Profile' with key 'A'"
onPress={() =>
this.props.navigation.navigate({
routeName: 'Profile',
key: 'A',
})
}
/>
</View>
);
}
}
const Stack = createStackNavigator(
{
Home: {
screen: HomeScreen,
},
Profile: {
screen: ProfileScreen,
},
Settings: {
screen: SettingsScreen,
},
},
{
headerMode: 'none',
}
);
export default Stack;

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

View File

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

View File

@@ -3,13 +3,13 @@
*/
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: {

View File

@@ -0,0 +1,103 @@
/**
* @flow
*/
import React from 'react';
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';
class Child extends React.Component<any, any> {
render() {
return (
<Text style={{ color: this.props.isFocused ? 'green' : 'maroon' }}>
{this.props.isFocused
? 'I know that my parent is focused!'
: 'My parent is not focused! :O'}
</Text>
);
}
}
const ChildWithNavigationFocus = withNavigationFocus(Child);
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
class TabScreen extends React.Component<any, any> {
static navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
state = { showChild: false };
render() {
const { isFocused } = this.props;
return (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
{'Tab ' + name.toLowerCase()}
</Text>
<Text style={{ marginBottom: 20 }}>
{'props.isFocused: ' + (isFocused ? ' true' : 'false')}
</Text>
{this.state.showChild ? (
<ChildWithNavigationFocus />
) : (
<Button
title="Press me"
onPress={() => this.setState({ showChild: true })}
/>
)}
<Button
onPress={() => this.props.navigation.pop()}
title="Back to other examples"
/>
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
return withNavigationFocus(TabScreen);
};
const TabsWithNavigationFocus = 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 TabsWithNavigationFocus;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

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

View File

@@ -8,21 +8,25 @@
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js && flow"
"test": "flow"
},
"dependencies": {
"expo": "^25.0.0",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-navigation": "link:../.."
"expo": "^26.0.0",
"react": "16.3.0-alpha.1",
"react-native": "^0.54.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-navigation": "link:../..",
"react-navigation-header-buttons": "^0.0.4",
"react-navigation-material-bottom-tabs": "0.1.3"
},
"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",
"jest": "^22.1.3",
"jest-expo": "^26.0.0",
"react-native-scripts": "^1.5.0",
"react-test-renderer": "16.0.0"
"react-test-renderer": "16.3.0-alpha.1"
},
"jest": {
"preset": "jest-expo",

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,8 @@
"redux": "^3.7.2"
},
"devDependencies": {
"babel-jest": "^21.0.0",
"jest": "^21.0.1",
"babel-jest": "^22.4.1",
"jest": "^22.1.3",
"jest-expo": "^25.1.0",
"react-native-scripts": "^1.3.1",
"react-test-renderer": "16.0.0"

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
import { StackNavigator } from 'react-navigation';
import LoginScreen from '../components/LoginScreen';
import MainScreen from '../components/MainScreen';
@@ -24,11 +24,11 @@ class AppWithNavigationState extends React.Component {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={addNavigationHelpers({
navigation={{
dispatch,
state: nav,
addListener,
})}
}}
/>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,15 @@ declare module 'react-navigation' {
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js
declare type AnimatedValue = Object;
declare type HeaderForceInset = {
horizontal?: string,
vertical?: string,
left?: string,
right?: string,
top?: string,
bottom?: string,
};
/**
* Next, all the type declarations
*/
@@ -71,25 +80,11 @@ declare module 'react-navigation' {
key?: string,
|};
declare type DeprecatedNavigationNavigateAction = {|
type: 'Navigate',
routeName: string,
params?: NavigationParams,
// The action to run inside the sub-router
action?: NavigationNavigateAction | DeprecatedNavigationNavigateAction,
|};
declare export type NavigationBackAction = {|
type: 'Navigation/BACK',
key?: ?string,
|};
declare type DeprecatedNavigationBackAction = {|
type: 'Back',
key?: ?string,
|};
declare export type NavigationSetParamsAction = {|
type: 'Navigation/SET_PARAMS',
@@ -100,26 +95,11 @@ declare module 'react-navigation' {
params: NavigationParams,
|};
declare type DeprecatedNavigationSetParamsAction = {|
type: 'SetParams',
// The key of the route where the params should be set
key: string,
// The new params to merge into the existing route params
params: NavigationParams,
|};
declare export type NavigationInitAction = {|
type: 'Navigation/INIT',
params?: NavigationParams,
|};
declare type DeprecatedNavigationInitAction = {|
type: 'Init',
params?: NavigationParams,
|};
declare export type NavigationResetAction = {|
type: 'Navigation/RESET',
index: number,
@@ -127,25 +107,11 @@ declare module 'react-navigation' {
actions: Array<NavigationNavigateAction>,
|};
declare type DeprecatedNavigationResetAction = {|
type: 'Reset',
index: number,
key?: ?string,
actions: Array<
NavigationNavigateAction | DeprecatedNavigationNavigateAction
>,
|};
declare export type NavigationUriAction = {|
type: 'Navigation/URI',
uri: string,
|};
declare type DeprecatedNavigationUriAction = {|
type: 'Uri',
uri: string,
|};
declare export type NavigationReplaceAction = {|
+type: 'Navigation/REPLACE',
+key: string,
@@ -181,17 +147,6 @@ declare module 'react-navigation' {
| NavigationSetParamsAction
| NavigationResetAction;
declare type DeprecatedNavigationAction =
| DeprecatedNavigationInitAction
| DeprecatedNavigationNavigateAction
| DeprecatedNavigationBackAction
| DeprecatedNavigationSetParamsAction
| DeprecatedNavigationResetAction;
declare export type PossiblyDeprecatedNavigationAction =
| NavigationAction
| DeprecatedNavigationAction;
/**
* NavigationState is a tree of routes for a single navigator, where each
* child route may either be a NavigationScreenRoute or a
@@ -314,7 +269,8 @@ declare module 'react-navigation' {
declare export type NavigationComponent =
| NavigationScreenComponent<NavigationRoute, *, *>
| NavigationContainer<NavigationStateRoute, *, *>;
| NavigationContainer<*, *, *>
| any;
declare export type NavigationScreenComponent<
Route: NavigationRoute,
@@ -332,10 +288,12 @@ declare module 'react-navigation' {
navigationOptions?: ?NavigationScreenConfig<Options>,
};
declare export type NavigationRouteConfig = {
navigationOptions?: NavigationScreenConfig<*>,
path?: string,
} & NavigationScreenRouteConfig;
declare export type NavigationRouteConfig =
| NavigationComponent
| ({
navigationOptions?: NavigationScreenConfig<*>,
path?: string,
} & NavigationScreenRouteConfig);
declare export type NavigationScreenRouteConfig =
| {
@@ -378,18 +336,21 @@ declare module 'react-navigation' {
declare export type NavigationStackScreenOptions = NavigationScreenOptions & {
header?: ?(React$Node | (HeaderProps => React$Node)),
headerTransparent?: boolean,
headerTitle?: string | React$Node | React$ElementType,
headerTitleStyle?: AnimatedTextStyleProp,
headerTitleAllowFontScaling?: boolean,
headerTintColor?: string,
headerLeft?: React$Node | React$ElementType,
headerBackTitle?: string,
headerBackImage?: ImageSource,
headerBackImage?: React$Node | React$ElementType,
headerTruncatedBackTitle?: string,
headerBackTitleStyle?: TextStyleProp,
headerPressColorAndroid?: string,
headerRight?: React$Node,
headerStyle?: ViewStyleProp,
headerForceInset?: HeaderForceInset,
headerBackground?: React$Node | React$ElementType,
gesturesEnabled?: boolean,
gestureResponseDistance?: { vertical?: number, horizontal?: number },
gestureDirection?: 'default' | 'inverted',
@@ -400,11 +361,13 @@ declare module 'react-navigation' {
initialRouteParams?: NavigationParams,
paths?: NavigationPathsConfig,
navigationOptions?: NavigationScreenConfig<*>,
initialRouteKey?: string,
|};
declare export type NavigationStackViewConfig = {|
mode?: 'card' | 'modal',
headerMode?: HeaderMode,
headerTransitionPreset?: 'fade-in-place' | 'uikit',
cardStyle?: ViewStyleProp,
transitionConfig?: () => TransitionConfig,
onTransitionStart?: () => void,
@@ -416,6 +379,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
*/
@@ -427,7 +404,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`
|};
@@ -476,7 +452,7 @@ declare module 'react-navigation' {
*/
declare export type NavigationDispatch = (
action: PossiblyDeprecatedNavigationAction
action: NavigationAction
) => boolean;
declare export type NavigationProp<S> = {
@@ -495,7 +471,7 @@ declare module 'react-navigation' {
type: EventType,
action: NavigationAction,
state: NavigationState,
lastState: NavigationState,
lastState: ?NavigationState,
};
declare export type NavigationEventCallback = (
@@ -510,12 +486,21 @@ declare module 'react-navigation' {
+state: S,
dispatch: NavigationDispatch,
goBack: (routeKey?: ?string) => boolean,
dismiss: () => boolean,
navigate: (
routeName: string,
routeName:
| string
| {
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
key?: string,
},
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
setParams: (newParams: NavigationParams) => boolean,
getParam: (paramName: string, fallback?: any) => any,
addListener: (
eventName: string,
callback: NavigationEventCallback
@@ -532,6 +517,7 @@ declare module 'react-navigation' {
) => boolean,
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
popToTop: (params?: { immediate?: boolean }) => boolean,
isFocused: () => boolean,
};
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
@@ -540,29 +526,6 @@ declare module 'react-navigation' {
navigationOptions?: O,
}>;
//declare export type NavigationNavigatorProps<O: {}, S: {}> =
// | {}
// | { navigation: NavigationScreenProp<S> }
// | { screenProps: {} }
// | { navigationOptions: O }
// | {
// navigation: NavigationScreenProp<S>,
// screenProps: {},
// }
// | {
// navigation: NavigationScreenProp<S>,
// navigationOptions: O,
// }
// | {
// screenProps: {},
// navigationOptions: O,
// }
// | {
// navigation: NavigationScreenProp<S>,
// screenProps: {},
// navigationOptions: O,
// };
/**
* Navigation container
*/
@@ -578,12 +541,14 @@ declare module 'react-navigation' {
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,
}>;
@@ -737,10 +702,6 @@ declare module 'react-navigation' {
) => NavigationState,
};
declare export function addNavigationHelpers<S: {}>(
navigation: NavigationProp<S>
): NavigationScreenProp<S>;
declare export var NavigationActions: {
BACK: 'Navigation/BACK',
INIT: 'Navigation/INIT',
@@ -749,11 +710,11 @@ declare module 'react-navigation' {
SET_PARAMS: 'Navigation/SET_PARAMS',
URI: 'Navigation/URI',
back: {
(payload: { key?: ?string }): NavigationBackAction,
(payload?: { key?: ?string }): NavigationBackAction,
toString: () => string,
},
init: {
(payload: { params?: NavigationParams }): NavigationInitAction,
(payload?: { params?: NavigationParams }): NavigationInitAction,
toString: () => string,
},
navigate: {
@@ -783,36 +744,38 @@ declare module 'react-navigation' {
(payload: { uri: string }): NavigationUriAction,
toString: () => string,
},
mapDeprecatedActionAndWarn: (
action: PossiblyDeprecatedNavigationAction
) => NavigationAction,
};
declare type _RouterProp<S: NavigationState, O: {}> = {
router: NavigationRouter<S, O>,
};
declare type _NavigatorCreator<
NavigationViewProps: {},
S: NavigationState,
O: {}
> = (
NavigationView: React$ComponentType<_RouterProp<S, O> & NavigationViewProps>
) => NavigationNavigator<S, O, NavigationViewProps>;
declare export function createNavigator<
S: NavigationState,
O: {},
NavigatorConfig: {},
NavigationViewProps: NavigationNavigatorProps<O, S>
>(
declare type NavigationDescriptor = {
key: string,
state: NavigationLeafRoute | NavigationStateRoute,
navigation: NavigationScreenProp<*>,
getComponent: () => React$ComponentType<{}>,
};
declare type NavigationView<O, S> = React$ComponentType<{
descriptors: { [key: string]: NavigationDescriptor },
navigation: NavigationScreenProp<S>,
}>;
declare export function createNavigator<O: *, S: *, NavigatorConfig: *>(
view: NavigationView<O, S>,
router: NavigationRouter<S, O>,
routeConfigs?: NavigationRouteConfigMap,
navigatorConfig?: NavigatorConfig
): _NavigatorCreator<NavigationViewProps, S, O>;
): any;
declare export function StackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createStackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationContainer<*, *, *>;
declare type _TabViewConfig = {|
tabBarComponent?: React$ElementType,
@@ -829,20 +792,43 @@ declare module 'react-navigation' {
declare type _TabNavigatorConfig = {|
...NavigationTabRouterConfig,
..._TabViewConfig,
lazy?: boolean,
removeClippedSubviews?: boolean,
containerOptions?: void,
|};
declare export function TabNavigator(
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',
drawerWidth?: number | (() => number),
drawerPosition?: 'left' | 'right',
drawerOpenRoute?: string,
drawerCloseRoute?: string,
drawerToggleRoute?: string,
contentComponent?: React$ElementType,
contentOptions?: {},
style?: ViewStyleProp,
@@ -859,6 +845,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,
@@ -944,12 +934,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,
@@ -975,9 +967,6 @@ declare module 'react-navigation' {
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
drawerWidth: number | (() => number),
drawerPosition: 'left' | 'right',
drawerOpenRoute: string,
drawerCloseRoute: string,
drawerToggleRoute: string,
contentComponent: React$ElementType,
contentOptions?: {},
style?: ViewStyleProp,
@@ -1014,6 +1003,8 @@ declare module 'react-navigation' {
itemsContainerStyle?: ViewStyleProp,
itemStyle?: ViewStyleProp,
labelStyle?: TextStyleProp,
activeLabelStyle?: TextStyleProp,
inactiveLabelStyle?: TextStyleProp,
iconContainerStyle?: ViewStyleProp,
drawerPosition: 'left' | 'right',
};
@@ -1093,10 +1084,17 @@ declare module 'react-navigation' {
};
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
declare type _NavigationInjectedProps = {
navigation: NavigationScreenProp<NavigationState>,
};
declare export function withNavigation<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 }>>;
}

View File

@@ -1,14 +1,13 @@
{
"name": "react-navigation",
"version": "1.0.0",
"version": "2.0.2",
"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,37 +21,44 @@
"format": "eslint --fix .",
"precommit": "lint-staged"
},
"files": ["src"],
"files": [
"src"
],
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"clamp": "^1.0.1",
"create-react-context": "^0.2.1",
"hoist-non-react-statics": "^2.2.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.5.10",
"react-lifecycles-compat": "^3",
"react-native-drawer-layout-polyfill": "^1.3.2",
"react-native-tab-view": "^0.0.74"
"react-native-safe-area-view": "^0.7.0",
"react-navigation-deprecated-tab-navigator": "1.2.0",
"react-navigation-tabs": "0.2.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",
@@ -61,16 +67,36 @@
"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)"
]
},
"lint-staged": {
"*.js": ["eslint --fix", "git add"]
"*.js": [
"eslint --fix",
"prettier --write flow/react-navigation.js",
"git add"
]
}
}

View File

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

View File

@@ -1,27 +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 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,
};
@@ -29,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,
@@ -46,146 +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,
}));
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 mapDeprecatedNavigateAction = action => {
if (action.type === 'Navigate') {
const payload = {
routeName: action.routeName,
params: action.params,
};
if (action.action) {
payload.action = mapDeprecatedNavigateAction(action.action);
}
return navigate(payload);
}
return action;
};
const mapDeprecatedAction = action => {
if (action.type === 'Back') {
return back(action);
} else if (action.type === 'Init') {
return init(action);
} else if (action.type === 'Navigate') {
return mapDeprecatedNavigateAction(action);
} else if (action.type === 'Reset') {
return reset({
index: action.index,
key: action.key,
actions: action.actions.map(mapDeprecatedNavigateAction),
});
} else if (action.type === 'SetParams') {
return setParams(action);
}
return action;
};
const mapDeprecatedActionAndWarn = action => {
const newAction = mapDeprecatedAction(action);
if (newAction !== action) {
const oldType = action.type;
const newType = newAction.type;
console.warn(
[
`The action type '${oldType}' has been renamed to '${newType}'.`,
`'${oldType}' will continue to work while in beta but will be removed`,
'in the first major release. Moving forward, you should use the',
'action constants and action creators exported by this library in',
"the 'actions' object.",
'See https://github.com/react-community/react-navigation/pull/120 for',
'more details.',
].join(' ')
);
}
return newAction;
};
});
export default {
// Action constants
BACK,
INIT,
NAVIGATE,
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
SET_PARAMS,
URI,
COMPLETE_TRANSITION,
// Action creators
back,
init,
navigate,
pop,
popToTop,
push,
reset,
replace,
setParams,
uri,
completeTransition,
// TODO: Remove once old actions are deprecated
mapDeprecatedActionAndWarn,
};

View File

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

View File

@@ -1,9 +1,6 @@
export const Linking = {
addEventListener: () => {},
removeEventListener: () => {},
getInitialURL: () => Promise.reject('Unsupported platform'),
};
import React from 'react';
import { BackHandler, View } from 'react-native';
export const BackHandler = {
addEventListener: () => {},
};
const MaskedViewIOS = () => <View>{this.props.children}</View>;
export { BackHandler, MaskedViewIOS };

View File

@@ -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,8 +116,23 @@ 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.
*/
replaceAndPrune(state, key, route) {
const index = StateUtils.indexOf(state, key);
const replaced = StateUtils.replaceAtIndex(state, index, route);
return {
...replaced,
routes: replaced.routes.slice(0, index + 1),
};
},
/**
* Replace a route by a key.
* Note that this moves the index to the position to where the new route in the
* stack is at. Does not prune the routes.
*/
replaceAt(state, key, route) {
const index = StateUtils.indexOf(state, key);
@@ -137,7 +152,7 @@ const StateUtils = {
route.key
);
if (state.routes[index] === route) {
if (state.routes[index] === route && index === state.index) {
return state;
}
@@ -153,7 +168,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) {

View File

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

View File

@@ -1,46 +1,50 @@
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/StackNavigator';
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();
import createStackNavigator from '../navigators/createStackNavigator';
import { _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 NavigationContainer = createStackNavigator(
{
foo: {
screen: FooScreen,
},
bar: {
screen: BarScreen,
},
baz: {
screen: BazScreen,
},
car: {
screen: CarScreen,
},
dog: {
screen: DogScreen,
},
elk: {
screen: ElkScreen,
},
},
{
initialRouteName: 'foo',
}
);
describe('state.nav', () => {
it("should be preloaded with the router's initial state", () => {
const navigationContainer = renderer
@@ -198,4 +202,57 @@ describe('NavigationContainer', () => {
});
});
});
describe('warnings', () => {
function spyConsole() {
let spy = {};
beforeEach(() => {
spy.console = jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
spy.console.mockRestore();
});
return spy;
}
describe('detached navigators', () => {
beforeEach(() => {
_TESTING_ONLY_reset_container_count();
});
let spy = spyConsole();
it('warns when you render more than one navigator explicitly', () => {
class BlankScreen extends React.Component {
render() {
return <View />;
}
}
class RootScreen extends React.Component {
render() {
return (
<View>
<ChildNavigator />
</View>
);
}
}
const ChildNavigator = createStackNavigator({
Child: BlankScreen,
});
const RootStack = createStackNavigator({
Root: RootScreen,
});
renderer.create(<RootStack />).toJSON();
expect(spy).toMatchSnapshot();
});
});
});
});

View File

@@ -202,7 +202,7 @@ describe('StateUtils', () => {
).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 }],
@@ -210,7 +210,7 @@ describe('StateUtils', () => {
};
expect(
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
).toEqual(state);
).toEqual({ ...state, index: 1 });
});
// Reset

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NavigationContainer warnings detached navigators warns when you render more than one navigator explicitly 1`] = `
Object {
"console": [MockFunction] {
"calls": Array [
Array [
"You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: https://v2.reactnavigation.org/docs/common-mistakes.html#explicitly-rendering-more-than-one-navigator",
],
],
},
}
`;

View File

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

View File

@@ -11,10 +11,8 @@ test('child action events only flow when focused', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const testState = {
key: 'foo',
routeName: 'FooRoute',
@@ -66,11 +64,9 @@ test('grandchildren subscription', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const parentBlurState = {
key: 'foo',
routeName: 'FooRoute',
@@ -135,11 +131,9 @@ test('grandchildren transitions', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: 1,
isTransitioning: false,
@@ -230,11 +224,9 @@ test('grandchildren pass through transitions', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: childIndex,
isTransitioning: childIsTransitioning,
@@ -322,10 +314,8 @@ test('child focus with transition', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
@@ -417,10 +407,8 @@ test('child focus with immediate transition', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',

View File

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

View File

@@ -1,8 +1,54 @@
import React from 'react';
import { BackHandler, Linking } from './PlatformHelpers';
import { AsyncStorage, Linking, Platform } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';
import { BackHandler } from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant';
import getNavigationActionCreators from './routers/getNavigationActionCreators';
import docsUrl from './utils/docsUrl';
function isStateful(props) {
return !props.navigation;
}
function validateProps(props) {
if (isStateful(props)) {
return;
}
const { navigation, screenProps, ...containerProps } = props;
const keys = Object.keys(containerProps);
if (keys.length !== 0) {
throw new Error(
'This navigator has both navigation and container props, so it is ' +
`unclear if it should own its own state. Remove props: "${keys.join(
', '
)}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.'
);
}
}
// Track the number of stateful container instances. Warn if >0 and not using the
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
// stateful navigation containers in a future release and require a provider style pattern
// instead in order to eliminate confusion entirely.
let _statefulContainerCount = 0;
export function _TESTING_ONLY_reset_container_count() {
_statefulContainerCount = 0;
}
// We keep a global flag to catch errors during the state persistence hydrating scenario.
// The innermost navigator who catches the error will dispatch a new init action.
let _reactNavigationIsHydratingState = false;
// Unfortunate to use global state here, but it seems necessesary for the time
// being. There seems to be some problems with cascading componentDidCatch
// handlers. Ideally the inner non-stateful navigator catches the error and
// re-throws it, to be caught by the top-level stateful navigator.
/**
* Create an HOC that injects the navigation and manages the navigation state
@@ -17,23 +63,49 @@ 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();
if (this._isStateful()) {
this.subs = BackHandler.addEventListener('hardwareBackPress', () => {
if (!this._isMounted) {
this.subs && this.subs.remove();
} else {
// dispatch returns true if the action results in a state change,
// and false otherwise. This maps well to what BackHandler expects
// from a callback -- true if handled, false if not handled
return this.dispatch(NavigationActions.back());
}
});
}
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) {
@@ -112,77 +184,175 @@ 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;
}
this.subs = BackHandler.addEventListener('hardwareBackPress', () =>
this.dispatch(NavigationActions.back())
);
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 } = this.props;
const startupStateJSON =
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
const url = await Linking.getInitialURL();
const parsedUrl = url && this._urlToPathAndParams(url);
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());
}
}
_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 = inputAction => {
const action = NavigationActions.mapDeprecatedActionAndWarn(inputAction);
if (!this._isStateful()) {
return false;
dispatch = action => {
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;
};
@@ -190,9 +360,11 @@ export default function createNavigationContainer(Component) {
let navigation = this.props.navigation;
if (this._isStateful()) {
const nav = this.state.nav;
invariant(nav, 'should be set in constructor if stateful');
if (!nav) {
return this._renderLoading();
}
if (!this._navigation || this._navigation.state !== nav) {
this._navigation = addNavigationHelpers({
this._navigation = {
dispatch: this.dispatch,
state: nav,
addListener: (eventName, handler) => {
@@ -206,6 +378,11 @@ export default function createNavigationContainer(Component) {
},
};
},
};
const actionCreators = getNavigationActionCreators(nav);
Object.keys(actionCreators).forEach(actionName => {
this._navigation[actionName] = (...args) =>
this.dispatch(actionCreators[actionName](...args));
});
}
navigation = this._navigation;
@@ -215,5 +392,5 @@ export default function createNavigationContainer(Component) {
}
}
return NavigationContainer;
return polyfill(NavigationContainer);
}

View File

@@ -11,6 +11,18 @@ export default function getChildEventSubscriber(addListener, key) {
const willBlurSubscribers = new Set();
const didBlurSubscribers = new Set();
const removeAll = () => {
[
actionSubscribers,
willFocusSubscribers,
didFocusSubscribers,
willBlurSubscribers,
didBlurSubscribers,
].forEach(set => set.clear());
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const getChildSubscribers = evtName => {
switch (evtName) {
case 'action':
@@ -43,10 +55,6 @@ export default function getChildEventSubscriber(addListener, key) {
// considered blurred
let lastEmittedEvent = 'didBlur';
const cleanup = () => {
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const upstreamEvents = [
'willFocus',
'didFocus',
@@ -130,18 +138,24 @@ export default function getChildEventSubscriber(addListener, key) {
emit((lastEmittedEvent = 'didBlur'), childPayload);
}
}
if (lastEmittedEvent === 'didBlur' && !newRoute) {
removeAll();
}
})
);
return (eventName, eventHandler) => {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
return {
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
},
};
}

View File

@@ -1,55 +0,0 @@
import React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createNavigator from './createNavigator';
import CardStackTransitioner from '../views/CardStack/CardStackTransitioner';
import StackRouter from '../routers/StackRouter';
import NavigationActions from '../NavigationActions';
// A stack navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
export default (routeConfigMap, stackConfig = {}) => {
const {
initialRouteName,
initialRouteParams,
paths,
headerMode,
mode,
cardStyle,
transitionConfig,
onTransitionStart,
onTransitionEnd,
navigationOptions,
} = stackConfig;
const stackRouterConfig = {
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
// Create a navigator with CardStackTransitioner as the view
const navigator = createNavigator(router, routeConfigMap, stackConfig)(
props => (
<CardStackTransitioner
{...props}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}
transitionConfig={transitionConfig}
onTransitionStart={onTransitionStart}
onTransitionEnd={(lastTransition, transition) => {
const { state, dispatch } = props.navigation;
dispatch(NavigationActions.completeTransition({ key: state.key }));
onTransitionEnd && onTransitionEnd();
}}
/>
)
);
return createNavigationContainer(navigator);
};

View File

@@ -1,89 +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';
// A tab navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
const TabNavigator = (routeConfigs, config = {}) => {
// Use the look native to the platform by default
const mergedConfig = { ...TabNavigator.Presets.Default, ...config };
const {
tabBarComponent,
tabBarPosition,
tabBarOptions,
swipeEnabled,
animationEnabled,
configureTransition,
initialLayout,
...tabsConfig
} = mergedConfig;
const router = TabRouter(routeConfigs, tabsConfig);
const navigator = createNavigator(router, routeConfigs, config)(props => (
<TabView
{...props}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
configureTransition={configureTransition}
initialLayout={initialLayout}
/>
));
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;

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import DrawerNavigator from '../DrawerNavigator';
import DrawerNavigator from '../createDrawerNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({

View File

@@ -1,8 +1,16 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import renderer from 'react-test-renderer';
import StackNavigator from '../StackNavigator';
import StackNavigator from '../createStackNavigator';
import withNavigation from '../../views/withNavigation';
import { _TESTING_ONLY_reset_container_count } from '../../createNavigationContainer';
const styles = StyleSheet.create({
header: {
opacity: 0.5,
},
});
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
@@ -10,6 +18,7 @@ class HomeScreen extends Component {
navigation.state.params ? navigation.state.params.user : 'anonymous'
}`,
gesturesEnabled: true,
headerStyle: [{ backgroundColor: 'red' }, styles.header],
});
render() {
@@ -24,6 +33,10 @@ const routeConfig = {
};
describe('StackNavigator', () => {
beforeEach(() => {
_TESTING_ONLY_reset_container_count();
});
it('renders successfully', () => {
const MyStackNavigator = StackNavigator(routeConfig);
const rendered = renderer.create(<MyStackNavigator />).toJSON();
@@ -44,4 +57,37 @@ describe('StackNavigator', () => {
expect(rendered).toMatchSnapshot();
});
it('passes navigation to headerRight when wrapped in withNavigation', () => {
const spy = jest.fn();
class TestComponent extends React.Component {
render() {
return <View>{this.props.onPress(this.props.navigation)}</View>;
}
}
const TestComponentWithNavigation = withNavigation(TestComponent);
class A extends React.Component {
static navigationOptions = {
headerRight: <TestComponentWithNavigation onPress={spy} />,
};
render() {
return <View />;
}
}
const Nav = StackNavigator({ A: { screen: A } });
renderer.create(<Nav />);
expect(spy).toBeCalledWith(
expect.objectContaining({
navigate: expect.any(Function),
addListener: expect.any(Function),
})
);
});
});

View File

@@ -0,0 +1,18 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import SwitchNavigator from '../createSwitchNavigator';
const A = () => <View />;
const B = () => <View />;
const routeConfig = { A, B };
describe('SwitchNavigator', () => {
it('renders successfully', () => {
const MySwitchNavigator = SwitchNavigator(routeConfig);
const rendered = renderer.create(<MySwitchNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -2,7 +2,9 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import TabNavigator from '../TabNavigator';
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();

View File

@@ -140,6 +140,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"paddingBottom": 0,
@@ -187,6 +188,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "rgba(0, 0, 0, .04)",
@@ -222,6 +224,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
"color": "#2196f3",
},
undefined,
undefined,
]
}
>

View File

@@ -51,6 +51,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
@@ -75,70 +76,28 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
/>
</View>
<View
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
style={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
leftInterpolator={[Function]}
mode="float"
navigation={
Object {
"addListener": [Function],
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"pop": [Function],
"popToTop": [Function],
"push": [Function],
"replace": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"isTransitioning": false,
"key": "StackRouterRoot",
"routes": Array [
Object {
"key": "Init-id-0-1",
"routeName": "Home",
},
],
},
}
}
rightInterpolator={[Function]}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
titleInterpolator={[Function]}
transitionConfig={undefined}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "rgba(0, 0, 0, .3)",
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
@@ -146,6 +105,17 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
}
}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
@@ -155,18 +125,14 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
>
<View
style={
Array [
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
Object {
"flexDirection": "row",
},
]
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
@@ -177,17 +143,13 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 70,
"opacity": 1,
"position": "absolute",
"right": 70,
"top": 0,
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
>
@@ -203,7 +165,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "600",
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
@@ -220,7 +182,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"justifyContent": "center",
"flexDirection": "row",
"opacity": 1,
"position": "absolute",
"right": 0,
@@ -289,6 +251,7 @@ exports[`StackNavigator renders successfully 1`] = `
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
@@ -313,70 +276,28 @@ exports[`StackNavigator renders successfully 1`] = `
/>
</View>
<View
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
style={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
leftInterpolator={[Function]}
mode="float"
navigation={
Object {
"addListener": [Function],
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"pop": [Function],
"popToTop": [Function],
"push": [Function],
"replace": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"isTransitioning": false,
"key": "StackRouterRoot",
"routes": Array [
Object {
"key": "Init-id-0-0",
"routeName": "Home",
},
],
},
}
}
rightInterpolator={[Function]}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
titleInterpolator={[Function]}
transitionConfig={undefined}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "rgba(0, 0, 0, .3)",
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
@@ -384,6 +305,17 @@ exports[`StackNavigator renders successfully 1`] = `
}
}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
@@ -393,18 +325,14 @@ exports[`StackNavigator renders successfully 1`] = `
>
<View
style={
Array [
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
Object {
"flexDirection": "row",
},
]
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
@@ -415,17 +343,13 @@ exports[`StackNavigator renders successfully 1`] = `
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
>
@@ -441,7 +365,7 @@ exports[`StackNavigator renders successfully 1`] = `
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "600",
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}

View File

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

View File

@@ -55,13 +55,23 @@ exports[`TabNavigator renders successfully 1`] = `
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
/>
>
<View
style={
Object {
"flex": 1,
}
}
/>
</View>
</View>
</View>
<View
@@ -71,6 +81,7 @@ exports[`TabNavigator renders successfully 1`] = `
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
@@ -126,9 +137,15 @@ exports[`TabNavigator renders successfully 1`] = `
>
<View
style={
Object {
"flexGrow": 1,
}
Array [
Object {
"height": 29,
},
false,
Object {
"flex": 1,
},
]
}
>
<View
@@ -136,13 +153,13 @@ exports[`TabNavigator renders successfully 1`] = `
style={
Object {
"alignItems": "center",
"bottom": 0,
"alignSelf": "center",
"height": "100%",
"justifyContent": "center",
"left": 0,
"minWidth": 30,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
"width": "100%",
}
}
/>
@@ -151,13 +168,13 @@ exports[`TabNavigator renders successfully 1`] = `
style={
Object {
"alignItems": "center",
"bottom": 0,
"alignSelf": "center",
"height": "100%",
"justifyContent": "center",
"left": 0,
"minWidth": 30,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
"width": "100%",
}
}
/>
@@ -167,6 +184,7 @@ exports[`TabNavigator renders successfully 1`] = `
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
style={
Object {
"backgroundColor": "transparent",

View File

@@ -1,13 +1,13 @@
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 TabRouter from '../routers/TabRouter';
import DrawerRouter from '../routers/DrawerRouter';
import DrawerScreen from '../views/Drawer/DrawerScreen';
import DrawerView from '../views/Drawer/DrawerView';
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
import SafeAreaView from '../views/SafeAreaView';
// A stack navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
@@ -38,9 +38,6 @@ const DefaultDrawerConfig = {
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: defaultContentComponent,
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
drawerPosition: 'left',
drawerBackgroundColor: 'white',
useNativeAnimations: true,
@@ -48,58 +45,25 @@ const DefaultDrawerConfig = {
const DrawerNavigator = (routeConfigs, config = {}) => {
const mergedConfig = { ...DefaultDrawerConfig, ...config };
const {
containerConfig,
drawerWidth,
drawerLockMode,
contentComponent,
contentOptions,
drawerPosition,
useNativeAnimations,
drawerBackgroundColor,
drawerOpenRoute,
drawerCloseRoute,
drawerToggleRoute,
...tabsConfig
order,
paths,
initialRouteName,
backBehavior,
...drawerConfig
} = mergedConfig;
const contentRouter = TabRouter(routeConfigs, tabsConfig);
const drawerRouter = TabRouter(
{
[drawerCloseRoute]: {
screen: createNavigator(contentRouter, routeConfigs, config)(props => (
<DrawerScreen {...props} />
)),
},
[drawerOpenRoute]: {
screen: () => null,
},
[drawerToggleRoute]: {
screen: () => null,
},
},
{
initialRouteName: drawerCloseRoute,
}
);
const routerConfig = {
order,
paths,
initialRouteName,
backBehavior,
};
const navigator = createNavigator(drawerRouter, routeConfigs, config)(
props => (
<DrawerView
{...props}
drawerBackgroundColor={drawerBackgroundColor}
drawerLockMode={drawerLockMode}
useNativeAnimations={useNativeAnimations}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
drawerOpenRoute={drawerOpenRoute}
drawerCloseRoute={drawerCloseRoute}
drawerToggleRoute={drawerToggleRoute}
/>
)
);
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
return createNavigationContainer(navigator);
};

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { TextInput } from 'react-native';
export default Navigator =>
class KeyboardAwareNavigator extends React.Component {
static router = Navigator.router;
_previouslyFocusedTextInput = null;
render() {
return (
<Navigator
{...this.props}
onGestureBegin={this._handleGestureBegin}
onGestureCanceled={this._handleGestureCanceled}
onGestureFinish={this._handleGestureFinish}
onTransitionStart={this._handleTransitionStart}
/>
);
}
_handleGestureBegin = () => {
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
if (this._previouslyFocusedTextInput) {
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
}
this.props.onGestureBegin && this.props.onGestureBegin();
};
_handleGestureCanceled = () => {
if (this._previouslyFocusedTextInput) {
TextInput.State.focusTextInput(this._previouslyFocusedTextInput);
}
this.props.onGestureFinish && this.props.onGestureFinish();
};
_handleGestureFinish = () => {
this._previouslyFocusedTextInput = null;
this.props.onGestureCanceled && this.props.onGestureCanceled();
};
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
// TODO: We should not even have received the transition start event
// in the case where the index did not change, I believe. We
// should revisit this after 2.0 release.
if (transitionProps.index !== prevTransitionProps.index) {
const currentField = TextInput.State.currentlyFocusedField();
if (currentField) {
TextInput.State.blurTextInput(currentField);
}
}
this.props.onTransitionStart &&
this.props.onTransitionStart(transitionProps, prevTransitionProps);
};
};

View File

@@ -1,19 +1,109 @@
import React from 'react';
/**
* Creates a navigator based on a router and a view that renders the screens.
*/
export default function createNavigator(router, routeConfigs, navigatorConfig) {
return NavigationView => {
class Navigator extends React.Component {
static router = router;
static navigationOptions = null;
import getChildEventSubscriber from '../getChildEventSubscriber';
render() {
return <NavigationView {...this.props} router={router} />;
}
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)) {
delete this.childEventSubscribers[key];
}
});
}
return Navigator;
};
// Remove all subscription references
componentWillUnmount() {
this.childEventSubscribers = {};
}
_isRouteFocused = route => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
_dangerouslyGetParent = () => {
return this.props.navigation;
};
render() {
const { navigation, screenProps } = this.props;
const { dispatch, state, addListener } = navigation;
const { routes } = state;
const descriptors = {};
routes.forEach(route => {
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
if (!this.childEventSubscribers[route.key]) {
this.childEventSubscribers[route.key] = getChildEventSubscriber(
addListener,
route.key
);
}
const actionCreators = {
...navigation.actions,
...router.getActionCreators(route, state.key),
};
const actionHelpers = {};
Object.keys(actionCreators).forEach(actionName => {
actionHelpers[actionName] = (...args) => {
const actionCreator = actionCreators[actionName];
const action = actionCreator(...args);
dispatch(action);
};
});
const childNavigation = {
...actionHelpers,
actions: actionCreators,
dispatch,
state: route,
isFocused: () => this._isRouteFocused(route),
dangerouslyGetParent: this._dangerouslyGetParent,
addListener: this.childEventSubscribers[route.key].addListener,
getParam: (paramName, defaultValue) => {
const params = route.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
};
const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = {
key: route.key,
getComponent,
options,
state: route,
navigation: childNavigation,
};
});
return (
<NavigatorView
{...this.props}
screenProps={screenProps}
navigation={navigation}
navigationConfig={navigationConfig}
descriptors={descriptors}
/>
);
}
}
return Navigator;
}
export default createNavigator;

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createKeyboardAwareNavigator from './createKeyboardAwareNavigator';
import createNavigator from './createNavigator';
import StackView from '../views/StackView/StackView';
import StackRouter from '../routers/StackRouter';
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
disableKeyboardHandling,
} = stackConfig;
const stackRouterConfig = {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
// Create a navigator with StackView as the view
let Navigator = createNavigator(StackView, router, stackConfig);
if (!disableKeyboardHandling) {
Navigator = createKeyboardAwareNavigator(Navigator);
}
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
return createNavigationContainer(Navigator);
}
export default createStackNavigator;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createNavigator from '../navigators/createNavigator';
import SwitchRouter from '../routers/SwitchRouter';
import SwitchView from '../views/SwitchView/SwitchView';
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
const router = SwitchRouter(routeConfigMap, switchConfig);
const Navigator = createNavigator(SwitchView, router, switchConfig);
return createNavigationContainer(Navigator);
}
export default createSwitchNavigator;

View File

@@ -8,25 +8,68 @@ module.exports = {
get StateUtils() {
return require('./StateUtils').default;
},
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() {
return require('./navigators/createNavigator').default;
},
get StackNavigator() {
return require('./navigators/StackNavigator').default;
get createStackNavigator() {
return require('./navigators/createStackNavigator').default;
},
get TabNavigator() {
return require('./navigators/TabNavigator').default;
get StackNavigator() {
console.warn(
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
);
return require('./navigators/createStackNavigator').default;
},
get createSwitchNavigator() {
return require('./navigators/createSwitchNavigator').default;
},
get SwitchNavigator() {
console.warn(
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
);
return require('./navigators/createSwitchNavigator').default;
},
get createDrawerNavigator() {
return require('./navigators/createDrawerNavigator').default;
},
get DrawerNavigator() {
return require('./navigators/DrawerNavigator').default;
console.warn(
'The DrawerNavigator function name is deprecated, please use createDrawerNavigator instead'
);
return require('./navigators/createDrawerNavigator').default;
},
get createTabNavigator() {
console.warn(
'createTabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
);
return require('react-navigation-deprecated-tab-navigator')
.createTabNavigator;
},
get TabNavigator() {
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('./routers/DrawerActions').default;
},
// Routers
@@ -36,22 +79,28 @@ module.exports = {
get TabRouter() {
return require('./routers/TabRouter').default;
},
get SwitchRouter() {
return require('./routers/SwitchRouter').default;
},
// Views
get Transitioner() {
return require('./views/Transitioner').default;
},
get CardStackTransitioner() {
return require('./views/CardStack/CardStackTransitioner').default;
get StackView() {
return require('./views/StackView/StackView').default;
},
get CardStack() {
return require('./views/CardStack/CardStack').default;
},
get Card() {
return require('./views/CardStack/Card').default;
get StackViewCard() {
return require('./views/StackView/StackViewCard').default;
},
get SafeAreaView() {
return require('./views/SafeAreaView').default;
return require('react-native-safe-area-view').default;
},
get SceneView() {
return require('./views/SceneView').default;
},
get ResourceSavingSceneView() {
return require('./views/ResourceSavingSceneView').default;
},
// Header
@@ -75,17 +124,37 @@ module.exports = {
// 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;
},
// HOCs
get withNavigation() {
return require('./views/withNavigation').default;
},
get withNavigationFocus() {
return require('./views/withNavigationFocus').default;
},
get withOrientation() {
return require('./views/withOrientation').default;
},
};

View File

@@ -8,18 +8,23 @@ module.exports = {
get StateUtils() {
return require('./StateUtils').default;
},
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() {
return require('./navigators/createNavigator').default;
},
// Actions
get NavigationActions() {
return require('./NavigationActions').default;
},
get StackActions() {
return require('./routers/StackActions').default;
},
get DrawerActions() {
return require('./routers/DrawerActions').default;
},
// Routers
get StackRouter() {
return require('./routers/StackRouter').default;
@@ -27,9 +32,15 @@ module.exports = {
get TabRouter() {
return require('./routers/TabRouter').default;
},
get SwitchRouter() {
return require('./routers/SwitchRouter').default;
},
// HOCs
get withNavigation() {
return require('./views/withNavigation').default;
},
get withNavigationFocus() {
return require('./views/withNavigationFocus').default;
},
};

View File

@@ -0,0 +1,28 @@
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
const openDrawer = payload => ({
type: OPEN_DRAWER,
...payload,
});
const closeDrawer = payload => ({
type: CLOSE_DRAWER,
...payload,
});
const toggleDrawer = payload => ({
type: TOGGLE_DRAWER,
...payload,
});
export default {
OPEN_DRAWER,
CLOSE_DRAWER,
TOGGLE_DRAWER,
openDrawer,
closeDrawer,
toggleDrawer,
};

View File

@@ -0,0 +1,85 @@
import SwitchRouter from './SwitchRouter';
import NavigationActions from '../NavigationActions';
import invariant from '../utils/invariant';
import withDefaultValue from '../utils/withDefaultValue';
import DrawerActions from './DrawerActions';
export default (routeConfigs, config = {}) => {
config = { ...config };
config = withDefaultValue(config, 'resetOnBlur', false);
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
const switchRouter = SwitchRouter(routeConfigs, config);
return {
...switchRouter,
getActionCreators(route, navStateKey) {
return {
openDrawer: () => DrawerActions.openDrawer({ key: navStateKey }),
closeDrawer: () => DrawerActions.closeDrawer({ key: navStateKey }),
toggleDrawer: () => DrawerActions.toggleDrawer({ key: navStateKey }),
...switchRouter.getActionCreators(route, navStateKey),
};
},
getStateForAction(action, lastState) {
const state = lastState || {
...switchRouter.getStateForAction(action, undefined),
isDrawerOpen: false,
};
const isRouterTargeted = action.key == null || action.key === state.key;
if (isRouterTargeted) {
// Only handle actions that are meant for this drawer, as specified by action.key.
if (action.type === DrawerActions.CLOSE_DRAWER && state.isDrawerOpen) {
return {
...state,
isDrawerOpen: false,
};
}
if (action.type === DrawerActions.OPEN_DRAWER && !state.isDrawerOpen) {
return {
...state,
isDrawerOpen: true,
};
}
if (action.type === DrawerActions.TOGGLE_DRAWER) {
return {
...state,
isDrawerOpen: !state.isDrawerOpen,
};
}
}
// Fall back on switch router for screen switching logic, and handling of child routers
const switchedState = switchRouter.getStateForAction(action, state);
if (switchedState === null) {
// The switch router or a child router is attempting to swallow this action. We return null to allow this.
return null;
}
if (switchedState !== state) {
if (switchedState.index !== state.index) {
// If the tabs have changed, make sure to close the drawer
return {
...switchedState,
isDrawerOpen: false,
};
}
// Return the state new state, as returned by the switch router.
// The index hasn't changed, so this most likely means that a child router has returned a new state
return switchedState;
}
return state;
},
};
};

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

View File

@@ -1,13 +1,14 @@
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 getScreenConfigDeprecated from './getScreenConfigDeprecated';
import invariant from '../utils/invariant';
import { generateKey } from './KeyGenerator';
import getNavigationActionCreators from './getNavigationActionCreators';
function isEmpty(obj) {
if (!obj) return true;
@@ -20,10 +21,16 @@ function isEmpty(obj) {
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);
@@ -44,6 +51,8 @@ export default (routeConfigs, stackConfig = {}) => {
});
const { initialRouteParams } = stackConfig;
const getCustomActionCreators =
stackConfig.getCustomActionCreators || defaultActionCreators;
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
@@ -51,6 +60,62 @@ export default (routeConfigs, stackConfig = {}) => {
const pathsByRouteNames = { ...stackConfig.paths } || {};
let paths = [];
function getInitialState(action) {
let route = {};
const childRouter = childRouters[action.routeName];
// This is a push-like action, and childRouter will be a router or null if we are responsible for this routeName
if (behavesLikePushAction(action) && childRouter !== undefined) {
let childState = {};
// The router is null for normal leaf routes
if (childRouter !== null) {
const childAction =
action.action || NavigationActions.init({ params: action.params });
childState = childRouter.getStateForAction(childAction);
}
return {
key: 'StackRouterRoot',
isTransitioning: false,
index: 0,
routes: [
{
params: action.params,
...childState,
key: action.key || generateKey(),
routeName: action.routeName,
},
],
};
}
if (initialChildRouter) {
route = initialChildRouter.getStateForAction(
NavigationActions.navigate({
routeName: initialRouteName,
params: initialRouteParams,
})
);
}
const params = (route.params || action.params || initialRouteParams) && {
...(route.params || {}),
...(action.params || {}),
...(initialRouteParams || {}),
};
const { initialRouteKey } = stackConfig;
route = {
...route,
...(params ? { params } : {}),
routeName: initialRouteName,
key: action.key || (initialRouteKey || generateKey()),
};
return {
key: 'StackRouterRoot',
isTransitioning: false,
index: 0,
routes: [route],
};
}
// Build paths for each route
routeNames.forEach(routeName => {
let pathPattern =
@@ -81,7 +146,7 @@ export default (routeConfigs, stackConfig = {}) => {
});
paths = Object.entries(pathsByRouteNames);
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
paths.sort((a, b) => b[1].priority - a[1].priority);
return {
getComponentForState(state) {
@@ -97,59 +162,75 @@ export default (routeConfigs, stackConfig = {}) => {
return getScreenForRouteName(routeConfigs, routeName);
},
getActionCreators(route, navStateKey) {
return {
...getNavigationActionCreators(route),
...getCustomActionCreators(route, navStateKey),
pop: (n, params) =>
StackActions.pop({
n,
...params,
}),
popToTop: params => StackActions.popToTop(params),
push: (routeName, params, action) =>
StackActions.push({
routeName,
params,
action,
}),
replace: (replaceWith, params, action, newKey) => {
if (typeof replaceWith === 'string') {
return StackActions.replace({
routeName: replaceWith,
params,
action,
key: route.key,
newKey,
});
}
invariant(
typeof replaceWith === 'object',
'Must replaceWith an object or a string'
);
invariant(
params == null,
'Params must not be provided to .replace() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .replace() when specifying an object'
);
invariant(
newKey == null,
'Child action must not be provided to .replace() when specifying an object'
);
return StackActions.replace(replaceWith);
},
reset: (actions, index) =>
StackActions.reset({
actions,
index: index == null ? actions.length - 1 : index,
key: navStateKey,
}),
dismiss: () =>
NavigationActions.back({
key: navStateKey,
}),
};
},
getStateForAction(action, state) {
// Set up the initial state if needed
if (!state) {
let route = {};
if (
behavesLikePushAction(action) &&
childRouters[action.routeName] !== undefined
) {
return {
key: 'StackRouterRoot',
isTransitioning: false,
index: 0,
routes: [
{
routeName: action.routeName,
params: action.params,
key: `Init-${generateKey()}`,
},
],
};
}
if (initialChildRouter) {
route = initialChildRouter.getStateForAction(
NavigationActions.navigate({
routeName: initialRouteName,
params: initialRouteParams,
})
);
}
const params = (route.params ||
action.params ||
initialRouteParams) && {
...(route.params || {}),
...(action.params || {}),
...(initialRouteParams || {}),
};
route = {
...route,
routeName: initialRouteName,
key: `Init-${generateKey()}`,
...(params ? { params } : {}),
};
// eslint-disable-next-line no-param-reassign
state = {
key: 'StackRouterRoot',
isTransitioning: false,
index: 0,
routes: [route],
};
return getInitialState(action);
}
// Check if a 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) {
// Check if the focused child scene wants to handle the action, as long as
// it is not a reset to the root stack
if (
!isResetToRootStack(action) &&
action.type !== NavigationActions.NAVIGATE
) {
const keyIndex = action.key
? StateUtils.indexOf(state, action.key)
: -1;
@@ -169,51 +250,35 @@ export default (routeConfigs, stackConfig = {}) => {
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;
// 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 (state.index === 0) {
return {
...state,
};
} else {
return {
...state,
isTransitioning: action.immediate !== true,
index: 0,
routes: [state.routes[0]],
};
}
return state;
}
// Handle replace action
if (action.type === NavigationActions.REPLACE) {
const 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];
let childState = {};
if (childRouter) {
const childAction =
action.action ||
NavigationActions.init({ params: action.params });
childState = childRouter.getStateForAction(childAction);
const nextRouteState = childRouter.getStateForAction(
childAction,
childRoute
);
if (nextRouteState === null || nextRouteState !== childRoute) {
return StateUtils.replaceAndPrune(
state,
nextRouteState ? nextRouteState.key : childRoute.key,
nextRouteState ? nextRouteState : childRoute
);
}
}
const routes = [...state.routes];
routes[routeIndex] = {
params: action.params,
// merge the child state in this order to allow params override
...childState,
key: action.newKey || generateKey(),
routeName: action.routeName,
};
return { ...state, routes };
}
}
// Handle explicit push navigation action. Make sure this happens after children have had a chance to handle the action
// Handle explicit push navigation action. This must happen after the
// focused child router has had a chance to handle the action.
if (
behavesLikePushAction(action) &&
childRouters[action.routeName] !== undefined
@@ -222,44 +287,52 @@ 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;
}
const routes = [...state.routes];
// 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 isTransitioning only if index has changed
return {
...state,
isTransitioning:
state.index !== lastRouteIndex
? action.immediate !== true
: undefined,
index: lastRouteIndex,
routes,
// 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;
}
});
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;
}
// 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,
};
}
const key = action.key || generateKey();
if (childRouter) {
const childAction =
action.action || NavigationActions.init({ params: action.params });
@@ -267,14 +340,14 @@ export default (routeConfigs, stackConfig = {}) => {
params: action.params,
// merge the child state in this order to allow params override
...childRouter.getStateForAction(childAction),
key,
routeName: action.routeName,
key: action.key || generateKey(),
};
} else {
route = {
params: action.params,
key,
routeName: action.routeName,
key: action.key || generateKey(),
};
}
return {
@@ -282,23 +355,11 @@ export default (routeConfigs, stackConfig = {}) => {
isTransitioning: action.immediate !== true,
};
} else if (
action.type === NavigationActions.PUSH &&
action.type === StackActions.PUSH &&
childRouters[action.routeName] === undefined
) {
return {
...state,
};
}
if (
action.type === NavigationActions.COMPLETE_TRANSITION &&
(action.key == null || action.key === state.key) &&
state.isTransitioning
) {
return {
...state,
isTransitioning: false,
};
// Return the state identity to bubble the action up
return state;
}
// Handle navigation to other child routers that are not yet pushed
@@ -326,16 +387,75 @@ export default (routeConfigs, stackConfig = {}) => {
routeToPush = navigatedChildRoute;
}
if (routeToPush) {
return StateUtils.push(state, {
const route = {
...routeToPush,
key: generateKey(),
routeName: childRouterName,
});
key: action.key || generateKey(),
};
return StateUtils.push(state, route);
}
}
}
}
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
if (action.type === StackActions.POP_TO_TOP) {
// Refuse to handle pop to top if a key is given that doesn't correspond
// to this router
if (action.key && state.key !== action.key) {
return state;
}
// If we're already at the top, then we return the state with a new
// identity so that the action is handled by this router.
if (state.index > 0) {
return {
...state,
isTransitioning: action.immediate !== true,
index: 0,
routes: [state.routes[0]],
};
}
return state;
}
// Handle replace action
if (action.type === StackActions.REPLACE) {
const 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];
let childState = {};
if (childRouter) {
const childAction =
action.action ||
NavigationActions.init({ params: action.params });
childState = childRouter.getStateForAction(childAction);
}
const routes = [...state.routes];
routes[routeIndex] = {
params: action.params,
// merge the child state in this order to allow params override
...childState,
routeName: action.routeName,
key: action.newKey || generateKey(),
};
return { ...state, routes };
}
}
// Update transitioning state
if (
action.type === StackActions.COMPLETE_TRANSITION &&
(action.key == null || action.key === state.key) &&
state.isTransitioning
) {
return {
...state,
isTransitioning: false,
};
}
if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key;
const lastRoute = state.routes.find(route => route.key === key);
@@ -356,33 +476,36 @@ 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
// undefined on either the state or the action
return state;
}
const resetAction = action;
const newStackActions = action.actions;
return {
...state,
routes: resetAction.actions.map(childAction => {
const router = childRouters[childAction.routeName];
routes: newStackActions.map(newStackAction => {
const router = childRouters[newStackAction.routeName];
let childState = {};
if (router) {
return {
...childAction,
...router.getStateForAction(childAction),
routeName: childAction.routeName,
key: generateKey(),
};
const childAction =
newStackAction.action ||
NavigationActions.init({ params: newStackAction.params });
childState = router.getStateForAction(childAction);
}
const route = {
...childAction,
key: generateKey(),
return {
params: newStackAction.params,
...childState,
routeName: newStackAction.routeName,
key: newStackAction.key || generateKey(),
};
delete route.type;
return route;
}),
index: action.index,
};
@@ -390,11 +513,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);
@@ -410,15 +533,9 @@ export default (routeConfigs, stackConfig = {}) => {
index: backRouteIndex - 1,
isTransitioning: immediate !== true,
};
} else if (
backRouteIndex === 0 &&
action.type === NavigationActions.POP
) {
return {
...state,
};
}
}
return state;
},
@@ -449,6 +566,7 @@ export default (routeConfigs, stackConfig = {}) => {
if (!pathToResolve) {
return NavigationActions.navigate({
routeName: initialRouteName,
params: inputParams,
});
}
@@ -519,9 +637,17 @@ export default (routeConfigs, stackConfig = {}) => {
if (key.asterisk || !key) {
return result;
}
const nextResult = result || {};
const nextResult = result || inputParams || {};
const paramName = key.name;
nextResult[paramName] = matchResult;
let decodedMatchResult;
try {
decodedMatchResult = decodeURIComponent(matchResult);
} catch (e) {
// ignore `URIError: malformed URI`
}
nextResult[paramName] = decodedMatchResult || matchResult;
return nextResult;
}, queryParams);
@@ -536,7 +662,5 @@ export default (routeConfigs, stackConfig = {}) => {
routeConfigs,
stackConfig.navigationOptions
),
getScreenConfig: getScreenConfigDeprecated,
};
};

388
src/routers/SwitchRouter.js Normal file
View File

@@ -0,0 +1,388 @@
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import validateRouteConfigMap from './validateRouteConfigMap';
import getNavigationActionCreators from './getNavigationActionCreators';
const defaultActionCreators = (route, navStateKey) => ({});
function childrenUpdateWithoutSwitchingIndex(actionType) {
return [
NavigationActions.SET_PARAMS,
// Todo: make SwitchRouter not depend on StackActions..
StackActions.COMPLETE_TRANSITION,
].includes(actionType);
}
export default (routeConfigs, config = {}) => {
// Fail fast on invalid route definitions
validateRouteConfigMap(routeConfigs);
const order = config.order || Object.keys(routeConfigs);
const paths = config.paths || {};
const getCustomActionCreators =
config.getCustomActionCreators || defaultActionCreators;
const initialRouteParams = config.initialRouteParams;
const initialRouteName = config.initialRouteName || order[0];
const backBehavior = config.backBehavior || 'none';
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
? config.resetOnBlur
: true;
const initialRouteIndex = order.indexOf(initialRouteName);
const childRouters = {};
order.forEach(routeName => {
const routeConfig = routeConfigs[routeName];
if (!paths[routeName]) {
paths[routeName] =
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
}
childRouters[routeName] = null;
const screen = getScreenForRouteName(routeConfigs, routeName);
if (screen.router) {
childRouters[routeName] = screen.router;
}
});
if (initialRouteIndex === -1) {
throw new Error(
`Invalid initialRouteName '${initialRouteName}'.` +
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
);
}
function resetChildRoute(routeName) {
const params =
routeName === initialRouteName ? initialRouteParams : undefined;
const childRouter = childRouters[routeName];
if (childRouter) {
const childAction = NavigationActions.init();
return {
...childRouter.getStateForAction(childAction),
key: routeName,
routeName,
params,
};
}
return {
key: routeName,
routeName,
params,
};
}
return {
getInitialState() {
const routes = order.map(resetChildRoute);
return {
routes,
index: initialRouteIndex,
isTransitioning: false,
};
},
getNextState(prevState, possibleNextState) {
if (!prevState) {
return possibleNextState;
}
let nextState;
if (prevState.index !== possibleNextState.index && resetOnBlur) {
const prevRouteName = prevState.routes[prevState.index].routeName;
const nextRoutes = [...possibleNextState.routes];
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
return {
...possibleNextState,
routes: nextRoutes,
};
} else {
nextState = possibleNextState;
}
return nextState;
},
getActionCreators(route, stateKey) {
return {
...getNavigationActionCreators(route),
...getCustomActionCreators(route, stateKey),
};
},
getStateForAction(action, inputState) {
let prevState = inputState ? { ...inputState } : inputState;
let state = inputState || this.getInitialState();
let activeChildIndex = state.index;
if (action.type === NavigationActions.INIT) {
// NOTE(brentvatne): this seems weird... why are we merging these
// params into child routes?
// ---------------------------------------------------------------
// Merge any params from the action into all the child routes
const { params } = action;
if (params) {
state.routes = state.routes.map(route => ({
...route,
params: {
...route.params,
...params,
...(route.routeName === initialRouteName
? initialRouteParams
: null),
},
}));
}
}
// Let the current child handle it
const activeChildLastState = state.routes[state.index];
const activeChildRouter = childRouters[order[state.index]];
if (activeChildRouter) {
const activeChildState = activeChildRouter.getStateForAction(
action,
activeChildLastState
);
if (!activeChildState && inputState) {
return null;
}
if (activeChildState && activeChildState !== activeChildLastState) {
const routes = [...state.routes];
routes[state.index] = activeChildState;
return this.getNextState(prevState, {
...state,
routes,
});
}
}
// Handle tab changing. Do this after letting the current tab try to
// handle the action, to allow inner children to change first
const isBackEligible =
action.key == null || action.key === activeChildLastState.key;
if (action.type === NavigationActions.BACK) {
if (isBackEligible && shouldBackNavigateToInitialRoute) {
activeChildIndex = initialRouteIndex;
} else {
return state;
}
}
let didNavigate = false;
if (action.type === NavigationActions.NAVIGATE) {
didNavigate = !!order.find((childId, i) => {
if (childId === action.routeName) {
activeChildIndex = i;
return true;
}
return false;
});
if (didNavigate) {
const childState = state.routes[activeChildIndex];
const childRouter = childRouters[action.routeName];
let newChildState;
if (action.action) {
newChildState = childRouter
? childRouter.getStateForAction(action.action, childState)
: null;
} else if (!action.action && !childRouter && action.params) {
newChildState = {
...childState,
params: {
...(childState.params || {}),
...action.params,
},
};
}
if (newChildState && newChildState !== childState) {
const routes = [...state.routes];
routes[activeChildIndex] = newChildState;
return this.getNextState(prevState, {
...state,
routes,
index: activeChildIndex,
});
} else if (
!newChildState &&
state.index === activeChildIndex &&
prevState
) {
return null;
}
}
}
if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key;
const lastRoute = state.routes.find(route => route.key === key);
if (lastRoute) {
const params = {
...lastRoute.params,
...action.params,
};
const routes = [...state.routes];
routes[state.routes.indexOf(lastRoute)] = {
...lastRoute,
params,
};
return this.getNextState(prevState, {
...state,
routes,
});
}
}
if (activeChildIndex !== state.index) {
return this.getNextState(prevState, {
...state,
index: activeChildIndex,
});
} else if (didNavigate && !inputState) {
return state;
} else if (didNavigate) {
return { ...state };
}
// Let other children handle it and switch to the first child that returns a new state
let index = state.index;
let routes = state.routes;
order.find((childId, i) => {
const childRouter = childRouters[childId];
if (i === index) {
return false;
}
let childState = routes[i];
if (childRouter) {
childState = childRouter.getStateForAction(action, childState);
}
if (!childState) {
index = i;
return true;
}
if (childState !== routes[i]) {
routes = [...routes];
routes[i] = childState;
index = i;
return true;
}
return false;
});
// Nested routers can be updated after switching children with actions such as SET_PARAMS
// and COMPLETE_TRANSITION.
// NOTE: This may be problematic with custom routers because we whitelist the actions
// that can be handled by child routers without automatically changing index.
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
index = state.index;
}
if (index !== state.index || routes !== state.routes) {
return this.getNextState(prevState, {
...state,
index,
routes,
});
}
return state;
},
getComponentForState(state) {
const routeName = state.routes[state.index].routeName;
invariant(
routeName,
`There is no route defined for index ${state.index}. Check that
that you passed in a navigation state with a valid tab/screen index.`
);
const childRouter = childRouters[routeName];
if (childRouter) {
return childRouter.getComponentForState(state.routes[state.index]);
}
return getScreenForRouteName(routeConfigs, routeName);
},
getComponentForRouteName(routeName) {
return getScreenForRouteName(routeConfigs, routeName);
},
getPathAndParamsForState(state) {
const route = state.routes[state.index];
const routeName = order[state.index];
const subPath = paths[routeName];
const screen = getScreenForRouteName(routeConfigs, routeName);
let path = subPath;
let params = route.params;
if (screen && screen.router) {
const stateRoute = route;
// If it has a router it's a navigator.
// If it doesn't have router it's an ordinary React component.
const child = screen.router.getPathAndParamsForState(stateRoute);
path = subPath ? `${subPath}/${child.path}` : child.path;
params = child.params ? { ...params, ...child.params } : params;
}
return {
path,
params,
};
},
/**
* Gets an optional action, based on a relative path and query params.
*
* This will return null if there is no action matched
*/
getActionForPathAndParams(path, params) {
if (!path) {
return NavigationActions.navigate({
routeName: initialRouteName,
params,
});
}
return (
order
.map(childId => {
const parts = path.split('/');
const pathToTest = paths[childId];
const partsInTestPath = pathToTest.split('/').length;
const pathPartsToTest = parts.slice(0, partsInTestPath).join('/');
if (pathPartsToTest === pathToTest) {
const childRouter = childRouters[childId];
const action = NavigationActions.navigate({
routeName: childId,
});
if (childRouter && childRouter.getActionForPathAndParams) {
action.action = childRouter.getActionForPathAndParams(
parts.slice(partsInTestPath).join('/'),
params
);
}
if (params) {
action.params = params;
}
return action;
}
return null;
})
.find(action => !!action) ||
order
.map(childId => {
const childRouter = childRouters[childId];
return (
childRouter && childRouter.getActionForPathAndParams(path, params)
);
})
.find(action => !!action) ||
null
);
},
getScreenOptions: createConfigGetter(
routeConfigs,
config.navigationOptions
),
};
};

View File

@@ -1,317 +1,11 @@
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions';
import validateRouteConfigMap from './validateRouteConfigMap';
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
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;
if (routeConfig.screen && routeConfig.screen.router) {
tabRouters[routeName] = routeConfig.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,
isTransitioning: false,
};
// 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});
// keep active tab index if action type is SET_PARAMS
index =
action.type === NavigationActions.SET_PARAMS ? state.index : index;
if (index !== state.index || routes !== state.routes) {
return {
...state,
index,
routes,
};
}
return state;
},
getComponentForState(state) {
const routeName = order[state.index];
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
),
getScreenConfig: getScreenConfigDeprecated,
};
const switchRouter = SwitchRouter(routeConfigs, config);
return switchRouter;
};

View File

@@ -0,0 +1,139 @@
/* eslint react/display-name:0 */
import React from 'react';
import DrawerRouter from '../DrawerRouter';
import NavigationActions from '../../NavigationActions';
import DrawerActions from '../../routers/DrawerActions';
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,
isTransitioning: false,
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,
isTransitioning: false,
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: DrawerActions.OPEN_DRAWER },
state
);
expect(state2.isDrawerOpen).toEqual(true);
const state3 = router.getStateForAction(
{ type: DrawerActions.CLOSE_DRAWER },
state2
);
expect(state3.isDrawerOpen).toEqual(false);
const state4 = router.getStateForAction(
{ type: DrawerActions.TOGGLE_DRAWER },
state3
);
expect(state4.isDrawerOpen).toEqual(true);
});
test('Drawer opens closes with key targeted', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
const router = DrawerRouter({
Foo: { screen: ScreenA },
Bar: { screen: ScreenB },
});
const state = router.getStateForAction(INIT_ACTION);
const state2 = router.getStateForAction(
{ type: DrawerActions.OPEN_DRAWER, key: 'wrong' },
state
);
expect(state2.isDrawerOpen).toEqual(false);
const state3 = router.getStateForAction(
{ type: DrawerActions.OPEN_DRAWER, key: state.key },
state2
);
expect(state3.isDrawerOpen).toEqual(true);
});
});
test('Nested routers bubble up blocked actions', () => {
const ScreenA = () => <div />;
ScreenA.router = {
getStateForAction(action, lastState) {
if (action.type === 'CHILD_ACTION') return null;
return lastState;
},
};
const ScreenB = () => <div />;
const router = DrawerRouter({
Foo: { screen: ScreenA },
Bar: { screen: ScreenB },
});
const state = router.getStateForAction(INIT_ACTION);
const state2 = router.getStateForAction({ type: 'CHILD_ACTION' }, state);
expect(state2).toEqual(null);
});
test('Drawer stays open when child routers return new state', () => {
const ScreenA = () => <div />;
ScreenA.router = {
getStateForAction(action, lastState = { changed: false }) {
if (action.type === 'CHILD_ACTION')
return { ...lastState, changed: true };
return lastState;
},
};
const router = DrawerRouter({
Foo: { screen: ScreenA },
});
const state = router.getStateForAction(INIT_ACTION);
expect(state.isDrawerOpen).toEqual(false);
const state2 = router.getStateForAction(
{ type: DrawerActions.OPEN_DRAWER, key: state.key },
state
);
expect(state2.isDrawerOpen).toEqual(true);
const state3 = router.getStateForAction({ type: 'CHILD_ACTION' }, state2);
expect(state3.isDrawerOpen).toEqual(true);
expect(state3.routes[0].changed).toEqual(true);
});

View File

@@ -1,19 +1,24 @@
/* 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(() => {
_TESTING_ONLY_normalize_keys();
});
const ROUTERS = {
TabRouter,
StackRouter,
};
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
const dummyEventSubscriber = (name, handler) => ({
remove: () => {},
});
@@ -53,31 +58,31 @@ 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');
@@ -85,6 +90,75 @@ Object.keys(ROUTERS).forEach(routerName => {
});
});
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 />;
@@ -105,8 +179,8 @@ test('Handles no-op actions with tabs within stack router', () => {
type: NavigationActions.NAVIGATE,
routeName: 'Qux',
});
expect(state1.routes[0].key).toEqual('Init-id-0-0');
expect(state2.routes[0].key).toEqual('Init-id-0-1');
expect(state1.routes[0].key).toEqual('id-0');
expect(state2.routes[0].key).toEqual('id-1');
state1.routes[0].key = state2.routes[0].key;
expect(state1).toEqual(state2);
const state3 = TestRouter.getStateForAction(
@@ -134,7 +208,7 @@ test('Handles deep action', () => {
key: 'StackRouterRoot',
routes: [
{
key: 'Init-id-0-2',
key: 'id-0',
routeName: 'Bar',
},
],
@@ -153,6 +227,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 />;
@@ -174,8 +312,8 @@ test('Supports lazily-evaluated getScreen', () => {
immediate: true,
routeName: 'Qux',
});
expect(state1.routes[0].key).toEqual('Init-id-0-4');
expect(state2.routes[0].key).toEqual('Init-id-0-5');
expect(state1.routes[0].key).toEqual('id-0');
expect(state2.routes[0].key).toEqual('id-1');
state1.routes[0].key = state2.routes[0].key;
expect(state1).toEqual(state2);
const state3 = TestRouter.getStateForAction(
@@ -188,3 +326,119 @@ test('Supports lazily-evaluated getScreen', () => {
);
expect(state2).toEqual(state3);
});
test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION or SET_PARAMS', () => {
const FooStackNavigator = () => <div />;
const BarView = () => <div />;
FooStackNavigator.router = StackRouter({
Foo: {
screen: BarView,
},
Bar: {
screen: BarView,
},
});
const TestRouter = TabRouter({
Zap: { screen: FooStackNavigator },
Zoo: { screen: FooStackNavigator },
});
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
// Navigate to the second screen in the first tab
const state2 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state1
);
// Switch tabs
const state3 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Zoo',
},
state2
);
const stateAfterCompleteTransition = TestRouter.getStateForAction(
{
type: NavigationActions.COMPLETE_TRANSITION,
key: state2.routes[0].key,
},
state3
);
const stateAfterSetParams = TestRouter.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
key: state1.routes[0].routes[0].key,
params: { key: 'value' },
},
state3
);
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

View File

@@ -0,0 +1,252 @@
/* eslint react/display-name:0 */
import React from 'react';
import SwitchRouter from '../SwitchRouter';
import StackRouter from '../StackRouter';
import NavigationActions from '../../NavigationActions';
describe('SwitchRouter', () => {
test('resets the route when unfocusing a tab by default', () => {
const router = getExampleRouter();
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
state
);
expect(state2.routes[0].index).toEqual(1);
expect(state2.routes[0].routes.length).toEqual(2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state2
);
expect(state3.routes[0].index).toEqual(0);
expect(state3.routes[0].routes.length).toEqual(1);
});
test('does not reset the route on unfocus if resetOnBlur is false', () => {
const router = getExampleRouter({ resetOnBlur: false });
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
state
);
expect(state2.routes[0].index).toEqual(1);
expect(state2.routes[0].routes.length).toEqual(2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state2
);
expect(state3.routes[0].index).toEqual(1);
expect(state3.routes[0].routes.length).toEqual(2);
});
test('ignores back by default', () => {
const router = getExampleRouter();
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state
);
expect(state2.index).toEqual(1);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2
);
expect(state3.index).toEqual(1);
});
test('handles back if given a backBehavior', () => {
const router = getExampleRouter({ backBehavior: 'initialRoute' });
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state
);
expect(state2.index).toEqual(1);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2
);
expect(state3.index).toEqual(0);
});
test('paths option on SwitchRouter overrides path from route config', () => {
const router = getExampleRouter({ paths: { A: 'overridden' } });
const action = router.getActionForPathAndParams('overridden', {});
expect(action.type).toEqual(NavigationActions.NAVIGATE);
expect(action.routeName).toEqual('A');
});
test('provides correct action for getActionForPathAndParams', () => {
const router = getExampleRouter({ backBehavior: 'initialRoute' });
const action = router.getActionForPathAndParams('A1', { foo: 'bar' });
expect(action.type).toEqual(NavigationActions.NAVIGATE);
expect(action.routeName).toEqual('A1');
const action1 = router.getActionForPathAndParams('', {});
expect(action1.type).toEqual(NavigationActions.NAVIGATE);
expect(action1.routeName).toEqual('A');
const action2 = router.getActionForPathAndParams(null, {});
expect(action2.type).toEqual(NavigationActions.NAVIGATE);
expect(action2.routeName).toEqual('A');
const action3 = router.getActionForPathAndParams('great/path', {
foo: 'baz',
});
expect(action3).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'B',
params: { foo: 'baz' },
action: {
type: NavigationActions.NAVIGATE,
routeName: 'B1',
params: { foo: 'baz' },
},
});
const action4 = router.getActionForPathAndParams('great/path/B2', {
foo: 'baz',
});
expect(action4).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'B',
params: { foo: 'baz' },
action: {
type: NavigationActions.NAVIGATE,
routeName: 'B2',
params: { foo: 'baz' },
},
});
});
test('order of handling navigate action is correct for nested switchrouters', () => {
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
// if we are focused on Other and navigate to Bar, what should happen?
const Screen = () => <div />;
const NestedSwitch = () => <div />;
const OtherNestedSwitch = () => <div />;
let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
let otherNestedRouter = SwitchRouter({ Foo: Screen });
NestedSwitch.router = nestedRouter;
OtherNestedSwitch.router = otherNestedRouter;
let router = SwitchRouter(
{
NestedSwitch,
OtherNestedSwitch,
Bar: Screen,
},
{
initialRouteName: 'OtherNestedSwitch',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Bar');
const state3 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'NestedSwitch',
},
state2
);
const state4 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state3
);
let activeState4 = state4.routes[state4.index];
expect(activeState4.routeName).toEqual('NestedSwitch');
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
});
// https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
test('order of handling navigate action is correct for nested stackrouters', () => {
const Screen = () => <div />;
const MainStack = () => <div />;
const LoginStack = () => <div />;
MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
let router = SwitchRouter(
{
Home: Screen,
Login: LoginStack,
Main: MainStack,
},
{
initialRouteName: 'Login',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('Login');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Home',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Home');
});
});
const getExampleRouter = (config = {}) => {
const PlainScreen = () => <div />;
const StackA = () => <div />;
const StackB = () => <div />;
StackA.router = StackRouter({
A1: PlainScreen,
A2: PlainScreen,
});
StackB.router = StackRouter({
B1: PlainScreen,
B2: PlainScreen,
});
const router = SwitchRouter(
{
A: {
screen: StackA,
path: '',
},
B: {
screen: StackB,
path: 'great/path',
},
},
{
initialRouteName: 'A',
...config,
}
);
return router;
};

View File

@@ -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' },
@@ -593,29 +637,6 @@ describe('TabRouter', () => {
expect(path).toEqual('f/Baz');
});
test('Maps old actions (uses "getStateForAction returns null when navigating to same tab" test)', () => {
global.console.warn = jest.fn();
const router = TabRouter(
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
{ initialRouteName: 'Bar' }
);
const initAction = NavigationActions.mapDeprecatedActionAndWarn({
type: 'Init',
});
const state = router.getStateForAction(initAction);
const navigateAction = NavigationActions.mapDeprecatedActionAndWarn({
type: 'Navigate',
routeName: 'Bar',
});
const state2 = router.getStateForAction(navigateAction, state);
expect(state2).toEqual(null);
expect(console.warn).toBeCalledWith(
expect.stringContaining(
"The action type 'Init' has been renamed to 'Navigation/INIT'"
)
);
});
test('Can navigate to other tab (no router) with params', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
@@ -713,56 +734,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 },
});
@@ -771,10 +751,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,
@@ -800,7 +780,6 @@ describe('TabRouter', () => {
routeName: 'Boo',
action: NavigationActions.navigate({ routeName: 'Zoo' }),
});
const expectedState = ScreenA.router.getStateForAction(
action,
screenApreState
@@ -808,8 +787,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)
);
});
});

View File

@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateRouteConfigMap Fails if both screen and getScreen are defined 1`] = `"Route 'Home' should declare a screen or a getScreen, not both."`;
exports[`validateRouteConfigMap Fails on bad object 1`] = `
"The component for route 'Home' must be a React component. For example:
import MyScreen from './MyScreen';
...
Home: MyScreen,
}
You can also use a navigator:
import MyNavigator from './MyNavigator';
...
Home: MyNavigator,
}"
`;
exports[`validateRouteConfigMap Fails on empty bare screen 1`] = `
"The component for route 'Home' must be a React component. For example:
import MyScreen from './MyScreen';
...
Home: MyScreen,
}
You can also use a navigator:
import MyNavigator from './MyNavigator';
...
Home: MyNavigator,
}"
`;
exports[`validateRouteConfigMap Fails on empty config 1`] = `"Please specify at least one route when configuring a navigator."`;

View File

@@ -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,34 +163,14 @@ test('should throw if the route does not exist', () => {
expect(() =>
getScreenOptions(
addNavigationHelpers({
{
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
}),
},
{}
)
).toThrowError(
"There is no route defined for key Settings.\nMust be one of: 'Home'"
);
});
test('should throw if the screen is not defined under the route config', () => {
/* eslint-disable react/no-multi-comp */
const getScreenOptions = createConfigGetter({
Home: {},
});
const routes = [{ key: 'B', routeName: 'Home' }];
expect(() =>
getScreenOptions(
addNavigationHelpers({
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
})
)
).toThrowError('Route Home must define a screen or a getScreen.');
});

View File

@@ -13,9 +13,19 @@ ProfileNavigator.router = StackRouter({
});
describe('validateRouteConfigMap', () => {
test('Fails on empty bare screen', () => {
const invalidMap = {
Home: undefined,
};
expect(() =>
validateRouteConfigMap(invalidMap)
).toThrowErrorMatchingSnapshot();
});
test('Fails on empty config', () => {
const invalidMap = {};
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
expect(() =>
validateRouteConfigMap(invalidMap)
).toThrowErrorMatchingSnapshot();
});
test('Fails on bad object', () => {
const invalidMap = {
@@ -23,7 +33,9 @@ describe('validateRouteConfigMap', () => {
foo: 'bar',
},
};
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
expect(() =>
validateRouteConfigMap(invalidMap)
).toThrowErrorMatchingSnapshot();
});
test('Fails if both screen and getScreen are defined', () => {
const invalidMap = {
@@ -32,17 +44,17 @@ describe('validateRouteConfigMap', () => {
getScreen: () => ListScreen,
},
};
expect(() => validateRouteConfigMap(invalidMap)).toThrow();
expect(() =>
validateRouteConfigMap(invalidMap)
).toThrowErrorMatchingSnapshot();
});
test('Succeeds on a valid config', () => {
const invalidMap = {
const validMap = {
Home: {
screen: ProfileNavigator,
},
Chat: {
screen: ListScreen,
},
Chat: ListScreen,
};
validateRouteConfigMap(invalidMap);
validateRouteConfigMap(validMap);
});
});

View File

@@ -1,9 +1,7 @@
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import addNavigationHelpers from '../addNavigationHelpers';
import validateScreenOptions from './validateScreenOptions';
import getChildEventSubscriber from '../getChildEventSubscriber';
function applyConfig(configurer, navigationOptions, configProps) {
if (typeof configurer === 'function') {
@@ -38,40 +36,15 @@ export default (routeConfigs, navigatorScreenConfig) => (
const Component = getScreenForRouteName(routeConfigs, route.routeName);
let outputConfig = {};
const router = Component.router;
if (router) {
const { routes, index } = route;
if (!route || !routes || index == null) {
throw new Error(
`Expect nav state to have routes and index, ${JSON.stringify(route)}`
);
}
const childRoute = routes[index];
const childNavigation = addNavigationHelpers({
state: childRoute,
dispatch,
addListener: getChildEventSubscriber(
navigation.addListener,
childRoute.key
),
});
outputConfig = router.getScreenOptions(childNavigation, screenProps);
}
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 || {} };
outputConfig = applyConfig(
navigatorScreenConfig,
outputConfig,
configOptions
);
let outputConfig = applyConfig(navigatorScreenConfig, {}, configOptions);
outputConfig = applyConfig(
componentScreenConfig,
outputConfig,

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

View File

@@ -1,7 +0,0 @@
import invariant from '../utils/invariant';
export default () =>
invariant(
false,
'`getScreenConfig` has been replaced with `getScreenOptions`'
);

View File

@@ -32,5 +32,5 @@ export default function getScreenForRouteName(routeConfigs, routeName) {
return screen;
}
throw new Error(`Route ${routeName} must define a screen or a getScreen.`);
return routeConfig;
}

View File

@@ -13,46 +13,44 @@ function validateRouteConfigMap(routeConfigs) {
routeNames.forEach(routeName => {
const routeConfig = routeConfigs[routeName];
if (!routeConfig.screen && !routeConfig.getScreen) {
throw new Error(
`Route '${routeName}' should declare a screen. ` +
'For example:\n\n' +
"import MyScreen from './MyScreen';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
'}'
);
} else if (routeConfig.screen && routeConfig.getScreen) {
throw new Error(
`Route '${routeName}' should declare a screen or ` +
'a getScreen, not both.'
);
}
const screenComponent = getScreenComponent(routeConfig);
if (
routeConfig.screen &&
typeof routeConfig.screen !== 'function' &&
typeof routeConfig.screen !== 'string'
!screenComponent ||
(typeof screenComponent !== 'function' &&
typeof screenComponent !== 'string' &&
!routeConfig.getScreen)
) {
throw new Error(
`The component for route '${routeName}' must be a ` +
'React component. For example:\n\n' +
"import MyScreen from './MyScreen';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
`${routeName}: MyScreen,\n` +
'}\n\n' +
'You can also use a navigator:\n\n' +
"import MyNavigator from './MyNavigator';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyNavigator,\n' +
`${routeName}: MyNavigator,\n` +
'}'
);
}
if (routeConfig.screen && routeConfig.getScreen) {
throw new Error(
`Route '${routeName}' should declare a screen or ` +
'a getScreen, not both.'
);
}
});
}
function getScreenComponent(routeConfig) {
if (!routeConfig) {
return null;
}
return routeConfig.screen ? routeConfig.screen : routeConfig;
}
export default validateRouteConfigMap;

3
src/utils/docsUrl.js Normal file
View File

@@ -0,0 +1,3 @@
export default function docsUrl(path) {
return `https://v2.reactnavigation.org/docs/${path}`;
}

View File

@@ -0,0 +1,8 @@
export default (obj, key, defaultValue) => {
if (obj.hasOwnProperty(key)) {
return obj;
}
obj[key] = defaultValue;
return obj;
};

View File

@@ -1,435 +0,0 @@
import React from 'react';
import clamp from 'clamp';
import {
Animated,
StyleSheet,
PanResponder,
Platform,
View,
I18nManager,
Easing,
} from 'react-native';
import Card from './Card';
import Header from '../Header/Header';
import NavigationActions from '../../NavigationActions';
import addNavigationHelpers from '../../addNavigationHelpers';
import getChildEventSubscriber from '../../getChildEventSubscriber';
import SceneView from '../SceneView';
import TransitionConfigs from './TransitionConfigs';
const emptyFunction = () => {};
const EaseInOut = Easing.inOut(Easing.ease);
/**
* The max duration of the card animation in milliseconds after released gesture.
* The actual duration should be always less then that because the rest distance
* is always less then the full distance of the layout.
*/
const ANIMATION_DURATION = 500;
/**
* The gesture distance threshold to trigger the back behavior. For instance,
* `1/2` means that moving greater than 1/2 of the width of the screen will
* trigger a back action
*/
const POSITION_THRESHOLD = 1 / 2;
/**
* The threshold (in pixels) to start the gesture action.
*/
const RESPOND_THRESHOLD = 20;
/**
* The distance of touch start from the edge of the screen where the gesture will be recognized
*/
const GESTURE_RESPONSE_DISTANCE_HORIZONTAL = 25;
const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
const animatedSubscribeValue = animatedValue => {
if (!animatedValue.__isNative) {
return;
}
if (Object.keys(animatedValue._listeners).length === 0) {
animatedValue.addListener(emptyFunction);
}
};
class CardStack extends React.Component {
/**
* Used to identify the starting point of the position when the gesture starts, such that it can
* be updated according to its relative position. This means that a card can effectively be
* "caught"- If a gesture starts while a card is animating, the card does not jump into a
* corresponding location for the touch.
*/
_gestureStartValue = 0;
// tracks if a touch is currently happening
_isResponding = false;
/**
* immediateIndex is used to represent the expected index that we will be on after a
* transition. To achieve a smooth animation when swiping back, the action to go back
* doesn't actually fire until the transition completes. The immediateIndex is used during
* the transition so that gestures can be handled correctly. This is a work-around for
* cases when the user quickly swipes back several times.
*/
_immediateIndex = null;
_screenDetails = {};
componentWillReceiveProps(props) {
if (props.screenProps !== this.props.screenProps) {
this._screenDetails = {};
}
props.scenes.forEach(newScene => {
if (
this._screenDetails[newScene.key] &&
this._screenDetails[newScene.key].state !== newScene.route
) {
this._screenDetails[newScene.key] = null;
}
});
}
_getScreenDetails = scene => {
const { screenProps, navigation, router } = this.props;
let screenDetails = this._screenDetails[scene.key];
if (!screenDetails || screenDetails.state !== scene.route) {
const screenNavigation = addNavigationHelpers({
dispatch: navigation.dispatch,
state: scene.route,
addListener: getChildEventSubscriber(
navigation.addListener,
scene.route.key
),
});
screenDetails = {
state: scene.route,
navigation: screenNavigation,
options: router.getScreenOptions(screenNavigation, screenProps),
};
this._screenDetails[scene.key] = screenDetails;
}
return screenDetails;
};
_renderHeader(scene, headerMode) {
const { header } = this._getScreenDetails(scene).options;
if (typeof header !== 'undefined' && typeof header !== 'function') {
return header;
}
const renderHeader = header || (props => <Header {...props} />);
const {
headerLeftInterpolator,
headerTitleInterpolator,
headerRightInterpolator,
} = this._getTransitionConfig();
const { mode, ...passProps } = this.props;
return renderHeader({
...passProps,
scene,
mode: headerMode,
getScreenDetails: this._getScreenDetails,
leftInterpolator: headerLeftInterpolator,
titleInterpolator: headerTitleInterpolator,
rightInterpolator: headerRightInterpolator,
});
}
// eslint-disable-next-line class-methods-use-this
_animatedSubscribe(props) {
// Hack to make this work with native driven animations. We add a single listener
// so the JS value of the following animated values gets updated. We rely on
// some Animated private APIs and not doing so would require using a bunch of
// value listeners but we'd have to remove them to not leak and I'm not sure
// when we'd do that with the current structure we have. `stopAnimation` callback
// is also broken with native animated values that have no listeners so if we
// want to remove this we have to fix this too.
animatedSubscribeValue(props.layout.width);
animatedSubscribeValue(props.layout.height);
animatedSubscribeValue(props.position);
}
_reset(resetToIndex, duration) {
Animated.timing(this.props.position, {
toValue: resetToIndex,
duration,
easing: EaseInOut,
useNativeDriver: this.props.position.__isNative,
}).start();
}
_goBack(backFromIndex, duration) {
const { navigation, position, scenes } = this.props;
const toValue = Math.max(backFromIndex - 1, 0);
// set temporary index for gesture handler to respect until the action is
// dispatched at the end of the transition.
this._immediateIndex = toValue;
Animated.timing(position, {
toValue,
duration,
easing: EaseInOut,
useNativeDriver: position.__isNative,
}).start(() => {
this._immediateIndex = null;
const backFromScene = scenes.find(s => s.index === toValue + 1);
if (!this._isResponding && backFromScene) {
navigation.dispatch(
NavigationActions.back({
key: backFromScene.route.key,
immediate: true,
})
);
}
});
}
render() {
let floatingHeader = null;
const headerMode = this._getHeaderMode();
if (headerMode === 'float') {
floatingHeader = this._renderHeader(this.props.scene, headerMode);
}
const { navigation, position, layout, scene, scenes, mode } = this.props;
const { index } = navigation.state;
const isVertical = mode === 'modal';
const { options } = this._getScreenDetails(scene);
const gestureDirectionInverted = options.gestureDirection === 'inverted';
const responder = PanResponder.create({
onPanResponderTerminate: () => {
this._isResponding = false;
this._reset(index, 0);
},
onPanResponderGrant: () => {
position.stopAnimation(value => {
this._isResponding = true;
this._gestureStartValue = value;
});
},
onMoveShouldSetPanResponder: (event, gesture) => {
if (index !== scene.index) {
return false;
}
const immediateIndex =
this._immediateIndex == null ? index : this._immediateIndex;
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
const currentDragPosition =
event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
const axisLength = isVertical
? layout.height.__getValue()
: layout.width.__getValue();
const axisHasBeenMeasured = !!axisLength;
// Measure the distance from the touch to the edge of the screen
const screenEdgeDistance = gestureDirectionInverted
? axisLength - (currentDragPosition - currentDragDistance)
: currentDragPosition - currentDragDistance;
// Compare to the gesture distance relavant to card or modal
const {
gestureResponseDistance: userGestureResponseDistance = {},
} = this._getScreenDetails(scene).options;
const gestureResponseDistance = isVertical
? userGestureResponseDistance.vertical ||
GESTURE_RESPONSE_DISTANCE_VERTICAL
: userGestureResponseDistance.horizontal ||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
// GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
if (screenEdgeDistance > gestureResponseDistance) {
// Reject touches that started in the middle of the screen
return false;
}
const hasDraggedEnough =
Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
const isOnFirstCard = immediateIndex === 0;
const shouldSetResponder =
hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
return shouldSetResponder;
},
onPanResponderMove: (event, gesture) => {
// Handle the moving touches for our granted responder
const startValue = this._gestureStartValue;
const axis = isVertical ? 'dy' : 'dx';
const axisDistance = isVertical
? layout.height.__getValue()
: layout.width.__getValue();
const currentValue =
(I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
? startValue + gesture[axis] / axisDistance
: startValue - gesture[axis] / axisDistance;
const value = clamp(index - 1, currentValue, index);
position.setValue(value);
},
onPanResponderTerminationRequest: () =>
// Returning false will prevent other views from becoming responder while
// the navigation view is the responder (mid-gesture)
false,
onPanResponderRelease: (event, gesture) => {
if (!this._isResponding) {
return;
}
this._isResponding = false;
const immediateIndex =
this._immediateIndex == null ? index : this._immediateIndex;
// Calculate animate duration according to gesture speed and moved distance
const axisDistance = isVertical
? layout.height.__getValue()
: layout.width.__getValue();
const movementDirection = gestureDirectionInverted ? -1 : 1;
const movedDistance =
movementDirection * gesture[isVertical ? 'dy' : 'dx'];
const gestureVelocity =
movementDirection * gesture[isVertical ? 'vy' : 'vx'];
const defaultVelocity = axisDistance / ANIMATION_DURATION;
const velocity = Math.max(Math.abs(gestureVelocity), defaultVelocity);
const resetDuration = gestureDirectionInverted
? (axisDistance - movedDistance) / velocity
: movedDistance / velocity;
const goBackDuration = gestureDirectionInverted
? movedDistance / velocity
: (axisDistance - movedDistance) / velocity;
// To asyncronously get the current animated value, we need to run stopAnimation:
position.stopAnimation(value => {
// If the speed of the gesture release is significant, use that as the indication
// of intent
if (gestureVelocity < -0.5) {
this._reset(immediateIndex, resetDuration);
return;
}
if (gestureVelocity > 0.5) {
this._goBack(immediateIndex, goBackDuration);
return;
}
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
// and the back will happen.
if (value <= index - POSITION_THRESHOLD) {
this._goBack(immediateIndex, goBackDuration);
} else {
this._reset(immediateIndex, resetDuration);
}
});
},
});
const gesturesEnabled =
typeof options.gesturesEnabled === 'boolean'
? options.gesturesEnabled
: Platform.OS === 'ios';
const handlers = gesturesEnabled ? responder.panHandlers : {};
const containerStyle = [
styles.container,
this._getTransitionConfig().containerStyle,
];
return (
<View {...handlers} style={containerStyle}>
<View style={styles.scenes}>
{scenes.map(s => this._renderCard(s))}
</View>
{floatingHeader}
</View>
);
}
_getHeaderMode() {
if (this.props.headerMode) {
return this.props.headerMode;
}
if (Platform.OS === 'android' || this.props.mode === 'modal') {
return 'screen';
}
return 'float';
}
_renderInnerScene(SceneComponent, scene) {
const { navigation } = this._getScreenDetails(scene);
const { screenProps } = this.props;
const headerMode = this._getHeaderMode();
if (headerMode === 'screen') {
return (
<View style={styles.container}>
<View style={{ flex: 1 }}>
<SceneView
screenProps={screenProps}
navigation={navigation}
component={SceneComponent}
/>
</View>
{this._renderHeader(scene, headerMode)}
</View>
);
}
return (
<SceneView
screenProps={this.props.screenProps}
navigation={navigation}
component={SceneComponent}
/>
);
}
_getTransitionConfig = () => {
const isModal = this.props.mode === 'modal';
return TransitionConfigs.getTransitionConfig(
this.props.transitionConfig,
{},
{},
isModal
);
};
_renderCard = scene => {
const { screenInterpolator } = this._getTransitionConfig();
const style =
screenInterpolator && screenInterpolator({ ...this.props, scene });
const SceneComponent = this.props.router.getComponentForRouteName(
scene.route.routeName
);
return (
<Card
{...this.props}
key={`card_${scene.key}`}
style={[style, this.props.cardStyle]}
scene={scene}
>
{this._renderInnerScene(SceneComponent, scene)}
</Card>
);
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
// Header is physically rendered after scenes so that Header won't be
// covered by the shadows of the scenes.
// That said, we'd have use `flexDirection: 'column-reverse'` to move
// Header above the scenes.
flexDirection: 'column-reverse',
},
scenes: {
flex: 1,
},
});
export default CardStack;

View File

@@ -1,79 +0,0 @@
import React from 'react';
import { NativeModules } from 'react-native';
import CardStack from './CardStack';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
import Transitioner from '../Transitioner';
import TransitionConfigs from './TransitionConfigs';
const NativeAnimatedModule =
NativeModules && NativeModules.NativeAnimatedModule;
class CardStackTransitioner extends React.Component {
static defaultProps = {
mode: 'card',
};
render() {
return (
<Transitioner
configureTransition={this._configureTransition}
navigation={this.props.navigation}
render={this._render}
onTransitionStart={this.props.onTransitionStart}
onTransitionEnd={this.props.onTransitionEnd}
/>
);
}
_configureTransition = (
// props for the new screen
transitionProps,
// props for the old screen
prevTransitionProps
) => {
const isModal = this.props.mode === 'modal';
// Copy the object so we can assign useNativeDriver below
const transitionSpec = {
...TransitionConfigs.getTransitionConfig(
this.props.transitionConfig,
transitionProps,
prevTransitionProps,
isModal
).transitionSpec,
};
if (
!!NativeAnimatedModule &&
// Native animation support also depends on the transforms used:
CardStackStyleInterpolator.canUseNativeDriver()
) {
// Internal undocumented prop
transitionSpec.useNativeDriver = true;
}
return transitionSpec;
};
_render = props => {
const {
screenProps,
headerMode,
mode,
router,
cardStyle,
transitionConfig,
} = this.props;
return (
<CardStack
screenProps={screenProps}
headerMode={headerMode}
mode={mode}
router={router}
cardStyle={cardStyle}
transitionConfig={transitionConfig}
{...props}
/>
);
};
}
export default CardStackTransitioner;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, Platform, StyleSheet } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import SafeAreaView from '../SafeAreaView';
import TouchableItem from '../TouchableItem';
/**
@@ -21,6 +21,8 @@ const DrawerNavigatorItems = ({
itemsContainerStyle,
itemStyle,
labelStyle,
activeLabelStyle,
inactiveLabelStyle,
iconContainerStyle,
drawerPosition,
}) => (
@@ -34,6 +36,7 @@ const DrawerNavigatorItems = ({
const scene = { route, index, focused, tintColor: color };
const icon = renderIcon(scene);
const label = getLabel(scene);
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
return (
<TouchableItem
key={route.key}
@@ -63,7 +66,9 @@ const DrawerNavigatorItems = ({
</View>
) : null}
{typeof label === 'string' ? (
<Text style={[styles.label, { color }, labelStyle]}>
<Text
style={[styles.label, { color }, labelStyle, extraLabelStyle]}
>
{label}
</Text>
) : (

Some files were not shown because too many files have changed in this diff Show More