mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-19 18:38:16 +08:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54874969d | ||
|
|
ef0c8487b0 | ||
|
|
ea002fe950 | ||
|
|
41cf95941f | ||
|
|
884bde0338 | ||
|
|
31e07e9692 | ||
|
|
363f06d30e | ||
|
|
59c09a6040 | ||
|
|
c4b039cc19 | ||
|
|
49e3fb0d97 | ||
|
|
3367501d4d | ||
|
|
4fb6b163ac | ||
|
|
d0b4ba40e9 | ||
|
|
1be5c7ae35 | ||
|
|
bfa68ded1e | ||
|
|
2d3f159c2a | ||
|
|
e0fb642490 | ||
|
|
d341162731 | ||
|
|
4eec187d66 | ||
|
|
03be0324a3 | ||
|
|
55f326960a | ||
|
|
0641bdd656 | ||
|
|
087831ab71 | ||
|
|
23f7895792 | ||
|
|
cfa1eb2eff | ||
|
|
e34c724106 | ||
|
|
93a754a3d5 | ||
|
|
dcadbfcfa0 | ||
|
|
a393818875 | ||
|
|
f16ca93521 | ||
|
|
b3ab5ce23c | ||
|
|
b4da1a63ce | ||
|
|
7bf6404733 | ||
|
|
334be6021a | ||
|
|
4254d46694 | ||
|
|
a00784ae6e | ||
|
|
22f3422293 | ||
|
|
4502061a10 | ||
|
|
5a8ebc806c | ||
|
|
7942eecb4e | ||
|
|
6b3d6c1399 | ||
|
|
adcb2e5b4a | ||
|
|
352e703ea9 | ||
|
|
308d38015d | ||
|
|
f34095e71a | ||
|
|
000f8afe8f | ||
|
|
77c30d543b | ||
|
|
93ce7f4e9e | ||
|
|
e456d29104 | ||
|
|
eb54371306 | ||
|
|
a2e053d31c | ||
|
|
af18e1c672 | ||
|
|
0c4845f2ff | ||
|
|
0c9014e52f | ||
|
|
156f55fe34 | ||
|
|
46be835351 | ||
|
|
89b01f2211 | ||
|
|
ffae3efe42 | ||
|
|
ccfbaedb9f | ||
|
|
84de94d961 | ||
|
|
550d4fb676 | ||
|
|
5acc07b9b0 | ||
|
|
d34f883180 | ||
|
|
89af1a6917 | ||
|
|
402b201618 | ||
|
|
df1728e13b | ||
|
|
b331dea1a1 | ||
|
|
3e827e4061 | ||
|
|
4c168a7f73 | ||
|
|
200f596461 | ||
|
|
9e1982d8a8 | ||
|
|
51d791d301 | ||
|
|
07afa55265 | ||
|
|
3ac5f412b7 | ||
|
|
70a2c3b97c | ||
|
|
4bd6f17b46 | ||
|
|
9824e90b9f | ||
|
|
eae992467b | ||
|
|
6b4d92ca4d | ||
|
|
41d3c97cea | ||
|
|
ab3e053338 | ||
|
|
b14262c2ef | ||
|
|
03d9133a7d | ||
|
|
d0835351bd | ||
|
|
f892526e7b | ||
|
|
1afdb799fc | ||
|
|
83d36dcf7c | ||
|
|
aa94038190 | ||
|
|
0b698ae5d6 | ||
|
|
dd3ce66120 | ||
|
|
82754d41d9 | ||
|
|
9d54ec68dd | ||
|
|
460754fde1 | ||
|
|
ffd1865485 | ||
|
|
50320bf0d9 | ||
|
|
74a04c3ce5 | ||
|
|
54d0d5180d | ||
|
|
14eb5a1e75 | ||
|
|
222c77a360 | ||
|
|
39316fc339 | ||
|
|
27eb73cc14 | ||
|
|
f01b4896e6 | ||
|
|
556c31626e | ||
|
|
b6bca3ed2e | ||
|
|
0c56b21b46 | ||
|
|
912c7ca076 | ||
|
|
73c76f1e4b | ||
|
|
d746a587b0 | ||
|
|
dee03c839a | ||
|
|
2104bf1a04 | ||
|
|
4e2a409dca | ||
|
|
51bfe8dd19 | ||
|
|
04a4512c1b | ||
|
|
4a5da86ce0 | ||
|
|
a118122aed | ||
|
|
a94f89ffe1 | ||
|
|
9d77fd6d54 | ||
|
|
13cf4497ee | ||
|
|
9175118383 | ||
|
|
6fc21250ec | ||
|
|
714d5eab6b | ||
|
|
67233dc9ef | ||
|
|
b0443c1861 | ||
|
|
c0b637df52 | ||
|
|
9a82706fba | ||
|
|
d973a26edb | ||
|
|
852e7e1974 | ||
|
|
cd3707d64b | ||
|
|
3c36db455f | ||
|
|
ec52c884c5 | ||
|
|
c4b3f25a0f | ||
|
|
93642e16e7 | ||
|
|
1a76556290 | ||
|
|
12b21f052e | ||
|
|
c1f07dc167 | ||
|
|
bc04b31d01 | ||
|
|
35307c70be | ||
|
|
7e3f4f3bec | ||
|
|
cbd0958e6f | ||
|
|
cab4d71a5e | ||
|
|
108ac0e2a9 | ||
|
|
fa4fdb9c57 | ||
|
|
ebdd2da79f | ||
|
|
1fe11c100e | ||
|
|
c4b84f1d66 | ||
|
|
69f394be5b | ||
|
|
316e4991ac | ||
|
|
805064cb5e | ||
|
|
8f199980cb | ||
|
|
37ca6a92ca | ||
|
|
980e0409dc | ||
|
|
a00ba5918a | ||
|
|
ad6b25cff9 | ||
|
|
a69b67d6d2 | ||
|
|
dc436e4d01 | ||
|
|
fe95bdeee6 | ||
|
|
525528e38f | ||
|
|
9f5f3d994c | ||
|
|
e8c1833053 | ||
|
|
0921889f7a | ||
|
|
1951a3ac46 | ||
|
|
4e384f8057 | ||
|
|
3d06d19d6a | ||
|
|
30ef5ef72b | ||
|
|
c7fff52408 | ||
|
|
bc01a4cd57 | ||
|
|
cad3d70aed | ||
|
|
bb5719f438 | ||
|
|
3dd3f5b804 | ||
|
|
3d8d5a0634 | ||
|
|
54448ed070 | ||
|
|
369ac2b568 | ||
|
|
3dc592f679 | ||
|
|
4f93200c91 | ||
|
|
665736d754 | ||
|
|
5598c3e28f | ||
|
|
cde6e845cd | ||
|
|
fb8c712ad8 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -25,7 +25,7 @@ Bugs with react-navigation must be reproducible *without any external libraries
|
||||
|
||||
### How to reproduce
|
||||
|
||||
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repoistory as that is outside of the scope of Rect Navigation.
|
||||
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repository as that is outside of the scope of React Navigation.
|
||||
- Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug.
|
||||
- Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app.
|
||||
|
||||
|
||||
15
.release-it.json
Normal file
15
.release-it.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"increment": "conventional:angular",
|
||||
"changelogCommand": "conventional-changelog -p angular | tail -n +3",
|
||||
"safeBump": false,
|
||||
"src": {
|
||||
"commitMessage": "chore: release %s",
|
||||
"tagName": "v%s"
|
||||
},
|
||||
"npm": {
|
||||
"publish": true
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
}
|
||||
}
|
||||
128
CHANGELOG.md
128
CHANGELOG.md
@@ -7,23 +7,119 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.5.1] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.1)
|
||||
### Fixed
|
||||
- `transitionConfig` in stack navigator no longer passes incorrect `fromTransitionProps` when navigating back
|
||||
## [3.1.4] - [2019-02-05](https://github.com/react-navigation/react-navigation/releases/tag/3.1.4)
|
||||
|
||||
## [2.5.0] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.0)
|
||||
### Changed
|
||||
- Refactor internals to make it play more nicely with web
|
||||
## Fixed
|
||||
|
||||
### Fixed
|
||||
- `const defaultGetStateForAction = SwitchBasedNavigator.router.getStateForAction` no longer throws error.
|
||||
- Updated react-navigation-drawer to 0.4.1 which should fix issues related to automatically closing drawer when changing routes.
|
||||
- Fix references to onGestureFinish in StackViewLayout, should be onGestureEnd
|
||||
|
||||
## [2.4.1] - [2018-06-21](https://github.com/react-navigation/react-navigation/releases/tag/2.4.1)
|
||||
### Changed
|
||||
- Improved examples
|
||||
## [3.1.3] - [2019-02-04](https://github.com/react-navigation/react-navigation/releases/tag/3.1.3)
|
||||
|
||||
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/2.5.1...HEAD
|
||||
[2.5.0]: https://github.com/react-navigation/react-navigation/compare/2.5.0...2.5.1
|
||||
[2.5.0]: https://github.com/react-navigation/react-navigation/compare/2.4.1...2.5.0
|
||||
[2.4.1]: https://github.com/react-navigation/react-navigation/compare/2.4.0...2.4.1
|
||||
## Fixed
|
||||
|
||||
- Stack navigator properly dismisses and restores keyboard when gesture starts and is cancelled
|
||||
- Transparent header measurement fix (https://github.com/react-navigation/react-navigation-stack/pull/71)
|
||||
|
||||
## [3.1.2] - [2019-02-01](https://github.com/react-navigation/react-navigation/releases/tag/3.1.2)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Update flow definition for `withNavigation` and `withNavigationFocus` to support `defaultProps`
|
||||
- Prevent onRef callback be called twice on withNavigationFocus components (https://github.com/react-navigation/react-navigation-core/pull/30)
|
||||
- Bump react-navigation-drawer version to improve performance - if you use Expo, you will need expo@^32.0.3 to update!
|
||||
|
||||
## [3.0.9] - [2018-12-19](https://github.com/react-navigation/react-navigation/releases/tag/3.0.9)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Intermittent flicker when changing tabs while using react-native-screens fixed by not changing opacity (https://github.com/react-navigation/react-navigation-tabs/pull/80)
|
||||
- Prevent fading the previous screen on push/pop on Android (https://github.com/react-navigation/react-navigation-stack/pull/73)
|
||||
|
||||
## [3.0.8] - [2018-12-08](https://github.com/react-navigation/react-navigation/releases/tag/3.0.8)
|
||||
|
||||
## Changed
|
||||
|
||||
- Lock create-react-context to 0.2.2
|
||||
|
||||
## [3.0.7] - [2018-12-08](https://github.com/react-navigation/react-navigation/releases/tag/3.0.7)
|
||||
|
||||
## Changed
|
||||
|
||||
- Optimize stack gesture to avoid a setState and reduce unnecessary Animated node creation (https://github.com/react-navigation/react-navigation-stack/pull/70)
|
||||
|
||||
## [3.0.6] - [2018-12-06](https://github.com/react-navigation/react-navigation/releases/tag/3.0.6)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix drawer accessibility label when drawer label is not a string
|
||||
|
||||
|
||||
## [3.0.5] - [2018-12-03](https://github.com/react-navigation/react-navigation/releases/tag/3.0.5)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix crash in rare case where onNavigationStateChange on container leads to setState and container has screenProps (https://github.com/react-navigation/react-navigation/issues/5301)
|
||||
- Expose underlaying ScrollView methods to NavigationAwareScrollable (https://github.com/react-navigation/react-navigation-native/pull/8)
|
||||
|
||||
## [3.0.4] - [2018-11-30](https://github.com/react-navigation/react-navigation/releases/tag/3.0.4)
|
||||
|
||||
## Changed
|
||||
|
||||
- Lock dependencies to exact versions
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix crash when screenInterpolator is null (https://github.com/react-navigation/react-navigation-stack/issues/64)
|
||||
- Fix renderPager override (https://github.com/react-navigation/react-navigation-tabs/pull/70)
|
||||
|
||||
## Added
|
||||
|
||||
- Accessibility labels on drawer items (https://github.com/react-navigation/react-navigation-drawer/pull/30)
|
||||
|
||||
|
||||
## [3.0.3] - [2018-11-30](https://github.com/react-navigation/react-navigation/releases/tag/3.0.3)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix bug where if you navigate immediately when the navigator is first mounted the stack could get in an invalid state.
|
||||
- Transparent stack card factors in header height now, even though you probably won't want to use this.
|
||||
- Fix bug where shadow was still rendered on transparent stack
|
||||
- Fix gestureResponseDistance custom values being ignored for modal stack
|
||||
|
||||
## [3.0.2] - [2018-11-27](https://github.com/react-navigation/react-navigation/releases/tag/3.0.2)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix `drawerLockMode` on drawer navigator
|
||||
- Fix RTL support in drawer navigator
|
||||
|
||||
## [3.0.1] - [2018-11-26](https://github.com/react-navigation/react-navigation/releases/tag/3.0.1)
|
||||
|
||||
## Fixes
|
||||
|
||||
- fix NavigationTestUtils.js deprecated file import.
|
||||
- Update `getParam` flow typings to check `key` and `fallback` arguments, as well as return the correct type automatically.
|
||||
- Fix regression in backgroundColor on cardStyle for stack navigator.
|
||||
|
||||
## [3.0.0] - [2018-11-17](https://github.com/react-navigation/react-navigation/releases/tag/3.0.0)
|
||||
|
||||
- Changes between the latest 2.x release and 3.0.0 are listed on the blog at https://reactnavigation.org/blog/2018/11/17/react-navigation-3.0.html
|
||||
|
||||
# [Previous major versions]
|
||||
|
||||
- [2.x](https://github.com/react-navigation/react-navigation/blob/2.x/CHANGELOG.md)
|
||||
|
||||
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/3.1.4...HEAD
|
||||
[3.1.4]: https://github.com/react-navigation/react-navigation/compare/3.1.3...3.1.4
|
||||
[3.1.3]: https://github.com/react-navigation/react-navigation/compare/3.1.2...3.1.3
|
||||
[3.1.2]: https://github.com/react-navigation/react-navigation/compare/3.0.9...3.1.2
|
||||
[3.0.9]: https://github.com/react-navigation/react-navigation/compare/3.0.8...3.0.9
|
||||
[3.0.8]: https://github.com/react-navigation/react-navigation/compare/3.0.7...3.0.8
|
||||
[3.0.7]: https://github.com/react-navigation/react-navigation/compare/3.0.6...3.0.7
|
||||
[3.0.6]: https://github.com/react-navigation/react-navigation/compare/3.0.5...3.0.6
|
||||
[3.0.5]: https://github.com/react-navigation/react-navigation/compare/3.0.4...3.0.5
|
||||
[3.0.4]: https://github.com/react-navigation/react-navigation/compare/3.0.3...3.0.4
|
||||
[3.0.3]: https://github.com/react-navigation/react-navigation/compare/3.0.2...3.0.3
|
||||
[3.0.2]: https://github.com/react-navigation/react-navigation/compare/3.0.1...3.0.2
|
||||
[3.0.1]: https://github.com/react-navigation/react-navigation/compare/3.0.0...3.0.1
|
||||
[3.0.0]: https://github.com/react-navigation/react-navigation/compare/2.x...3.0.0
|
||||
|
||||
7
NavigationTestUtils.js
Normal file
7
NavigationTestUtils.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { _TESTING_ONLY_reset_container_count } from '@react-navigation/native/src/createAppContainer';
|
||||
|
||||
export default {
|
||||
resetInternalState: () => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
},
|
||||
};
|
||||
14
README.md
14
README.md
@@ -1,22 +1,12 @@
|
||||
# React Navigation
|
||||
|
||||
[](https://badge.fury.io/js/react-navigation) [](https://codecov.io/gh/react-navigation/react-navigation) [](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [](https://reactnavigation.org/docs/contributing.html)
|
||||
[](https://badge.fury.io/js/react-navigation) [](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [](https://reactnavigation.org/docs/contributing.html)
|
||||
|
||||
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
|
||||
|
||||
## Installation
|
||||
|
||||
Since the library is a JS-based solution, to install the latest version of react-navigation you only need to run:
|
||||
|
||||
```bash
|
||||
yarn add react-navigation
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npm install --save react-navigation
|
||||
```
|
||||
See: https://reactnavigation.org/docs/en/getting-started.html
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"babel-preset-expo"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
"transform-react-jsx-source"
|
||||
],
|
||||
},
|
||||
"production": {
|
||||
"plugins": [
|
||||
"transform-remove-console"
|
||||
]
|
||||
}
|
||||
}
|
||||
"expo"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { useScreens } from 'react-native-screens';
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// useScreens();
|
||||
}
|
||||
|
||||
import App from './js/App';
|
||||
export default App;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Navigation Playground Example
|
||||
|
||||
A playground for experimenting with react-navigation in a pure-JS React Native app.
|
||||
The NavigationPlayground example app includes a variety of patterns and is used as a simple way for contributors to manually integration test changes.
|
||||
|
||||
## Usage
|
||||
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
||||
```bash
|
||||
yarn # in the react-navigation root directory
|
||||
cd examples/NavigationPlayground
|
||||
yarn
|
||||
yarn start
|
||||
```
|
||||
|
||||
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).
|
||||
You can view this example application directly on Android phones by visiting scanning the QR code on [this site](https://exp.host/@react-navigation/NavigationPlayground) with the [Expo app](https://play.google.com/store/apps/details?id=host.exp.exponent&hl=en).
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"splash": {
|
||||
"image": "./assets/icons/splash.png"
|
||||
},
|
||||
"sdkVersion": "27.0.0",
|
||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"sdkVersion": "32.0.0",
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
|
||||
@@ -11,12 +11,20 @@ import {
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StatusBar,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, createStackNavigator } from 'react-navigation';
|
||||
import {
|
||||
RectButton,
|
||||
NativeViewGestureHandler,
|
||||
} from 'react-native-gesture-handler';
|
||||
import {
|
||||
createAppContainer,
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
} from 'react-navigation';
|
||||
import { Assets as StackAssets } from 'react-navigation-stack';
|
||||
|
||||
import CustomTabs from './CustomTabs';
|
||||
import CustomTransitioner from './CustomTransitioner';
|
||||
@@ -34,10 +42,14 @@ import SimpleStack from './SimpleStack';
|
||||
import StackWithHeaderPreset from './StackWithHeaderPreset';
|
||||
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
import CustomTabUI from './CustomTabUI';
|
||||
import SwitchWithStacks from './SwitchWithStacks';
|
||||
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
|
||||
import TabsWithNavigationEvents from './TabsWithNavigationEvents';
|
||||
import KeyboardHandlingExample from './KeyboardHandlingExample';
|
||||
|
||||
process.env.REACT_NAV_LOGGING = true;
|
||||
|
||||
const ExampleInfo = {
|
||||
SimpleStack: {
|
||||
name: 'Stack Example',
|
||||
@@ -126,11 +138,20 @@ const ExampleInfo = {
|
||||
name: 'withNavigationFocus',
|
||||
description: 'Receive the focus prop to know when a screen is focused',
|
||||
},
|
||||
TabsWithNavigationEvents: {
|
||||
name: 'NavigationEvents',
|
||||
description:
|
||||
'Declarative NavigationEvents component to subscribe to navigation events',
|
||||
},
|
||||
KeyboardHandlingExample: {
|
||||
name: 'Keyboard Handling Example',
|
||||
description:
|
||||
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
|
||||
},
|
||||
CustomTabUI: {
|
||||
name: 'Custom Tabs UI',
|
||||
description: 'Render additional views around a Tab navigator',
|
||||
},
|
||||
};
|
||||
|
||||
const ExampleRoutes = {
|
||||
@@ -155,6 +176,7 @@ const ExampleRoutes = {
|
||||
ModalStack: ModalStack,
|
||||
StacksWithKeys: StacksWithKeys,
|
||||
StacksInTabs: StacksInTabs,
|
||||
CustomTabUI: CustomTabUI,
|
||||
StacksOverTabs: StacksOverTabs,
|
||||
StacksOverTopTabs: StacksOverTopTabs,
|
||||
LinkStack: {
|
||||
@@ -166,6 +188,7 @@ const ExampleRoutes = {
|
||||
path: 'settings',
|
||||
},
|
||||
TabsWithNavigationFocus,
|
||||
TabsWithNavigationEvents,
|
||||
KeyboardHandlingExample,
|
||||
// This is commented out because it's rarely useful
|
||||
// InactiveStack,
|
||||
@@ -180,12 +203,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
};
|
||||
|
||||
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();
|
||||
Asset.loadAsync(StackAssets);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -227,69 +245,72 @@ class MainScreen extends React.Component<any, State> {
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<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 }] }}
|
||||
<NativeViewGestureHandler>
|
||||
<Animated.ScrollView
|
||||
style={{ flex: 1, backgroundColor: '#eee' }}
|
||||
scrollEventThrottle={1}
|
||||
onScroll={Animated.event(
|
||||
[
|
||||
{
|
||||
nativeEvent: { contentOffset: { y: this.state.scrollY } },
|
||||
},
|
||||
],
|
||||
{ useNativeDriver: true }
|
||||
)}
|
||||
>
|
||||
<SafeAreaView
|
||||
style={styles.bannerContainer}
|
||||
forceInset={{ top: 'always', bottom: 'never' }}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.backgroundUnderlay,
|
||||
{
|
||||
transform: [
|
||||
{ scale: backgroundScale },
|
||||
{ translateY: backgroundTranslateY },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Animated.View
|
||||
style={{ opacity, transform: [{ scale }, { translateY }] }}
|
||||
>
|
||||
<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
|
||||
style={styles.bannerContainer}
|
||||
forceInset={{ top: 'always', bottom: 'never' }}
|
||||
>
|
||||
<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' }}
|
||||
<SafeAreaView
|
||||
forceInset={{ top: 'never', bottom: 'always' }}
|
||||
style={{ backgroundColor: '#eee' }}
|
||||
>
|
||||
<View style={{ backgroundColor: '#fff' }}>
|
||||
{Object.keys(ExampleRoutes).map((routeName: string) => (
|
||||
<RectButton
|
||||
key={routeName}
|
||||
underlayColor="#ccc"
|
||||
activeOpacity={0.3}
|
||||
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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.title}>
|
||||
@@ -299,12 +320,12 @@ class MainScreen extends React.Component<any, State> {
|
||||
{ExampleInfo[routeName].description}
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Animated.ScrollView>
|
||||
</RectButton>
|
||||
))}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Animated.ScrollView>
|
||||
</NativeViewGestureHandler>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<Animated.View
|
||||
style={[styles.statusBarUnderlay, { opacity: underlayOpacity }]}
|
||||
@@ -314,34 +335,37 @@ class MainScreen extends React.Component<any, State> {
|
||||
}
|
||||
}
|
||||
|
||||
const AppNavigator = createStackNavigator(
|
||||
{
|
||||
...ExampleRoutes,
|
||||
Index: {
|
||||
screen: MainScreen,
|
||||
const AppNavigator = createAppContainer(
|
||||
createStackNavigator(
|
||||
{
|
||||
...ExampleRoutes,
|
||||
Index: {
|
||||
screen: MainScreen,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'Index',
|
||||
headerMode: 'none',
|
||||
{
|
||||
initialRouteName: 'Index',
|
||||
headerMode: 'none',
|
||||
|
||||
/*
|
||||
* Use modal on iOS because the card mode comes from the right,
|
||||
* which conflicts with the drawer example gesture
|
||||
*/
|
||||
mode: Platform.OS === 'ios' ? 'modal' : 'card',
|
||||
}
|
||||
/*
|
||||
* Use modal on iOS because the card mode comes from the right,
|
||||
* which conflicts with the drawer example gesture
|
||||
*/
|
||||
mode: Platform.OS === 'ios' ? 'modal' : 'card',
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default AppNavigator;
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return <AppNavigator /* persistenceKey="if-you-want-it" */ />;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
itemContainer: {
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#ddd',
|
||||
},
|
||||
|
||||
129
examples/NavigationPlayground/js/CustomTabUI.js
Normal file
129
examples/NavigationPlayground/js/CustomTabUI.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
LayoutAnimation,
|
||||
View,
|
||||
StyleSheet,
|
||||
StatusBar,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, createMaterialTopTabNavigator } from 'react-navigation';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
class MyHomeScreen extends React.Component {
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
<Text>Home Screen</Text>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
title="Go to home tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReccomendedScreen extends React.Component {
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'Reccomended',
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-people' : 'ios-people'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
<Text>Reccomended Screen</Text>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
title="Go to home tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FeaturedScreen extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
tabBarLabel: 'Featured',
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-star' : 'ios-star'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
});
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return (
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
<Text>Featured Screen</Text>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
title="Go to home tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SimpleTabs = createMaterialTopTabNavigator({
|
||||
Home: MyHomeScreen,
|
||||
Reccomended: ReccomendedScreen,
|
||||
Featured: FeaturedScreen,
|
||||
});
|
||||
|
||||
class TabNavigator extends React.Component {
|
||||
static router = SimpleTabs.router;
|
||||
componentWillUpdate() {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { routes, index } = navigation.state;
|
||||
const activeRoute = routes[index];
|
||||
let bottom = null;
|
||||
if (activeRoute.routeName !== 'Home') {
|
||||
bottom = (
|
||||
<View style={{ height: 50, borderTopWidth: StyleSheet.hairlineWidth }}>
|
||||
<Button title="Check out" onPress={() => {}} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<StatusBar barStyle="default" />
|
||||
<SafeAreaView
|
||||
style={{ flex: 1 }}
|
||||
forceInset={{ horizontal: 'always', top: 'always' }}
|
||||
>
|
||||
<SimpleTabs navigation={navigation} />
|
||||
</SafeAreaView>
|
||||
{bottom}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TabNavigator;
|
||||
@@ -9,15 +9,11 @@ import {
|
||||
StyleSheet,
|
||||
StatusBar,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
createNavigator,
|
||||
createNavigationContainer,
|
||||
SafeAreaView,
|
||||
TabRouter,
|
||||
} from 'react-navigation';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import { createNavigator, SafeAreaView, TabRouter } from 'react-navigation';
|
||||
import { createAppContainer } from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
@@ -53,13 +49,13 @@ const CustomTabBar = ({ navigation }) => {
|
||||
return (
|
||||
<SafeAreaView style={styles.tabContainer}>
|
||||
{routes.map(route => (
|
||||
<TouchableOpacity
|
||||
<BorderlessButton
|
||||
onPress={() => navigation.navigate(route.routeName)}
|
||||
style={styles.tab}
|
||||
key={route.routeName}
|
||||
>
|
||||
<Text>{route.routeName}</Text>
|
||||
</TouchableOpacity>
|
||||
</BorderlessButton>
|
||||
))}
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -98,7 +94,7 @@ const CustomTabRouter = TabRouter(
|
||||
}
|
||||
);
|
||||
|
||||
const CustomTabs = createNavigationContainer(
|
||||
const CustomTabs = createAppContainer(
|
||||
createNavigator(CustomTabView, CustomTabRouter, {})
|
||||
);
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
createAppContainer,
|
||||
Transitioner,
|
||||
SafeAreaView,
|
||||
StackRouter,
|
||||
createNavigationContainer,
|
||||
createNavigator,
|
||||
} from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
@@ -100,7 +100,7 @@ const CustomRouter = StackRouter({
|
||||
Settings: { screen: MySettingsScreen },
|
||||
});
|
||||
|
||||
const CustomTransitioner = createNavigationContainer(
|
||||
const CustomTransitioner = createAppContainer(
|
||||
createNavigator(CustomNavigationView, CustomRouter, {})
|
||||
);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const MyNavScreen = ({ navigation, banner }) => (
|
||||
onPress={() => navigation.navigate('Email')}
|
||||
title="Open other screen"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<Button onPress={() => navigation.navigate('Index')} title="Go back" />
|
||||
</SafeAreaView>
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
@@ -46,33 +46,39 @@ DraftsScreen.navigationOptions = {
|
||||
headerTitle: 'Drafts',
|
||||
};
|
||||
|
||||
const InboxStack = createStackNavigator({
|
||||
Inbox: { screen: InboxScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
const InboxStack = createStackNavigator(
|
||||
{
|
||||
Inbox: { screen: InboxScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
drawerLabel: 'Inbox',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="move-to-inbox"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
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 DraftsStack = createStackNavigator(
|
||||
{
|
||||
Drafts: { screen: DraftsScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
drawerLabel: 'Drafts',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const DrawerExample = createDrawerNavigator(
|
||||
{
|
||||
@@ -85,6 +91,7 @@ const DrawerExample = createDrawerNavigator(
|
||||
screen: DraftsStack,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
initialRouteName: 'Drafts',
|
||||
contentOptions: {
|
||||
|
||||
@@ -70,7 +70,7 @@ const ProfileNavigator = createStackNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
defaultNavigationOptions: {
|
||||
headerLeft: null,
|
||||
},
|
||||
mode: 'modal',
|
||||
@@ -97,7 +97,7 @@ const ModalStack = createStackNavigator(
|
||||
HeaderTest: { screen: MyHeaderTestScreen },
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
defaultNavigationOptions: {
|
||||
header: null,
|
||||
},
|
||||
mode: 'modal',
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
} from 'react-navigation';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import { Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
SafeAreaView,
|
||||
@@ -24,6 +24,8 @@ import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
import { HeaderButtons } from './commonComponents/HeaderButtons';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
type MyNavScreenProps = {
|
||||
navigation: NavigationScreenProp<NavigationState>,
|
||||
banner: React.Node,
|
||||
@@ -58,7 +60,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
'missing action creators for StackNavigator'
|
||||
);
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<SafeAreaView forceInset={{ top: 'never' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => push('Profile', { name: 'Jane' })}
|
||||
@@ -133,16 +135,16 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus HomeScreen', a);
|
||||
DEBUG && console.log('_willFocus HomeScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus HomeScreen', a);
|
||||
DEBUG && console.log('_didFocus HomeScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur HomeScreen', a);
|
||||
DEBUG && console.log('_willBlur HomeScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur HomeScreen', a);
|
||||
DEBUG && console.log('_didBlur HomeScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -177,16 +179,16 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_willFocus PhotosScreen', a);
|
||||
DEBUG && console.log('_willFocus PhotosScreen', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_didFocus PhotosScreen', a);
|
||||
DEBUG && console.log('_didFocus PhotosScreen', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_willBlur PhotosScreen', a);
|
||||
DEBUG && console.log('_willBlur PhotosScreen', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_didBlur PhotosScreen', a);
|
||||
DEBUG && console.log('_didBlur PhotosScreen', a);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -231,18 +233,23 @@ MyProfileScreen.navigationOptions = props => {
|
||||
};
|
||||
};
|
||||
|
||||
const SimpleStack = createStackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
const SimpleStack = createStackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
},
|
||||
Profile: {
|
||||
path: 'people/:name',
|
||||
screen: MyProfileScreen,
|
||||
},
|
||||
Photos: {
|
||||
path: 'photos/:name',
|
||||
screen: MyPhotosScreen,
|
||||
},
|
||||
});
|
||||
{
|
||||
// headerLayoutPreset: 'center',
|
||||
}
|
||||
);
|
||||
|
||||
export default SimpleStack;
|
||||
|
||||
@@ -8,26 +8,57 @@ import type {
|
||||
} from 'react-navigation';
|
||||
|
||||
import React from 'react';
|
||||
import { Platform, ScrollView, StatusBar, View } from 'react-native';
|
||||
import { SafeAreaView, createBottomTabNavigator } from 'react-navigation';
|
||||
import { Animated, Platform, Text, StatusBar, View } from 'react-native';
|
||||
import {
|
||||
ScrollView,
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
createBottomTabNavigator,
|
||||
withNavigation,
|
||||
} from 'react-navigation';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import SampleText from './SampleText';
|
||||
import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const TEXT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla a hendrerit dui, id consectetur nulla. Curabitur mattis sapien nunc, quis dignissim eros venenatis sit amet. Praesent rutrum dapibus diam quis eleifend. Donec vulputate quis purus sed vulputate. Fusce ipsum felis, cursus at congue vel, consectetur tincidunt purus. Pellentesque et fringilla lorem. In at augue malesuada, sollicitudin ex ut, convallis elit. Curabitur metus nibh, consequat vel libero sit amet, iaculis congue nisl. Maecenas eleifend sodales sapien, fringilla sagittis nisi ornare volutpat. Integer tellus enim, volutpat vitae nisl et, dignissim pharetra leo. Sed sit amet efficitur sapien, at tristique sapien. Aenean dignissim semper sagittis. Nullam sit amet volutpat mi.
|
||||
Curabitur auctor orci et justo molestie iaculis. Integer elementum tortor ac ipsum egestas pharetra. Etiam ultrices elementum pharetra. Maecenas lobortis ultrices risus dignissim luctus. Nunc malesuada cursus posuere. Vestibulum tristique lectus pretium pellentesque pellentesque. Nunc ac nisi lacus. Duis ultrices dui ac viverra ullamcorper. Morbi placerat laoreet lacus sit amet ullamcorper.
|
||||
Nulla convallis pulvinar hendrerit. Nulla mattis sem et aliquam ultrices. Nam egestas magna leo, nec luctus turpis sollicitudin ac. Sed id leo luctus, lobortis tortor ut, rhoncus ex. Aliquam gravida enim ac dapibus ultricies. Vestibulum at interdum est, et vehicula nibh. Phasellus dignissim iaculis rhoncus. Vestibulum tempus leo lectus, quis euismod metus ullamcorper quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut id ipsum at enim eleifend porttitor id quis metus. Proin bibendum ornare iaculis. Duis elementum lacus vel cursus efficitur. Nunc eu tortor sed risus lacinia scelerisque.
|
||||
Praesent lobortis elit sit amet mauris pulvinar, viverra condimentum massa pellentesque. Curabitur massa ex, dignissim eget neque at, fringilla consectetur justo. Cras sollicitudin vel ligula sed cursus. Aliquam porta sem hendrerit diam porta ultricies. Sed eu mi erat. Curabitur id justo vel tortor hendrerit vestibulum id eget est. Morbi eros magna, placerat id diam ut, varius sollicitudin mi. Curabitur pretium finibus accumsan.`;
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
title="Go to home tab"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Settings')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
<ScrollView navigation={navigation} style={{ flex: 1 }}>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
title="Go to home tab"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Settings')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
{TEXT.split('\n').map((p, n) => (
|
||||
<Text key={n} style={{ marginVertical: 10, marginHorizontal: 8 }}>
|
||||
{p}
|
||||
</Text>
|
||||
))}
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
const MyListScreen = ({ navigation, data }) => (
|
||||
<FlatList
|
||||
navigation={navigation}
|
||||
data={TEXT.split('\n')}
|
||||
style={{ paddingTop: 10 }}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<Text style={{ fontSize: 16, marginVertical: 10, marginHorizontal: 8 }}>
|
||||
{item}
|
||||
</Text>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
const MyHomeScreen = ({ navigation }) => (
|
||||
@@ -40,14 +71,15 @@ MyHomeScreen.navigationOptions = {
|
||||
accessibilityLabel: 'TEST_ID_HOME_ACLBL',
|
||||
},
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||
size={26}
|
||||
name={'ios-home'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
MyListScreen.navigationOptions = MyHomeScreen.navigationOptions;
|
||||
|
||||
type MyPeopleScreenProps = {
|
||||
navigation: NavigationScreenProp<*>,
|
||||
@@ -60,25 +92,25 @@ class MyPeopleScreen extends React.Component<MyPeopleScreenProps> {
|
||||
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'People',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-people' : 'ios-people-outline'}
|
||||
size={26}
|
||||
name={'ios-people'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
// this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
// this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
// this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
// this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
// this._s0.remove();
|
||||
// this._s1.remove();
|
||||
// this._s2.remove();
|
||||
// this._s3.remove();
|
||||
}
|
||||
_onEvent = a => {
|
||||
console.log('EVENT ON PEOPLE TAB', a.type, a);
|
||||
@@ -100,25 +132,25 @@ class MyChatScreen extends React.Component<MyChatScreenProps> {
|
||||
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
|
||||
size={26}
|
||||
name={'ios-chatboxes'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
// this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
// this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
// this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
// this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
// this._s0.remove();
|
||||
// this._s1.remove();
|
||||
// this._s2.remove();
|
||||
// this._s3.remove();
|
||||
}
|
||||
_onEvent = a => {
|
||||
console.log('EVENT ON CHAT TAB', a.type, a);
|
||||
@@ -135,10 +167,10 @@ const MySettingsScreen = ({ navigation }) => (
|
||||
|
||||
MySettingsScreen.navigationOptions = {
|
||||
tabBarLabel: 'Settings',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
tabBarIcon: ({ tintColor, focused, horizontal }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||
size={26}
|
||||
name={'ios-settings'}
|
||||
size={horizontal ? 20 : 26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
@@ -147,7 +179,7 @@ MySettingsScreen.navigationOptions = {
|
||||
const SimpleTabs = createBottomTabNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
screen: MyListScreen,
|
||||
path: '',
|
||||
},
|
||||
People: {
|
||||
@@ -182,16 +214,16 @@ class SimpleTabsContainer extends React.Component<SimpleTabsContainerProps> {
|
||||
_s3: NavigationEventSubscription;
|
||||
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
|
||||
// this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
|
||||
// this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
|
||||
// this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
|
||||
// this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
// this._s0.remove();
|
||||
// this._s1.remove();
|
||||
// this._s2.remove();
|
||||
// this._s3.remove();
|
||||
}
|
||||
_onAction = a => {
|
||||
console.log('TABS EVENT', a.type, a);
|
||||
|
||||
@@ -122,7 +122,7 @@ const StackWithCustomHeaderBackImage = createStackNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
defaultNavigationOptions: {
|
||||
headerBackImage: MyCustomHeaderBackImage,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@ import {
|
||||
Platform,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Header, createStackNavigator } from 'react-navigation';
|
||||
import {
|
||||
Header,
|
||||
HeaderStyleInterpolator,
|
||||
createStackNavigator,
|
||||
} from 'react-navigation';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import SampleText from './SampleText';
|
||||
@@ -229,8 +234,18 @@ const StackWithTranslucentHeader = createStackNavigator(
|
||||
},
|
||||
{
|
||||
headerTransitionPreset: 'uikit',
|
||||
navigationOptions: {
|
||||
// You can leave this out if you don't want the card shadow to
|
||||
// be visible through the header
|
||||
transitionConfig: () => ({
|
||||
headerBackgroundInterpolator:
|
||||
HeaderStyleInterpolator.forBackgroundWithTranslation,
|
||||
}),
|
||||
defaultNavigationOptions: {
|
||||
headerTransparent: true,
|
||||
headerStyle: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#A7A7AA',
|
||||
},
|
||||
headerBackground: Platform.select({
|
||||
ios: <BlurView style={{ flex: 1 }} intensity={98} />,
|
||||
android: (
|
||||
|
||||
@@ -3,43 +3,59 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import { StatusBar, Text } from 'react-native';
|
||||
import {
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createBottomTabNavigator,
|
||||
withNavigation,
|
||||
} 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>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||
title="Open profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('NotifSettings')}
|
||||
title="Open notifications screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('SettingsTab')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
const TEXT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla a hendrerit dui, id consectetur nulla. Curabitur mattis sapien nunc, quis dignissim eros venenatis sit amet. Praesent rutrum dapibus diam quis eleifend. Donec vulputate quis purus sed vulputate. Fusce ipsum felis, cursus at congue vel, consectetur tincidunt purus. Pellentesque et fringilla lorem. In at augue malesuada, sollicitudin ex ut, convallis elit. Curabitur metus nibh, consequat vel libero sit amet, iaculis congue nisl. Maecenas eleifend sodales sapien, fringilla sagittis nisi ornare volutpat. Integer tellus enim, volutpat vitae nisl et, dignissim pharetra leo. Sed sit amet efficitur sapien, at tristique sapien. Aenean dignissim semper sagittis. Nullam sit amet volutpat mi.
|
||||
Curabitur auctor orci et justo molestie iaculis. Integer elementum tortor ac ipsum egestas pharetra. Etiam ultrices elementum pharetra. Maecenas lobortis ultrices risus dignissim luctus. Nunc malesuada cursus posuere. Vestibulum tristique lectus pretium pellentesque pellentesque. Nunc ac nisi lacus. Duis ultrices dui ac viverra ullamcorper. Morbi placerat laoreet lacus sit amet ullamcorper.
|
||||
Nulla convallis pulvinar hendrerit. Nulla mattis sem et aliquam ultrices. Nam egestas magna leo, nec luctus turpis sollicitudin ac. Sed id leo luctus, lobortis tortor ut, rhoncus ex. Aliquam gravida enim ac dapibus ultricies. Vestibulum at interdum est, et vehicula nibh. Phasellus dignissim iaculis rhoncus. Vestibulum tempus leo lectus, quis euismod metus ullamcorper quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut id ipsum at enim eleifend porttitor id quis metus. Proin bibendum ornare iaculis. Duis elementum lacus vel cursus efficitur. Nunc eu tortor sed risus lacinia scelerisque.
|
||||
Praesent lobortis elit sit amet mauris pulvinar, viverra condimentum massa pellentesque. Curabitur massa ex, dignissim eget neque at, fringilla consectetur justo. Cras sollicitudin vel ligula sed cursus. Aliquam porta sem hendrerit diam porta ultricies. Sed eu mi erat. Curabitur id justo vel tortor hendrerit vestibulum id eget est. Morbi eros magna, placerat id diam ut, varius sollicitudin mi. Curabitur pretium finibus accumsan.`;
|
||||
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
);
|
||||
class MyNavScreen extends React.Component {
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const banner = navigation.getParam('banner');
|
||||
|
||||
const MyHomeScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Home Screen" navigation={navigation} />
|
||||
);
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||
title="Open profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('NotifSettings')}
|
||||
title="Open notifications screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('SettingsTab')}
|
||||
title="Go to settings tab"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
|
||||
{TEXT.split('\n').map((p, n) => (
|
||||
<Text key={n} style={{ marginVertical: 10, marginHorizontal: 8 }}>
|
||||
{p}
|
||||
</Text>
|
||||
))}
|
||||
</SafeAreaView>
|
||||
|
||||
<StatusBar barStyle="default" />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MyProfileScreen = ({ navigation }) => (
|
||||
<MyNavScreen
|
||||
@@ -48,18 +64,11 @@ const MyProfileScreen = ({ navigation }) => (
|
||||
/>
|
||||
);
|
||||
|
||||
const MyNotificationsSettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Settings Screen" navigation={navigation} />
|
||||
);
|
||||
|
||||
const MainTab = createStackNavigator({
|
||||
Home: {
|
||||
screen: MyHomeScreen,
|
||||
screen: MyNavScreen,
|
||||
path: '/',
|
||||
params: { banner: 'Home Screen' },
|
||||
navigationOptions: {
|
||||
title: 'Welcome',
|
||||
},
|
||||
@@ -75,14 +84,16 @@ const MainTab = createStackNavigator({
|
||||
|
||||
const SettingsTab = createStackNavigator({
|
||||
Settings: {
|
||||
screen: MySettingsScreen,
|
||||
screen: MyNavScreen,
|
||||
path: '/',
|
||||
params: { banner: 'Settings Screen' },
|
||||
navigationOptions: () => ({
|
||||
title: 'Settings',
|
||||
}),
|
||||
},
|
||||
NotifSettings: {
|
||||
screen: MyNotificationsSettingsScreen,
|
||||
screen: MyNavScreen,
|
||||
params: { banner: 'Notifications Screen' },
|
||||
navigationOptions: {
|
||||
title: 'Notifications',
|
||||
},
|
||||
@@ -98,7 +109,7 @@ const StacksInTabs = createBottomTabNavigator(
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||
name={focused ? 'ios-home' : 'ios-home'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
@@ -112,7 +123,7 @@ const StacksInTabs = createBottomTabNavigator(
|
||||
tabBarLabel: 'Settings',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||
name={focused ? 'ios-settings' : 'ios-settings'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
SafeAreaView,
|
||||
createStackNavigator,
|
||||
createBottomTabNavigator,
|
||||
getActiveChildNavigationOptions,
|
||||
} from 'react-navigation';
|
||||
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
@@ -16,7 +17,7 @@ import { Button } from './commonComponents/ButtonWithMargin';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always' }}>
|
||||
<SafeAreaView forceInset={{ horizontal: 'always', vertical: 'never' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
|
||||
@@ -65,7 +66,7 @@ const TabNav = createBottomTabNavigator(
|
||||
tabBarLabel: 'Home',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-home' : 'ios-home-outline'}
|
||||
name={focused ? 'ios-home' : 'ios-home'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
@@ -79,7 +80,7 @@ const TabNav = createBottomTabNavigator(
|
||||
title: 'Settings',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-settings' : 'ios-settings-outline'}
|
||||
name={focused ? 'ios-settings' : 'ios-settings'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
@@ -94,16 +95,10 @@ const TabNav = createBottomTabNavigator(
|
||||
}
|
||||
);
|
||||
|
||||
TabNav.navigationOptions = ({ navigation }) => {
|
||||
let { routeName } = navigation.state.routes[navigation.state.index];
|
||||
let title;
|
||||
if (routeName === 'SettingsTab') {
|
||||
title = 'Settings';
|
||||
} else if (routeName === 'MainTab') {
|
||||
title = 'Home';
|
||||
}
|
||||
TabNav.navigationOptions = ({ navigation, screenProps }) => {
|
||||
const childOptions = getActiveChildNavigationOptions(navigation, screenProps);
|
||||
return {
|
||||
title,
|
||||
title: childOptions.title,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ const StackNavigator = createStackNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
defaultNavigationOptions: {
|
||||
headerStyle: styles.stackHeader,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,31 +13,19 @@ const TabsInDrawer = createDrawerNavigator({
|
||||
SimpleTabs: {
|
||||
screen: SimpleTabs,
|
||||
navigationOptions: {
|
||||
drawer: () => ({
|
||||
label: 'Simple Tabs',
|
||||
icon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="filter-1"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
drawerLabel: 'Simple tabs',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="filter-1" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
},
|
||||
},
|
||||
StacksOverTabs: {
|
||||
screen: StacksOverTabs,
|
||||
navigationOptions: {
|
||||
drawer: () => ({
|
||||
label: 'Stacks Over Tabs',
|
||||
icon: ({ tintColor }) => (
|
||||
<MaterialIcons
|
||||
name="filter-2"
|
||||
size={24}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
drawerLabel: 'Stacks Over Tabs',
|
||||
drawerIcon: ({ tintColor }) => (
|
||||
<MaterialIcons name="filter-2" size={24} style={{ color: tintColor }} />
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
127
examples/NavigationPlayground/js/TabsWithNavigationEvents.js
Normal file
127
examples/NavigationPlayground/js/TabsWithNavigationEvents.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FlatList, SafeAreaView, StatusBar, Text, View } from 'react-native';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
const Event = ({ event }) => (
|
||||
<View
|
||||
style={{
|
||||
borderColor: 'grey',
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
padding: 5,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Text>{event.type}</Text>
|
||||
<Text>
|
||||
{event.action.type.replace('Navigation/', '')}
|
||||
{event.action.routeName ? '=>' + event.action.routeName : ''}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const createTabScreen = (name, icon, focusedIcon) => {
|
||||
class TabScreen extends React.Component<any, any> {
|
||||
static navigationOptions = {
|
||||
tabBarLabel: name,
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? focusedIcon : icon}
|
||||
size={26}
|
||||
style={{ color: focused ? tintColor : '#ccc' }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
state = { eventLog: [] };
|
||||
|
||||
append = navigationEvent => {
|
||||
this.setState(({ eventLog }) => ({
|
||||
eventLog: eventLog.concat(navigationEvent),
|
||||
}));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView
|
||||
forceInset={{ horizontal: 'always', top: 'always' }}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
margin: 10,
|
||||
marginTop: 30,
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
Events for tab {name}
|
||||
</Text>
|
||||
|
||||
<View style={{ flex: 1, width: '100%', marginTop: 10 }}>
|
||||
<FlatList
|
||||
data={this.state.eventLog}
|
||||
keyExtractor={item => `${this.state.eventLog.indexOf(item)}`}
|
||||
renderItem={({ item }) => (
|
||||
<View
|
||||
style={{
|
||||
marginVertical: 5,
|
||||
marginHorizontal: 10,
|
||||
backgroundColor: '#e4e4e4',
|
||||
}}
|
||||
>
|
||||
<Event event={item} />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<NavigationEvents
|
||||
onWillFocus={this.append}
|
||||
onDidFocus={this.append}
|
||||
onWillBlur={this.append}
|
||||
onDidBlur={this.append}
|
||||
/>
|
||||
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return TabScreen;
|
||||
};
|
||||
|
||||
const TabsWithNavigationEvents = createMaterialBottomTabNavigator(
|
||||
{
|
||||
One: {
|
||||
screen: createTabScreen('One', 'numeric-1-box', 'numeric-1-box'),
|
||||
},
|
||||
Two: {
|
||||
screen: createTabScreen('Two', 'numeric-2-box', 'numeric-2-box'),
|
||||
},
|
||||
Three: {
|
||||
screen: createTabScreen(
|
||||
'Three',
|
||||
'numeric-3-box',
|
||||
'numeric-3-box'
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
shifting: false,
|
||||
activeTintColor: '#F44336',
|
||||
}
|
||||
);
|
||||
|
||||
export default TabsWithNavigationEvents;
|
||||
@@ -81,15 +81,15 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
|
||||
const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
|
||||
{
|
||||
One: {
|
||||
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
|
||||
screen: createTabScreen('One', 'numeric-1-box', 'numeric-1-box'),
|
||||
},
|
||||
Two: {
|
||||
screen: createTabScreen('Two', 'numeric-2-box-outline', 'numeric-2-box'),
|
||||
screen: createTabScreen('Two', 'numeric-2-box', 'numeric-2-box'),
|
||||
},
|
||||
Three: {
|
||||
screen: createTabScreen(
|
||||
'Three',
|
||||
'numeric-3-box-outline',
|
||||
'numeric-3-box',
|
||||
'numeric-3-box'
|
||||
),
|
||||
},
|
||||
|
||||
165
examples/NavigationPlayground/js/commonComponents/Button.js
Normal file
165
examples/NavigationPlayground/js/commonComponents/Button.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import React from 'react';
|
||||
import { Platform, StyleSheet, Text, View } from 'react-native';
|
||||
import { BorderlessButton, RectButton } from 'react-native-gesture-handler';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
type ButtonProps = $ReadOnly<{|
|
||||
/**
|
||||
* Text to display inside the button
|
||||
*/
|
||||
title: string,
|
||||
|
||||
/**
|
||||
* Handler to be called when the user taps the button
|
||||
*/
|
||||
onPress: (event?: any) => mixed,
|
||||
|
||||
/**
|
||||
* Color of the text (iOS), or background color of the button (Android)
|
||||
*/
|
||||
color?: ?string,
|
||||
|
||||
/**
|
||||
* TV preferred focus (see documentation for the View component).
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
/**
|
||||
* Text to display for blindness accessibility features
|
||||
*/
|
||||
accessibilityLabel?: ?string,
|
||||
|
||||
/**
|
||||
* If true, disable all interactions for this component.
|
||||
*/
|
||||
disabled?: ?boolean,
|
||||
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
testID?: ?string,
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* A basic button component that should render nicely on any platform. Supports
|
||||
* a minimal level of customization.
|
||||
*
|
||||
* <center><img src="img/buttonExample.png"></img></center>
|
||||
*
|
||||
* If this button doesn't look right for your app, you can build your own
|
||||
* button using [TouchableOpacity](docs/touchableopacity.html)
|
||||
* or [TouchableNativeFeedback](docs/touchablenativefeedback.html).
|
||||
* For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js).
|
||||
* Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button).
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```
|
||||
* import { Button } from 'react-native';
|
||||
* ...
|
||||
*
|
||||
* <Button
|
||||
* onPress={onPressLearnMore}
|
||||
* title="Learn More"
|
||||
* color="#841584"
|
||||
* accessibilityLabel="Learn more about this purple button"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
||||
export default class Button extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
color,
|
||||
onPress,
|
||||
title,
|
||||
hasTVPreferredFocus,
|
||||
disabled,
|
||||
testID,
|
||||
} = this.props;
|
||||
const buttonStyles = [styles.button];
|
||||
const textStyles = [styles.text];
|
||||
if (color) {
|
||||
if (Platform.OS === 'ios') {
|
||||
textStyles.push({ color: color });
|
||||
} else {
|
||||
buttonStyles.push({ backgroundColor: color });
|
||||
}
|
||||
}
|
||||
const accessibilityStates = [];
|
||||
if (disabled) {
|
||||
buttonStyles.push(styles.buttonDisabled);
|
||||
textStyles.push(styles.textDisabled);
|
||||
accessibilityStates.push('disabled');
|
||||
}
|
||||
invariant(
|
||||
typeof title === 'string',
|
||||
'The title prop of a Button must be a string'
|
||||
);
|
||||
const formattedTitle =
|
||||
Platform.OS === 'android' ? title.toUpperCase() : title;
|
||||
const Touchable = Platform.OS === 'android' ? RectButton : BorderlessButton;
|
||||
return (
|
||||
<Touchable
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole="button"
|
||||
accessibilityStates={accessibilityStates}
|
||||
hasTVPreferredFocus={hasTVPreferredFocus}
|
||||
testID={testID}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={buttonStyles}>
|
||||
<Text style={textStyles} disabled={disabled}>
|
||||
{formattedTitle}
|
||||
</Text>
|
||||
</View>
|
||||
</Touchable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: Platform.select({
|
||||
ios: {},
|
||||
android: {
|
||||
elevation: 4,
|
||||
// Material design blue from https://material.google.com/style/color.html#color-color-palette
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 2,
|
||||
},
|
||||
}),
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
padding: 8,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
// iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
|
||||
color: '#007AFF',
|
||||
fontSize: 18,
|
||||
},
|
||||
android: {
|
||||
color: 'white',
|
||||
fontWeight: '500',
|
||||
},
|
||||
}),
|
||||
},
|
||||
buttonDisabled: Platform.select({
|
||||
ios: {},
|
||||
android: {
|
||||
elevation: 0,
|
||||
backgroundColor: '#dfdfdf',
|
||||
},
|
||||
}),
|
||||
textDisabled: Platform.select({
|
||||
ios: {
|
||||
color: '#cdcdcd',
|
||||
},
|
||||
android: {
|
||||
color: '#a1a1a1',
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button as RNButton, StyleSheet, View, Platform } from 'react-native';
|
||||
import { StyleSheet, View, Platform } from 'react-native';
|
||||
import BaseButton from './Button';
|
||||
import React from 'react';
|
||||
|
||||
export const Button = props => (
|
||||
<View style={styles.margin}>
|
||||
<RNButton {...props} />
|
||||
<BaseButton {...props} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -2,33 +2,33 @@
|
||||
"name": "NavigationPlayground",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "react-native-scripts start",
|
||||
"eject": "react-native-scripts eject",
|
||||
"android": "react-native-scripts android",
|
||||
"ios": "react-native-scripts ios",
|
||||
"postinstall": "rm -rf node_modules/react-navigation/{.git,node_modules,examples}",
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"test": "flow"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^27.0.0",
|
||||
"expo": "^32.0.0",
|
||||
"hoist-non-react-statics": "^3.0.1",
|
||||
"invariant": "^2.2.4",
|
||||
"react": "16.3.1",
|
||||
"react-native": "^0.55.0",
|
||||
"react": "16.5.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
|
||||
"react-native-iphone-x-helper": "^1.0.2",
|
||||
"react-navigation": "link:../..",
|
||||
"react-native-paper": "^2.1.3",
|
||||
"react-navigation": "../..",
|
||||
"react-navigation-header-buttons": "^0.0.4",
|
||||
"react-navigation-material-bottom-tabs": "0.1.3",
|
||||
"react-navigation-tabs": "^0.5.1"
|
||||
"react-navigation-material-bottom-tabs": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^22.4.1",
|
||||
"babel-plugin-transform-remove-console": "^6.9.0",
|
||||
"flow-bin": "^0.67.0",
|
||||
"jest": "^22.1.3",
|
||||
"jest-expo": "^26.0.0",
|
||||
"react-native-scripts": "^1.5.0",
|
||||
"react-test-renderer": "16.3.0-alpha.1"
|
||||
"jest-expo": "^28.0.0",
|
||||
"react-test-renderer": "16.3.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo",
|
||||
@@ -38,13 +38,6 @@
|
||||
"json",
|
||||
"jsx",
|
||||
"node"
|
||||
],
|
||||
"modulePathIgnorePatterns": [
|
||||
"/node_modules/.*/react-native/",
|
||||
"/node_modules/.*/react/"
|
||||
],
|
||||
"transformIgnorePatterns": [
|
||||
"/node_modules/(?!react-native|react-navigation)/"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const blacklist = require('metro/src/blacklist');
|
||||
|
||||
module.exports = {
|
||||
getBlacklistRE() {
|
||||
return blacklist([
|
||||
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
|
||||
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react-native-paper\/(.*)/,
|
||||
/react\-navigation\/node_modules\/@expo\/vector-icons\/(.*)/,
|
||||
]);
|
||||
},
|
||||
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
||||
};
|
||||
|
||||
function getNodeModulesForDirectory(rootPath) {
|
||||
const nodeModulePath = path.join(rootPath, 'node_modules');
|
||||
const folders = fs.readdirSync(nodeModulePath);
|
||||
return folders.reduce((modules, folderName) => {
|
||||
const folderPath = path.join(nodeModulePath, folderName);
|
||||
if (folderName.startsWith('@')) {
|
||||
const scopedModuleFolders = fs.readdirSync(folderPath);
|
||||
const scopedModules = scopedModuleFolders.reduce(
|
||||
(scopedModules, scopedFolderName) => {
|
||||
scopedModules[
|
||||
`${folderName}/${scopedFolderName}`
|
||||
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
|
||||
return scopedModules;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return Object.assign({}, modules, scopedModules);
|
||||
}
|
||||
modules[folderName] = maybeResolveSymlink(folderPath);
|
||||
return modules;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function maybeResolveSymlink(maybeSymlinkPath) {
|
||||
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
|
||||
const resolved = path.resolve(
|
||||
path.dirname(maybeSymlinkPath),
|
||||
fs.readlinkSync(maybeSymlinkPath)
|
||||
);
|
||||
return resolved;
|
||||
}
|
||||
return maybeSymlinkPath;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
# Examples
|
||||
|
||||
## Usage
|
||||
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": ["babel-preset-expo"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
examples/ReduxExample/.gitignore
vendored
3
examples/ReduxExample/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
npm-debug.*
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { AppRegistry } from 'react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
|
||||
import AppReducer from './src/reducers';
|
||||
import { AppNavigator, middleware } from './src/navigators/AppNavigator';
|
||||
|
||||
const store = createStore(AppReducer, applyMiddleware(middleware));
|
||||
|
||||
class ReduxExampleApp extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<AppNavigator />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AppRegistry.registerComponent('ReduxExample', () => ReduxExampleApp);
|
||||
|
||||
export default ReduxExampleApp;
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
import App from './App';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const rendered = renderer.create(<App />).toJSON();
|
||||
expect(rendered).toBeTruthy();
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
# Redux example
|
||||
|
||||
## Usage
|
||||
|
||||
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "ReduxExample",
|
||||
"description": "Try out react-navigation with this awesome Redux example",
|
||||
"version": "1.0.0",
|
||||
"slug": "ReduxExample",
|
||||
"privacy": "public",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"icon": "./assets/icons/react-navigation.png",
|
||||
"loading": {
|
||||
"icon": "./assets/icons/react-navigation.png",
|
||||
"hideExponentText": false
|
||||
},
|
||||
"sdkVersion": "27.0.0",
|
||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "ReduxExample",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"scripts": {
|
||||
"start": "react-native-scripts start",
|
||||
"eject": "react-native-scripts eject",
|
||||
"android": "react-native-scripts android",
|
||||
"ios": "react-native-scripts ios",
|
||||
"test": "node node_modules/jest/bin/jest.js"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo",
|
||||
"modulePathIgnorePatterns": [
|
||||
"/node_modules/.*/react-native/",
|
||||
"/node_modules/.*/react/"
|
||||
],
|
||||
"transformIgnorePatterns": [
|
||||
"/node_modules/(?!react-native|react-navigation)/"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^27.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "16.3.1",
|
||||
"react-native": "^0.55.0",
|
||||
"react-navigation": "link:../..",
|
||||
"react-navigation-redux-helpers": "^2.0.0-beta.1",
|
||||
"react-redux": "^5.0.6",
|
||||
"redux": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^22.4.1",
|
||||
"flow-bin": "^0.74.0",
|
||||
"jest": "^22.1.3",
|
||||
"jest-expo": "^25.1.0",
|
||||
"react-native-scripts": "^1.3.1",
|
||||
"react-test-renderer": "16.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const blacklist = require('metro/src/blacklist');
|
||||
|
||||
module.exports = {
|
||||
getBlacklistRE() {
|
||||
return blacklist([
|
||||
/react\-navigation\/examples\/(?!ReduxExample).*/,
|
||||
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react\/(.*)/
|
||||
]);
|
||||
},
|
||||
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
||||
};
|
||||
|
||||
function getNodeModulesForDirectory(rootPath) {
|
||||
const nodeModulePath = path.join(rootPath, 'node_modules');
|
||||
const folders = fs.readdirSync(nodeModulePath);
|
||||
return folders.reduce((modules, folderName) => {
|
||||
const folderPath = path.join(nodeModulePath, folderName);
|
||||
if (folderName.startsWith('@')) {
|
||||
const scopedModuleFolders = fs.readdirSync(folderPath);
|
||||
const scopedModules = scopedModuleFolders.reduce(
|
||||
(scopedModules, scopedFolderName) => {
|
||||
scopedModules[
|
||||
`${folderName}/${scopedFolderName}`
|
||||
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
|
||||
return scopedModules;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return Object.assign({}, modules, scopedModules);
|
||||
}
|
||||
modules[folderName] = maybeResolveSymlink(folderPath);
|
||||
return modules;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function maybeResolveSymlink(maybeSymlinkPath) {
|
||||
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
|
||||
const resolved = path.resolve(
|
||||
path.dirname(maybeSymlinkPath),
|
||||
fs.readlinkSync(maybeSymlinkPath)
|
||||
);
|
||||
return resolved;
|
||||
}
|
||||
return maybeSymlinkPath;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button } from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
|
||||
const AuthButton = ({ logout, loginScreen, isLoggedIn }) => (
|
||||
<Button
|
||||
title={isLoggedIn ? 'Log Out' : 'Open Login Screen'}
|
||||
onPress={isLoggedIn ? logout : loginScreen}
|
||||
/>
|
||||
);
|
||||
|
||||
AuthButton.propTypes = {
|
||||
isLoggedIn: PropTypes.bool.isRequired,
|
||||
logout: PropTypes.func.isRequired,
|
||||
loginScreen: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isLoggedIn: state.auth.isLoggedIn,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
logout: () => dispatch({ type: 'Logout' }),
|
||||
loginScreen: () =>
|
||||
dispatch(NavigationActions.navigate({ routeName: 'Login' })),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AuthButton);
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
welcome: {
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const LoginScreen = ({ navigation }) => (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>
|
||||
Screen A
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
This is great
|
||||
</Text>
|
||||
<Button
|
||||
onPress={() => navigation.dispatch({ type: 'Login' })}
|
||||
title="Log in"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
LoginScreen.propTypes = {
|
||||
navigation: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
LoginScreen.navigationOptions = {
|
||||
title: 'Log In',
|
||||
};
|
||||
|
||||
export default LoginScreen;
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, StyleSheet, Text, View } from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
welcome: {
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const LoginStatusMessage = ({ isLoggedIn, dispatch }) => {
|
||||
if (!isLoggedIn) {
|
||||
return <Text>Please log in</Text>;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.welcome}>
|
||||
{'You are "logged in" right now'}
|
||||
</Text>
|
||||
<Button
|
||||
onPress={() =>
|
||||
dispatch(NavigationActions.navigate({ routeName: 'Profile' }))}
|
||||
title="Profile"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
LoginStatusMessage.propTypes = {
|
||||
isLoggedIn: PropTypes.bool.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isLoggedIn: state.auth.isLoggedIn,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(LoginStatusMessage);
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import LoginStatusMessage from './LoginStatusMessage';
|
||||
import AuthButton from './AuthButton';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
});
|
||||
|
||||
const MainScreen = () => (
|
||||
<View style={styles.container}>
|
||||
<LoginStatusMessage />
|
||||
<AuthButton />
|
||||
</View>
|
||||
);
|
||||
|
||||
MainScreen.navigationOptions = {
|
||||
title: 'Home Screen',
|
||||
};
|
||||
|
||||
export default MainScreen;
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
welcome: {
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const ProfileScreen = () => (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>
|
||||
Profile Screen
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
ProfileScreen.navigationOptions = {
|
||||
title: 'Profile',
|
||||
};
|
||||
|
||||
export default ProfileScreen;
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStackNavigator } from 'react-navigation';
|
||||
import {
|
||||
reduxifyNavigator,
|
||||
createReactNavigationReduxMiddleware,
|
||||
} from 'react-navigation-redux-helpers';
|
||||
|
||||
import LoginScreen from '../components/LoginScreen';
|
||||
import MainScreen from '../components/MainScreen';
|
||||
import ProfileScreen from '../components/ProfileScreen';
|
||||
|
||||
const middleware = createReactNavigationReduxMiddleware(
|
||||
'root',
|
||||
state => state.nav
|
||||
);
|
||||
|
||||
const RootNavigator = createStackNavigator({
|
||||
Login: { screen: LoginScreen },
|
||||
Main: { screen: MainScreen },
|
||||
Profile: { screen: ProfileScreen },
|
||||
});
|
||||
|
||||
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
state: state.nav,
|
||||
});
|
||||
|
||||
const AppNavigator = connect(mapStateToProps)(AppWithNavigationState);
|
||||
|
||||
export { RootNavigator, AppNavigator, middleware };
|
||||
@@ -1,57 +0,0 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
|
||||
import { RootNavigator } from '../navigators/AppNavigator';
|
||||
|
||||
// Start with two routes: The Main screen, with the Login screen on top.
|
||||
const firstAction = RootNavigator.router.getActionForPathAndParams('Main');
|
||||
const tempNavState = RootNavigator.router.getStateForAction(firstAction);
|
||||
const secondAction = RootNavigator.router.getActionForPathAndParams('Login');
|
||||
const initialNavState = RootNavigator.router.getStateForAction(
|
||||
secondAction,
|
||||
tempNavState
|
||||
);
|
||||
|
||||
function nav(state = initialNavState, action) {
|
||||
let nextState;
|
||||
switch (action.type) {
|
||||
case 'Login':
|
||||
nextState = RootNavigator.router.getStateForAction(
|
||||
NavigationActions.back(),
|
||||
state
|
||||
);
|
||||
break;
|
||||
case 'Logout':
|
||||
nextState = RootNavigator.router.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'Login' }),
|
||||
state
|
||||
);
|
||||
break;
|
||||
default:
|
||||
nextState = RootNavigator.router.getStateForAction(action, state);
|
||||
break;
|
||||
}
|
||||
|
||||
// Simply return the original `state` if `nextState` is null or undefined.
|
||||
return nextState || state;
|
||||
}
|
||||
|
||||
const initialAuthState = { isLoggedIn: false };
|
||||
|
||||
function auth(state = initialAuthState, action) {
|
||||
switch (action.type) {
|
||||
case 'Login':
|
||||
return { ...state, isLoggedIn: true };
|
||||
case 'Logout':
|
||||
return { ...state, isLoggedIn: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const AppReducer = combineReducers({
|
||||
nav,
|
||||
auth,
|
||||
});
|
||||
|
||||
export default AppReducer;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": ["babel-preset-expo"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
examples/SafeAreaExample/.gitignore
vendored
3
examples/SafeAreaExample/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,244 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { StackNavigator, withNavigation } from 'react-navigation';
|
||||
import { Constants } from 'expo';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import TabsScreen from './screens/TabsScreen';
|
||||
import DrawerScreen from './screens/DrawerScreen';
|
||||
import createDumbStack from './screens/createDumbStack';
|
||||
import createDumbTabs from './screens/createDumbTabs';
|
||||
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return <RootStack />;
|
||||
}
|
||||
}
|
||||
|
||||
@withNavigation
|
||||
class ExampleItem extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
borderBottomColor: '#eee',
|
||||
borderBottomWidth: 1,
|
||||
}}>
|
||||
<Touchable
|
||||
onPress={this._handlePress}
|
||||
style={{
|
||||
height: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: this.props.common ? '#fffcd3' : '#fff',
|
||||
}}>
|
||||
<Text style={{ fontSize: 15 }}>
|
||||
{this.props.title} {this.props.common ? '(commonly used)' : null}
|
||||
</Text>
|
||||
</Touchable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_handlePress = () => {
|
||||
this.props.navigation.navigate(this.props.route);
|
||||
};
|
||||
}
|
||||
|
||||
class ExampleListScreen extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{ paddingTop: 50, backgroundColor: '#fff' }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 25,
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
paddingBottom: 20,
|
||||
}}>
|
||||
SafeAreaView Examples
|
||||
</Text>
|
||||
|
||||
<ExampleItem title="Basic Tabs" route="tabs" common />
|
||||
{/* <ExampleItem title="Basic Drawer" route="drawer" /> */}
|
||||
<ExampleItem title="Header height" route="headerHeight" common />
|
||||
<ExampleItem title="Header padding" route="headerPadding" />
|
||||
<ExampleItem
|
||||
title="Header height and padding"
|
||||
route="headerHeightAndPadding"
|
||||
/>
|
||||
<ExampleItem
|
||||
title="Header padding as percent"
|
||||
route="headerPaddingPercent"
|
||||
/>
|
||||
<ExampleItem title="Header with margin" route="headerMargin" />
|
||||
<ExampleItem title="Tab bar height" route="tabBarHeight" common />
|
||||
<ExampleItem title="Tab bar padding" route="tabBarPadding" common />
|
||||
<ExampleItem
|
||||
common
|
||||
title="Tab bar height and padding"
|
||||
route="tabBarHeightAndPadding"
|
||||
/>
|
||||
<ExampleItem title="Tab bar margin" route="tabBarMargin" />
|
||||
</ScrollView>
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: Constants.statusBarHeight,
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const StackWithHeaderHeight = createDumbStack({
|
||||
title: 'height: 150',
|
||||
headerStyle: { height: 150 },
|
||||
});
|
||||
const StackWithHeaderPadding = createDumbStack({
|
||||
title: 'padding: 100',
|
||||
headerStyle: { padding: 100 },
|
||||
});
|
||||
const StackWithHeaderHeightAndPadding = createDumbStack({
|
||||
title: 'height: 150, padding: 100',
|
||||
headerStyle: { height: 150, padding: 100 },
|
||||
});
|
||||
const StackWithHeaderPaddingPercent = createDumbStack({
|
||||
title: 'padding: 60%',
|
||||
headerStyle: { padding: '60%' },
|
||||
});
|
||||
const StackWithHeaderMargin = createDumbStack({
|
||||
title: 'margin: 20 (but why? :/)',
|
||||
headerStyle: { margin: 20 },
|
||||
});
|
||||
|
||||
const TabBarWithHeight = createDumbTabs(
|
||||
{
|
||||
tabBarLabel: 'label!',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
height: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
createDumbStack({
|
||||
title: 'tabBar height 100',
|
||||
})
|
||||
);
|
||||
|
||||
const TabBarWithPadding = createDumbTabs(
|
||||
{
|
||||
tabBarLabel: 'label!',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
padding: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
createDumbStack({
|
||||
title: 'tabBar padding 20',
|
||||
})
|
||||
);
|
||||
|
||||
const TabBarWithHeightAndPadding = createDumbTabs(
|
||||
{
|
||||
tabBarLabel: 'label!',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
padding: 20,
|
||||
height: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
createDumbStack({
|
||||
title: 'tabBar height 100 padding 20',
|
||||
})
|
||||
);
|
||||
|
||||
const TabBarWithMargin = createDumbTabs(
|
||||
{
|
||||
tabBarLabel: 'label!',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
margin: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
createDumbStack({
|
||||
title: 'tabBar margin 20',
|
||||
})
|
||||
);
|
||||
|
||||
const RootStack = StackNavigator(
|
||||
{
|
||||
exampleList: {
|
||||
screen: ExampleListScreen,
|
||||
},
|
||||
tabs: {
|
||||
screen: TabsScreen,
|
||||
},
|
||||
headerHeight: {
|
||||
screen: StackWithHeaderHeight,
|
||||
},
|
||||
headerPadding: {
|
||||
screen: StackWithHeaderPadding,
|
||||
},
|
||||
headerHeightAndPadding: {
|
||||
screen: StackWithHeaderHeightAndPadding,
|
||||
},
|
||||
headerPaddingPercent: {
|
||||
screen: StackWithHeaderPaddingPercent,
|
||||
},
|
||||
headerMargin: {
|
||||
screen: StackWithHeaderMargin,
|
||||
},
|
||||
tabBarHeight: {
|
||||
screen: TabBarWithHeight,
|
||||
},
|
||||
tabBarPadding: {
|
||||
screen: TabBarWithPadding,
|
||||
},
|
||||
tabBarHeightAndPadding: {
|
||||
screen: TabBarWithHeightAndPadding,
|
||||
},
|
||||
tabBarMargin: {
|
||||
screen: TabBarWithMargin,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerMode: 'none',
|
||||
cardStyle: {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// basic tabs (different navbar color, different tabbar color)
|
||||
// different header height
|
||||
// different header padding
|
||||
// different header height and padding
|
||||
// different header margin
|
||||
// different tabbar height
|
||||
// different tabbar padding
|
||||
// different tabbar height and padding
|
||||
// different tabbar margin
|
||||
// without navbar, without safeareaview in one tab and with safeareaview in another tab
|
||||
// all should be able to toggle between landscape and portrait
|
||||
|
||||
// basic drawer (different navbar color, mess around with drawer options)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "SafeAreaExample",
|
||||
"description": "An empty new project",
|
||||
"slug": "SafeAreaExample",
|
||||
"privacy": "public",
|
||||
"sdkVersion": "25.0.0",
|
||||
"version": "1.0.0",
|
||||
"primaryColor": "#cccccc",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"expo": "^25.0.0",
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native-platform-touchable": "^1.1.1",
|
||||
"react-navigation": "link:../.."
|
||||
},
|
||||
"name": "SafeAreaExample",
|
||||
"version": "0.0.0",
|
||||
"description": "Hello Expo!",
|
||||
"author": null
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const blacklist = require('metro/src/blacklist');
|
||||
|
||||
module.exports = {
|
||||
getBlacklistRE() {
|
||||
return blacklist([
|
||||
/react\-navigation\/examples\/(?!SafeAreaExample).*/,
|
||||
/react\-navigation\/node_modules\/react-native\/(.*)/,
|
||||
/react\-navigation\/node_modules\/react\/(.*)/
|
||||
]);
|
||||
},
|
||||
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
|
||||
};
|
||||
|
||||
function getNodeModulesForDirectory(rootPath) {
|
||||
const nodeModulePath = path.join(rootPath, 'node_modules');
|
||||
const folders = fs.readdirSync(nodeModulePath);
|
||||
return folders.reduce((modules, folderName) => {
|
||||
const folderPath = path.join(nodeModulePath, folderName);
|
||||
if (folderName.startsWith('@')) {
|
||||
const scopedModuleFolders = fs.readdirSync(folderPath);
|
||||
const scopedModules = scopedModuleFolders.reduce(
|
||||
(scopedModules, scopedFolderName) => {
|
||||
scopedModules[
|
||||
`${folderName}/${scopedFolderName}`
|
||||
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
|
||||
return scopedModules;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return Object.assign({}, modules, scopedModules);
|
||||
}
|
||||
modules[folderName] = maybeResolveSymlink(folderPath);
|
||||
return modules;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function maybeResolveSymlink(maybeSymlinkPath) {
|
||||
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
|
||||
const resolved = path.resolve(
|
||||
path.dirname(maybeSymlinkPath),
|
||||
fs.readlinkSync(maybeSymlinkPath)
|
||||
);
|
||||
return resolved;
|
||||
}
|
||||
return maybeSymlinkPath;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import createDumbStack from './createDumbStack';
|
||||
|
||||
export default createDumbStack();
|
||||
@@ -1,3 +0,0 @@
|
||||
import createDumbTabs from './createDumbTabs';
|
||||
|
||||
export default createDumbTabs();
|
||||
@@ -1,99 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
import {
|
||||
Dimensions,
|
||||
Button,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Text,
|
||||
View,
|
||||
StatusBar,
|
||||
} from 'react-native';
|
||||
import { ScreenOrientation } from 'expo';
|
||||
|
||||
const Separator = () => (
|
||||
<View
|
||||
style={{
|
||||
width: Dimensions.get('window').width - 100,
|
||||
height: 1,
|
||||
backgroundColor: '#ccc',
|
||||
marginHorizontal: 50,
|
||||
marginTop: 15,
|
||||
marginBottom: 15,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const Spacer = () => (
|
||||
<View
|
||||
style={{
|
||||
marginBottom: Platform.OS === 'android' ? 20 : 5,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export default (navigationOptions = {}) => {
|
||||
class DumbScreen extends React.Component {
|
||||
static navigationOptions = {
|
||||
title: 'Title!',
|
||||
...navigationOptions,
|
||||
headerStyle: {
|
||||
backgroundColor: '#6b52ae',
|
||||
...navigationOptions.headerStyle,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: '#fff',
|
||||
...navigationOptions.headerTitleStyle,
|
||||
},
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 30,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<Button onPress={this._goBack} title="Go back" />
|
||||
<Separator />
|
||||
<Button onPress={this._setPortrait} title="Set portrait" />
|
||||
<Spacer />
|
||||
<Button onPress={this._setLandscape} title="Set landscape" />
|
||||
<Separator />
|
||||
<Button onPress={this._hideStatusBar} title="Hide status bar" />
|
||||
<Spacer />
|
||||
<Button onPress={this._showStatusBar} title="Show status bar" />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
_goBack = () => {
|
||||
this.props.navigation.goBack(null);
|
||||
};
|
||||
|
||||
_setPortrait = () => {
|
||||
ScreenOrientation.allow(ScreenOrientation.Orientation.PORTRAIT);
|
||||
};
|
||||
|
||||
_setLandscape = () => {
|
||||
ScreenOrientation.allow(ScreenOrientation.Orientation.LANDSCAPE);
|
||||
};
|
||||
|
||||
_hideStatusBar = () => {
|
||||
StatusBar.setHidden(true, 'slide');
|
||||
};
|
||||
|
||||
_showStatusBar = () => {
|
||||
StatusBar.setHidden(false, 'slide');
|
||||
};
|
||||
}
|
||||
|
||||
return StackNavigator({
|
||||
dumb: {
|
||||
screen: DumbScreen,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { TabNavigator, TabBarBottom } from 'react-navigation';
|
||||
import createDumbStack from './createDumbStack';
|
||||
|
||||
export default (navigationOptions = {}, DumbStack = createDumbStack()) => {
|
||||
return TabNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: DumbStack,
|
||||
},
|
||||
Links: {
|
||||
screen: DumbStack,
|
||||
},
|
||||
Settings: {
|
||||
screen: DumbStack,
|
||||
},
|
||||
},
|
||||
{
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
...navigationOptions,
|
||||
tabBarIcon: ({ focused }) => {
|
||||
const { routeName } = navigation.state;
|
||||
let iconName;
|
||||
switch (routeName) {
|
||||
case 'Home':
|
||||
iconName =
|
||||
Platform.OS === 'ios'
|
||||
? `ios-information-circle${focused ? '' : '-outline'}`
|
||||
: 'md-information-circle';
|
||||
break;
|
||||
case 'Links':
|
||||
iconName =
|
||||
Platform.OS === 'ios'
|
||||
? `ios-link${focused ? '' : '-outline'}`
|
||||
: 'md-link';
|
||||
break;
|
||||
case 'Settings':
|
||||
iconName =
|
||||
Platform.OS === 'ios'
|
||||
? `ios-options${focused ? '' : '-outline'}`
|
||||
: 'md-options';
|
||||
}
|
||||
return (
|
||||
<Ionicons
|
||||
name={iconName}
|
||||
size={28}
|
||||
style={{ marginBottom: -3 }}
|
||||
color={focused ? '#6b52ae' : '#ccc'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}),
|
||||
tabBarOptions: {
|
||||
activeTintColor: '#6b52ae',
|
||||
...navigationOptions.tabBarOptions,
|
||||
style: {
|
||||
backgroundColor: '#F5F1FF',
|
||||
...(navigationOptions.tabBarOptions ? navigationOptions.tabBarOptions.style : {}),
|
||||
}
|
||||
},
|
||||
tabBarComponent: TabBarBottom,
|
||||
tabBarPosition: 'bottom',
|
||||
animationEnabled: false,
|
||||
swipeEnabled: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
126
flow/react-navigation.js
vendored
126
flow/react-navigation.js
vendored
@@ -143,6 +143,14 @@ declare module 'react-navigation' {
|
||||
+type: 'Navigation/TOGGLE_DRAWER',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationDrawerOpenedAction = {|
|
||||
+type: 'Navigation/DRAWER_OPENED',
|
||||
+key?: string,
|
||||
|};
|
||||
declare export type NavigationDrawerClosedAction = {|
|
||||
+type: 'Navigation/DRAWER_CLOSED',
|
||||
+key?: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationAction =
|
||||
| NavigationBackAction
|
||||
@@ -157,7 +165,9 @@ declare module 'react-navigation' {
|
||||
| NavigationCompleteTransitionAction
|
||||
| NavigationOpenDrawerAction
|
||||
| NavigationCloseDrawerAction
|
||||
| NavigationToggleDrawerAction;
|
||||
| NavigationToggleDrawerAction
|
||||
| NavigationDrawerOpenedAction
|
||||
| NavigationDrawerClosedAction;
|
||||
|
||||
/**
|
||||
* NavigationState is a tree of routes for a single navigator, where each
|
||||
@@ -184,7 +194,7 @@ declare module 'react-navigation' {
|
||||
| NavigationLeafRoute
|
||||
| NavigationStateRoute;
|
||||
|
||||
declare export type NavigationLeafRoute = {
|
||||
declare export type NavigationLeafRoute = {|
|
||||
/**
|
||||
* React's key used by some navigators. No need to specify these manually,
|
||||
* they will be defined by the router.
|
||||
@@ -204,10 +214,12 @@ declare module 'react-navigation' {
|
||||
* e.g. `{ car_id: 123 }` in a route that displays a car.
|
||||
*/
|
||||
params?: NavigationParams,
|
||||
};
|
||||
|};
|
||||
|
||||
declare export type NavigationStateRoute = NavigationLeafRoute &
|
||||
NavigationState;
|
||||
declare export type NavigationStateRoute = {|
|
||||
...NavigationLeafRoute,
|
||||
...$Exact<NavigationState>,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Router
|
||||
@@ -281,7 +293,7 @@ declare module 'react-navigation' {
|
||||
|
||||
declare export type NavigationComponent =
|
||||
| NavigationScreenComponent<NavigationRoute, *, *>
|
||||
| NavigationContainer<*, *, *>;
|
||||
| NavigationNavigator<*, *, *>;
|
||||
|
||||
declare interface withOptionalNavigationOptions<Options> {
|
||||
navigationOptions?: NavigationScreenConfig<Options>;
|
||||
@@ -305,7 +317,7 @@ declare module 'react-navigation' {
|
||||
State: NavigationState,
|
||||
Options: {},
|
||||
Props: {}
|
||||
> = React$ComponentType<{
|
||||
> = React$StatelessFunctionalComponent<{
|
||||
...Props,
|
||||
...NavigationNavigatorProps<Options, State>,
|
||||
}> &
|
||||
@@ -384,7 +396,7 @@ declare module 'react-navigation' {
|
||||
initialRouteName?: string,
|
||||
initialRouteParams?: NavigationParams,
|
||||
paths?: NavigationPathsConfig,
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
defaultNavigationOptions?: NavigationScreenConfig<*>,
|
||||
initialRouteKey?: string,
|
||||
|};
|
||||
|
||||
@@ -392,10 +404,18 @@ declare module 'react-navigation' {
|
||||
mode?: 'card' | 'modal',
|
||||
headerMode?: HeaderMode,
|
||||
headerTransitionPreset?: 'fade-in-place' | 'uikit',
|
||||
headerLayoutPreset?: 'left' | 'center',
|
||||
headerBackTitleVisible?: boolean,
|
||||
cardStyle?: ViewStyleProp,
|
||||
transitionConfig?: () => TransitionConfig,
|
||||
transitionConfig?: (
|
||||
transitionProps: NavigationTransitionProps,
|
||||
prevTransitionProps: ?NavigationTransitionProps,
|
||||
isModal: boolean
|
||||
) => TransitionConfig,
|
||||
onTransitionStart?: () => void,
|
||||
onTransitionEnd?: () => void,
|
||||
transparentCard?: boolean,
|
||||
disableKeyboardHandling?: boolean,
|
||||
|};
|
||||
|
||||
declare export type StackNavigatorConfig = {|
|
||||
@@ -411,7 +431,7 @@ declare module 'react-navigation' {
|
||||
initialRouteName?: string,
|
||||
initialRouteParams?: NavigationParams,
|
||||
paths?: NavigationPathsConfig,
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
defaultNavigationOptions?: NavigationScreenConfig<*>,
|
||||
order?: Array<string>,
|
||||
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
|
||||
resetOnBlur?: boolean, // defaults to `true`
|
||||
@@ -425,7 +445,7 @@ declare module 'react-navigation' {
|
||||
initialRouteName?: string,
|
||||
initialRouteParams?: NavigationParams,
|
||||
paths?: NavigationPathsConfig,
|
||||
navigationOptions?: NavigationScreenConfig<*>,
|
||||
defaultNavigationOptions?: 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
|
||||
@@ -513,7 +533,29 @@ declare module 'react-navigation' {
|
||||
eventName: string,
|
||||
callback: NavigationEventCallback
|
||||
) => NavigationEventSubscription,
|
||||
getParam: (paramName: string, fallback?: any) => any,
|
||||
getParam: <ParamName: string>(
|
||||
paramName: ParamName,
|
||||
fallback?: $ElementType<
|
||||
$PropertyType<
|
||||
{|
|
||||
...{| params: {| [ParamName]: void |} |},
|
||||
...$Exact<S>,
|
||||
|},
|
||||
'params'
|
||||
>,
|
||||
ParamName
|
||||
>
|
||||
) => $ElementType<
|
||||
$PropertyType<
|
||||
{|
|
||||
...{| params: {| [ParamName]: void |} |},
|
||||
...$Exact<S>,
|
||||
|},
|
||||
'params'
|
||||
>,
|
||||
ParamName
|
||||
>,
|
||||
dangerouslyGetParent: () => NavigationScreenProp<*>,
|
||||
isFocused: () => boolean,
|
||||
// Shared action creators that exist for all routers
|
||||
goBack: (routeKey?: ?string) => boolean,
|
||||
@@ -557,6 +599,21 @@ declare module 'react-navigation' {
|
||||
navigationOptions?: O,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* NavigationEvents component
|
||||
*/
|
||||
|
||||
declare type _NavigationEventsProps = {
|
||||
navigation?: NavigationScreenProp<NavigationState>,
|
||||
onWillFocus?: NavigationEventCallback,
|
||||
onDidFocus?: NavigationEventCallback,
|
||||
onWillBlur?: NavigationEventCallback,
|
||||
onDidBlur?: NavigationEventCallback,
|
||||
};
|
||||
declare export var NavigationEvents: React$ComponentType<
|
||||
_NavigationEventsProps
|
||||
>;
|
||||
|
||||
/**
|
||||
* Navigation container
|
||||
*/
|
||||
@@ -621,8 +678,8 @@ declare module 'react-navigation' {
|
||||
// The value that represents the progress of the transition when navigation
|
||||
// state changes from one to another. Its numeric value will range from 0
|
||||
// to 1.
|
||||
// progress.__getAnimatedValue() < 1 : transtion is happening.
|
||||
// progress.__getAnimatedValue() == 1 : transtion completes.
|
||||
// progress.__getAnimatedValue() < 1 : transition is happening.
|
||||
// progress.__getAnimatedValue() == 1 : transition completes.
|
||||
progress: AnimatedValue,
|
||||
|
||||
// All the scenes of the transitioner.
|
||||
@@ -704,6 +761,9 @@ declare module 'react-navigation' {
|
||||
* Now we type the actual exported module
|
||||
*/
|
||||
|
||||
declare export function createAppContainer<S: NavigationState, O: {}>(
|
||||
Component: NavigationNavigator<S, O, *>
|
||||
): NavigationContainer<S, O, *>;
|
||||
declare export function createNavigationContainer<S: NavigationState, O: {}>(
|
||||
Component: NavigationNavigator<S, O, *>
|
||||
): NavigationContainer<S, O, *>;
|
||||
@@ -782,7 +842,7 @@ declare module 'react-navigation' {
|
||||
actions: Array<NavigationNavigateAction>,
|
||||
}) => NavigationResetAction,
|
||||
replace: (payload: {
|
||||
key: string,
|
||||
key?: string,
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction,
|
||||
@@ -796,6 +856,8 @@ declare module 'react-navigation' {
|
||||
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
|
||||
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
|
||||
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
|
||||
DRAWER_OPENED: 'Navigation/DRAWER_OPENED',
|
||||
DRAWER_CLOSED: 'Navigation/DRAWER_CLOSED',
|
||||
|
||||
openDrawer: (payload: {
|
||||
key?: string,
|
||||
@@ -833,11 +895,11 @@ declare module 'react-navigation' {
|
||||
declare export function StackNavigator(
|
||||
routeConfigMap: NavigationRouteConfigMap,
|
||||
stackConfig?: StackNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare export function createStackNavigator(
|
||||
routeConfigMap: NavigationRouteConfigMap,
|
||||
stackConfig?: StackNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
|
||||
declare type _TabViewConfig = {|
|
||||
tabBarComponent?: React$ElementType,
|
||||
@@ -861,31 +923,31 @@ declare module 'react-navigation' {
|
||||
declare export function TabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare export function createTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
/* TODO: fix the config for each of these tab navigator types */
|
||||
declare export function createBottomTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare export function createMaterialTopTabNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _TabNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare type _SwitchNavigatorConfig = {|
|
||||
...NavigationSwitchRouterConfig,
|
||||
|};
|
||||
declare export function SwitchNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _SwitchNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare export function createSwitchNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _SwitchNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
|
||||
declare type _DrawerViewConfig = {|
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
@@ -906,11 +968,11 @@ declare module 'react-navigation' {
|
||||
declare export function DrawerNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _DrawerNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
declare export function createDrawerNavigator(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config?: _DrawerNavigatorConfig
|
||||
): NavigationContainer<*, *, *>;
|
||||
): NavigationNavigator<*, *, *>;
|
||||
|
||||
declare export function StackRouter(
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
@@ -1146,19 +1208,19 @@ declare module 'react-navigation' {
|
||||
};
|
||||
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
|
||||
|
||||
declare export function withNavigation<Props: {}>(
|
||||
Component: React$ComponentType<Props>
|
||||
declare export function withNavigation<Props: {}, ComponentType: React$ComponentType<Props>>(
|
||||
Component: ComponentType
|
||||
): React$ComponentType<
|
||||
$Diff<
|
||||
Props,
|
||||
React$ElementConfig<ComponentType>,
|
||||
{
|
||||
navigation: NavigationScreenProp<NavigationStateRoute> | void,
|
||||
}
|
||||
>
|
||||
>;
|
||||
declare export function withNavigationFocus<Props: {}>(
|
||||
Component: React$ComponentType<Props>
|
||||
): React$ComponentType<$Diff<Props, { isFocused: boolean | void }>>;
|
||||
declare export function withNavigationFocus<Props: {}, ComponentType: React$ComponentType<Props>>(
|
||||
Component: ComponentType
|
||||
): React$ComponentType<$Diff<React$ElementConfig<ComponentType>, { isFocused: boolean | void }>>;
|
||||
|
||||
declare export function getNavigation<State: NavigationState, Options: {}>(
|
||||
router: NavigationRouter<State, Options>,
|
||||
@@ -1166,6 +1228,6 @@ declare module 'react-navigation' {
|
||||
dispatch: NavigationDispatch,
|
||||
actionSubscribers: Set<NavigationEventCallback>,
|
||||
getScreenProps: () => {},
|
||||
getCurrentNavigation: () => NavigationScreenProp<State>
|
||||
getCurrentNavigation: () => ?NavigationScreenProp<State>
|
||||
): NavigationScreenProp<State>;
|
||||
}
|
||||
|
||||
31
package.json
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "2.5.1",
|
||||
"version": "3.1.4",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/react-navigation.js",
|
||||
"repository": {
|
||||
@@ -19,25 +19,26 @@
|
||||
"test-update-snapshot": "jest --updateSnapshot",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint --fix .",
|
||||
"precommit": "lint-staged"
|
||||
"precommit": "lint-staged",
|
||||
"release": "release-it"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
"src",
|
||||
"NavigationTestUtils.js"
|
||||
],
|
||||
"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",
|
||||
"react-lifecycles-compat": "^3",
|
||||
"react-native-safe-area-view": "^0.8.0",
|
||||
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
||||
"react-navigation-drawer": "0.4.1",
|
||||
"react-navigation-tabs": "0.5.1"
|
||||
"@react-navigation/core": "3.0.3",
|
||||
"@react-navigation/native": "3.1.4",
|
||||
"react-navigation-drawer": "1.1.0",
|
||||
"react-navigation-stack": "1.0.8",
|
||||
"react-navigation-tabs": "1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
@@ -46,6 +47,7 @@
|
||||
"babel-jest": "^22.4.1",
|
||||
"babel-preset-react-native": "^2.1.0",
|
||||
"codecov": "^2.2.0",
|
||||
"conventional-changelog-cli": "^2.0.5",
|
||||
"eslint": "^4.2.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
@@ -61,7 +63,8 @@
|
||||
"react": "16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native-vector-icons": "^4.2.0",
|
||||
"react-test-renderer": "^16.0.0"
|
||||
"react-test-renderer": "^16.0.0",
|
||||
"release-it": "^7.6.1"
|
||||
},
|
||||
"jest": {
|
||||
"notify": true,
|
||||
@@ -88,7 +91,7 @@
|
||||
"<rootDir>/examples/"
|
||||
],
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator)"
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack|@react-navigation/core|@react-navigation/native)"
|
||||
]
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
const BACK = 'Navigation/BACK';
|
||||
const INIT = 'Navigation/INIT';
|
||||
const NAVIGATE = 'Navigation/NAVIGATE';
|
||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
||||
|
||||
const back = (payload = {}) => ({
|
||||
type: BACK,
|
||||
key: payload.key,
|
||||
immediate: payload.immediate,
|
||||
});
|
||||
|
||||
const init = (payload = {}) => {
|
||||
const action = {
|
||||
type: INIT,
|
||||
};
|
||||
if (payload.params) {
|
||||
action.params = payload.params;
|
||||
}
|
||||
return action;
|
||||
};
|
||||
|
||||
const navigate = payload => {
|
||||
const action = {
|
||||
type: NAVIGATE,
|
||||
routeName: payload.routeName,
|
||||
};
|
||||
if (payload.params) {
|
||||
action.params = payload.params;
|
||||
}
|
||||
if (payload.action) {
|
||||
action.action = payload.action;
|
||||
}
|
||||
if (payload.key) {
|
||||
action.key = payload.key;
|
||||
}
|
||||
return action;
|
||||
};
|
||||
|
||||
const setParams = payload => ({
|
||||
type: SET_PARAMS,
|
||||
key: payload.key,
|
||||
params: payload.params,
|
||||
});
|
||||
|
||||
export default {
|
||||
// Action constants
|
||||
BACK,
|
||||
INIT,
|
||||
NAVIGATE,
|
||||
SET_PARAMS,
|
||||
|
||||
// Action creators
|
||||
back,
|
||||
init,
|
||||
navigate,
|
||||
setParams,
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import {
|
||||
BackAndroid as DeprecatedBackAndroid,
|
||||
BackHandler as ModernBackHandler,
|
||||
MaskedViewIOS,
|
||||
} from 'react-native';
|
||||
|
||||
const BackHandler = ModernBackHandler || DeprecatedBackAndroid;
|
||||
|
||||
export { BackHandler, MaskedViewIOS };
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { BackHandler, View } from 'react-native';
|
||||
|
||||
const MaskedViewIOS = () => <View>{this.props.children}</View>;
|
||||
|
||||
export { BackHandler, MaskedViewIOS };
|
||||
@@ -1,199 +0,0 @@
|
||||
import invariant from './utils/invariant';
|
||||
|
||||
/**
|
||||
* Utilities to perform atomic operation with navigate state and routes.
|
||||
*
|
||||
* ```javascript
|
||||
* const state1 = {key: 'screen 1'};
|
||||
* const state2 = NavigationStateUtils.push(state1, {key: 'screen 2'});
|
||||
* ```
|
||||
*/
|
||||
const StateUtils = {
|
||||
/**
|
||||
* Gets a route by key. If the route isn't found, returns `null`.
|
||||
*/
|
||||
get(state, key) {
|
||||
return state.routes.find(route => route.key === key) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the first index at which a given route's key can be found in the
|
||||
* routes of the navigation state, or -1 if it is not present.
|
||||
*/
|
||||
indexOf(state, key) {
|
||||
return state.routes.findIndex(route => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns `true` at which a given route's key can be found in the
|
||||
* routes of the navigation state.
|
||||
*/
|
||||
has(state, key) {
|
||||
return !!state.routes.some(route => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pushes a new route into the navigation state.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* stack is at.
|
||||
*/
|
||||
push(state, route) {
|
||||
invariant(
|
||||
StateUtils.indexOf(state, route.key) === -1,
|
||||
'should not push route with duplicated key %s',
|
||||
route.key
|
||||
);
|
||||
|
||||
const routes = state.routes.slice();
|
||||
routes.push(route);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Pops out a route from the navigation state.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* stack is at.
|
||||
*/
|
||||
pop(state) {
|
||||
if (state.index <= 0) {
|
||||
// [Note]: Over-popping does not throw error. Instead, it will be no-op.
|
||||
return state;
|
||||
}
|
||||
const routes = state.routes.slice(0, -1);
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route of the navigation state by index.
|
||||
*/
|
||||
jumpToIndex(state, index) {
|
||||
if (index === state.index) {
|
||||
return state;
|
||||
}
|
||||
|
||||
invariant(!!state.routes[index], 'invalid index %s to jump to', index);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route of the navigation state by key.
|
||||
*/
|
||||
jumpTo(state, key) {
|
||||
const index = StateUtils.indexOf(state, key);
|
||||
return StateUtils.jumpToIndex(state, index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route to the previous route.
|
||||
*/
|
||||
back(state) {
|
||||
const index = state.index - 1;
|
||||
const route = state.routes[index];
|
||||
return route ? StateUtils.jumpToIndex(state, index) : state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route to the next route.
|
||||
*/
|
||||
forward(state) {
|
||||
const index = state.index + 1;
|
||||
const route = state.routes[index];
|
||||
return route ? StateUtils.jumpToIndex(state, index) : state;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
return StateUtils.replaceAtIndex(state, index, route);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a route by a index.
|
||||
* Note that this moves the index to the positon to where the new route in the
|
||||
* stack is at.
|
||||
*/
|
||||
replaceAtIndex(state, index, route) {
|
||||
invariant(
|
||||
!!state.routes[index],
|
||||
'invalid index %s for replacing route %s',
|
||||
index,
|
||||
route.key
|
||||
);
|
||||
|
||||
if (state.routes[index] === route && index === state.index) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const routes = state.routes.slice();
|
||||
routes[index] = route;
|
||||
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets all routes.
|
||||
* 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) {
|
||||
invariant(
|
||||
routes.length && Array.isArray(routes),
|
||||
'invalid routes to replace'
|
||||
);
|
||||
|
||||
const nextIndex = index === undefined ? routes.length - 1 : index;
|
||||
|
||||
if (state.routes.length === routes.length && state.index === nextIndex) {
|
||||
const compare = (route, ii) => routes[ii] === route;
|
||||
if (state.routes.every(compare)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
invariant(!!routes[nextIndex], 'invalid index %s to reset', nextIndex);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: nextIndex,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default StateUtils;
|
||||
@@ -1,57 +0,0 @@
|
||||
import NavigationActions from '../NavigationActions';
|
||||
|
||||
describe('generic navigation actions', () => {
|
||||
const params = { foo: 'bar' };
|
||||
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
|
||||
|
||||
it('exports back action and type', () => {
|
||||
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
|
||||
expect(NavigationActions.back({ key: 'test' })).toEqual({
|
||||
type: NavigationActions.BACK,
|
||||
key: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('exports init action and type', () => {
|
||||
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
|
||||
expect(NavigationActions.init({ params })).toEqual({
|
||||
type: NavigationActions.INIT,
|
||||
params,
|
||||
});
|
||||
});
|
||||
|
||||
it('exports navigate action and type', () => {
|
||||
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'test',
|
||||
});
|
||||
expect(
|
||||
NavigationActions.navigate({
|
||||
routeName: 'test',
|
||||
params,
|
||||
action: navigateAction,
|
||||
})
|
||||
).toEqual({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'test',
|
||||
params,
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'another',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('exports setParams action and type', () => {
|
||||
expect(
|
||||
NavigationActions.setParams({
|
||||
key: 'test',
|
||||
params,
|
||||
})
|
||||
).toEqual({
|
||||
type: NavigationActions.SET_PARAMS,
|
||||
key: 'test',
|
||||
params,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,265 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import createStackNavigator from '../navigators/createStackNavigator';
|
||||
import createNavigationContainer, {
|
||||
_TESTING_ONLY_reset_container_count,
|
||||
} from '../createNavigationContainer';
|
||||
|
||||
describe('NavigationContainer', () => {
|
||||
jest.useFakeTimers();
|
||||
beforeEach(() => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
});
|
||||
|
||||
const FooScreen = () => <div />;
|
||||
const BarScreen = () => <div />;
|
||||
const BazScreen = () => <div />;
|
||||
const CarScreen = () => <div />;
|
||||
const DogScreen = () => <div />;
|
||||
const ElkScreen = () => <div />;
|
||||
const Stack = createStackNavigator(
|
||||
{
|
||||
foo: {
|
||||
screen: FooScreen,
|
||||
},
|
||||
bar: {
|
||||
screen: BarScreen,
|
||||
},
|
||||
baz: {
|
||||
screen: BazScreen,
|
||||
},
|
||||
car: {
|
||||
screen: CarScreen,
|
||||
},
|
||||
dog: {
|
||||
screen: DogScreen,
|
||||
},
|
||||
elk: {
|
||||
screen: ElkScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'foo',
|
||||
}
|
||||
);
|
||||
const NavigationContainer = createNavigationContainer(Stack);
|
||||
|
||||
describe('state.nav', () => {
|
||||
it("should be preloaded with the router's initial state", () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
expect(navigationContainer.state.nav).toMatchObject({ index: 0 });
|
||||
expect(navigationContainer.state.nav.routes).toBeInstanceOf(Array);
|
||||
expect(navigationContainer.state.nav.routes.length).toBe(1);
|
||||
expect(navigationContainer.state.nav.routes[0]).toMatchObject({
|
||||
routeName: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispatch', () => {
|
||||
it('returns true when given a valid action', () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when given an invalid action', () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(navigationContainer.dispatch(NavigationActions.back())).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('updates state.nav with an action by the next tick', () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fake the passing of a tick
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(navigationContainer.state.nav).toMatchObject({
|
||||
index: 1,
|
||||
routes: [{ routeName: 'foo' }, { routeName: 'bar' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not discard actions when called twice in one tick', () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
const initialState = JSON.parse(
|
||||
JSON.stringify(navigationContainer.state.nav)
|
||||
);
|
||||
|
||||
// First dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Make sure that the test runner has NOT synchronously applied setState before the tick
|
||||
expect(navigationContainer.state.nav).toMatchObject(initialState);
|
||||
|
||||
// Second dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'baz' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fake the passing of a tick
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(navigationContainer.state.nav).toMatchObject({
|
||||
index: 2,
|
||||
routes: [
|
||||
{ routeName: 'foo' },
|
||||
{ routeName: 'bar' },
|
||||
{ routeName: 'baz' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not discard actions when called more than 2 times in one tick', () => {
|
||||
const navigationContainer = renderer
|
||||
.create(<NavigationContainer />)
|
||||
.getInstance();
|
||||
const initialState = JSON.parse(
|
||||
JSON.stringify(navigationContainer.state.nav)
|
||||
);
|
||||
|
||||
// First dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Make sure that the test runner has NOT synchronously applied setState before the tick
|
||||
expect(navigationContainer.state.nav).toMatchObject(initialState);
|
||||
|
||||
// Second dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'baz' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Third dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'car' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fourth dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'dog' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fifth dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'elk' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fake the passing of a tick
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(navigationContainer.state.nav).toMatchObject({
|
||||
index: 5,
|
||||
routes: [
|
||||
{ routeName: 'foo' },
|
||||
{ routeName: 'bar' },
|
||||
{ routeName: 'baz' },
|
||||
{ routeName: 'car' },
|
||||
{ routeName: 'dog' },
|
||||
{ routeName: 'elk' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('warnings', () => {
|
||||
function spyConsole() {
|
||||
let spy = {};
|
||||
|
||||
beforeEach(() => {
|
||||
spy.console = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spy.console.mockRestore();
|
||||
});
|
||||
|
||||
return spy;
|
||||
}
|
||||
|
||||
describe('detached navigators', () => {
|
||||
beforeEach(() => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
});
|
||||
|
||||
let spy = spyConsole();
|
||||
|
||||
it('warns when you render more than one container explicitly', () => {
|
||||
class BlankScreen extends React.Component {
|
||||
render() {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
class RootScreen extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<ChildNavigator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ChildNavigator = createNavigationContainer(
|
||||
createStackNavigator({
|
||||
Child: BlankScreen,
|
||||
})
|
||||
);
|
||||
|
||||
const RootStack = createNavigationContainer(
|
||||
createStackNavigator({
|
||||
Root: RootScreen,
|
||||
})
|
||||
);
|
||||
|
||||
renderer.create(<RootStack />).toJSON();
|
||||
expect(spy).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,267 +0,0 @@
|
||||
import NavigationStateUtils from '../StateUtils';
|
||||
|
||||
const routeName = 'Anything';
|
||||
|
||||
describe('StateUtils', () => {
|
||||
// Getters
|
||||
it('gets route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
||||
key: 'a',
|
||||
routeName,
|
||||
});
|
||||
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
|
||||
});
|
||||
|
||||
it('gets route index', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
|
||||
});
|
||||
|
||||
it('has a route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
||||
});
|
||||
|
||||
// Push
|
||||
it('pushes a route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
};
|
||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
||||
newState
|
||||
);
|
||||
});
|
||||
|
||||
it('does not push duplicated route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() =>
|
||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
// Pop
|
||||
it('pops route', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not pop route if not applicable', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
|
||||
// Jump
|
||||
it('jumps to new index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
||||
});
|
||||
|
||||
it('jumps to new key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
||||
});
|
||||
|
||||
it('move backwards', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
it('move forwards', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
// Replace
|
||||
it('Replaces by key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Replaces by index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
||||
).toEqual({ ...state, index: 1 });
|
||||
});
|
||||
|
||||
// Reset
|
||||
it('Resets routes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(state, [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
])
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(state, []);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('Resets routes with index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
0
|
||||
)
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[{ key: 'x', routeName }, { key: 'y', routeName }],
|
||||
100
|
||||
);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NavigationContainer warnings detached navigators warns when you render more than one container explicitly 1`] = `
|
||||
Object {
|
||||
"console": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: https://reactnavigation.org/docs/common-mistakes.html#explicitly-rendering-more-than-one-navigator",
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
`;
|
||||
@@ -1,460 +0,0 @@
|
||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||
|
||||
test('child action events only flow when focused', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const focusedTestState = {
|
||||
...testState,
|
||||
index: 1,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: testState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: focusedTestState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('grandchildren subscription', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const parentBlurState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [
|
||||
{ key: 'aunt' },
|
||||
{
|
||||
key: 'parent',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const parentTransitionState = {
|
||||
...parentBlurState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
};
|
||||
const parentFocusState = {
|
||||
...parentTransitionState,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentTransitionState,
|
||||
lastState: parentBlurState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentFocusState,
|
||||
lastState: parentTransitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('grandchildren transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(2, true);
|
||||
const blurred2State = makeFakeState(2, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('grandchildren pass through transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
].slice(0, childIndex + 1),
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(0, true);
|
||||
const blurred2State = makeFakeState(0, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('child focus with transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('child focus with immediate transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
import getNavigation from '../getNavigation';
|
||||
|
||||
test('getNavigation provides default action helpers', () => {
|
||||
const router = {
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
{},
|
||||
dispatch,
|
||||
new Set(),
|
||||
() => ({}),
|
||||
() => topNav
|
||||
);
|
||||
|
||||
topNav.navigate('GreatRoute');
|
||||
|
||||
expect(dispatch.mock.calls.length).toBe(1);
|
||||
expect(dispatch.mock.calls[0][0].type).toBe('Navigation/NAVIGATE');
|
||||
expect(dispatch.mock.calls[0][0].routeName).toBe('GreatRoute');
|
||||
});
|
||||
|
||||
test('getNavigation provides router action helpers', () => {
|
||||
const router = {
|
||||
getActionCreators: () => ({
|
||||
foo: bar => ({ type: 'FooBarAction', bar }),
|
||||
}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
{},
|
||||
dispatch,
|
||||
new Set(),
|
||||
() => ({}),
|
||||
() => topNav
|
||||
);
|
||||
|
||||
topNav.foo('Great');
|
||||
|
||||
expect(dispatch.mock.calls.length).toBe(1);
|
||||
expect(dispatch.mock.calls[0][0].type).toBe('FooBarAction');
|
||||
expect(dispatch.mock.calls[0][0].bar).toBe('Great');
|
||||
});
|
||||
|
||||
test('getNavigation get child navigation with router', () => {
|
||||
const actionSubscribers = new Set();
|
||||
let navigation = null;
|
||||
|
||||
const routerA = {
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
const router = {
|
||||
childRouters: {
|
||||
RouteA: routerA,
|
||||
},
|
||||
getActionCreators: () => ({}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
},
|
||||
};
|
||||
|
||||
const initState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
key: 'a',
|
||||
routeName: 'RouteA',
|
||||
routes: [{ key: 'c', routeName: 'RouteC' }],
|
||||
index: 0,
|
||||
},
|
||||
{ key: 'b', routeName: 'RouteB' },
|
||||
],
|
||||
};
|
||||
|
||||
const topNav = getNavigation(
|
||||
router,
|
||||
initState,
|
||||
() => {},
|
||||
actionSubscribers,
|
||||
() => ({}),
|
||||
() => navigation
|
||||
);
|
||||
|
||||
const childNavA = topNav.getChildNavigation('a');
|
||||
|
||||
expect(childNavA.router).toBe(routerA);
|
||||
});
|
||||
@@ -1,388 +0,0 @@
|
||||
import React from 'react';
|
||||
import { AsyncStorage, Linking, Platform } from 'react-native';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
|
||||
import { BackHandler } from './PlatformHelpers';
|
||||
import NavigationActions from './NavigationActions';
|
||||
import getNavigation from './getNavigation';
|
||||
import invariant from './utils/invariant';
|
||||
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
|
||||
* in case it's not passed from above.
|
||||
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
|
||||
* components.
|
||||
*/
|
||||
export default function createNavigationContainer(Component) {
|
||||
class NavigationContainer extends React.Component {
|
||||
subs = null;
|
||||
|
||||
static router = Component.router;
|
||||
static navigationOptions = null;
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
validateProps(nextProps);
|
||||
return null;
|
||||
}
|
||||
|
||||
_actionEventSubscribers = new Set();
|
||||
|
||||
constructor(props) {
|
||||
super(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() && !props.persistenceKey
|
||||
? Component.router.getStateForAction(this._initialAction)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
_renderLoading() {
|
||||
return this.props.renderLoadingExperimental
|
||||
? this.props.renderLoadingExperimental()
|
||||
: null;
|
||||
}
|
||||
|
||||
_isStateful() {
|
||||
return isStateful(this.props);
|
||||
}
|
||||
|
||||
_validateProps(props) {
|
||||
if (this._isStateful()) {
|
||||
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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_urlToPathAndParams(url) {
|
||||
const params = {};
|
||||
const delimiter = this.props.uriPrefix || '://';
|
||||
let path = url.split(delimiter)[1];
|
||||
if (typeof path === 'undefined') {
|
||||
path = url;
|
||||
} else if (path === '') {
|
||||
path = '/';
|
||||
}
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
_handleOpenURL = ({ url }) => {
|
||||
const parsedUrl = this._urlToPathAndParams(url);
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const action = Component.router.getActionForPathAndParams(path, params);
|
||||
if (action) {
|
||||
this.dispatch(action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onNavigationStateChange(prevNav, nav, action) {
|
||||
if (
|
||||
typeof this.props.onNavigationStateChange === 'undefined' &&
|
||||
this._isStateful() &&
|
||||
!!process.env.REACT_NAV_LOGGING
|
||||
) {
|
||||
/* eslint-disable no-console */
|
||||
if (console.group) {
|
||||
console.group('Navigation Dispatch: ');
|
||||
console.log('Action: ', action);
|
||||
console.log('New State: ', nav);
|
||||
console.log('Last State: ', prevNav);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log('Navigation Dispatch: ', {
|
||||
action,
|
||||
newState: nav,
|
||||
lastState: prevNav,
|
||||
});
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.props.onNavigationStateChange === 'function') {
|
||||
this.props.onNavigationStateChange(prevNav, nav, action);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Clear cached _navState every tick
|
||||
if (this._navState === this.state.nav) {
|
||||
this._navState = null;
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this._isMounted = true;
|
||||
if (!this._isStateful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__ && !this.props.detached) {
|
||||
if (_statefulContainerCount > 0) {
|
||||
// Temporarily only show this on iOS due to this issue:
|
||||
// https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
|
||||
if (Platform.OS === 'ios') {
|
||||
console.warn(
|
||||
`You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
|
||||
'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_statefulContainerCount++;
|
||||
Linking.addEventListener('url', this._handleOpenURL);
|
||||
|
||||
// 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);
|
||||
|
||||
// Initialize state. This must be done *after* any async code
|
||||
// so we don't end up with a different value for this.state.nav
|
||||
// due to changes while async function was resolving
|
||||
let action = this._initialAction;
|
||||
let startupState = this.state.nav;
|
||||
if (!startupState) {
|
||||
!!process.env.REACT_NAV_LOGGING &&
|
||||
console.log('Init new Navigation State');
|
||||
startupState = Component.router.getStateForAction(action);
|
||||
}
|
||||
|
||||
// Pull persisted state from AsyncStorage
|
||||
if (startupStateJSON) {
|
||||
try {
|
||||
startupState = JSON.parse(startupStateJSON);
|
||||
_reactNavigationIsHydratingState = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Pull state out of URL
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const urlAction = Component.router.getActionForPathAndParams(
|
||||
path,
|
||||
params
|
||||
);
|
||||
if (urlAction) {
|
||||
!!process.env.REACT_NAV_LOGGING &&
|
||||
console.log('Applying Navigation Action for Initial URL:', url);
|
||||
action = urlAction;
|
||||
startupState = Component.router.getStateForAction(
|
||||
urlAction,
|
||||
startupState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dispatchActions = () =>
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
state: this.state.nav,
|
||||
lastState: null,
|
||||
})
|
||||
);
|
||||
|
||||
if (startupState === this.state.nav) {
|
||||
dispatchActions();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ nav: startupState }, () => {
|
||||
_reactNavigationIsHydratingState = false;
|
||||
dispatchActions();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidCatch(e, errorInfo) {
|
||||
if (_reactNavigationIsHydratingState) {
|
||||
_reactNavigationIsHydratingState = false;
|
||||
console.warn(
|
||||
'Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state..'
|
||||
);
|
||||
this.dispatch(NavigationActions.init());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
_persistNavigationState = async nav => {
|
||||
const { persistenceKey } = this.props;
|
||||
if (!persistenceKey) {
|
||||
return;
|
||||
}
|
||||
await AsyncStorage.setItem(persistenceKey, JSON.stringify(nav));
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
Linking.removeEventListener('url', this._handleOpenURL);
|
||||
this.subs && this.subs.remove();
|
||||
|
||||
if (this._isStateful()) {
|
||||
_statefulContainerCount--;
|
||||
}
|
||||
}
|
||||
|
||||
// Per-tick temporary storage for state.nav
|
||||
|
||||
dispatch = action => {
|
||||
if (this.props.navigation) {
|
||||
return this.props.navigation.dispatch(action);
|
||||
}
|
||||
|
||||
// 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: navState,
|
||||
lastState: lastNavState,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
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._navState = navState;
|
||||
this.setState({ nav: navState }, () => {
|
||||
this._onNavigationStateChange(lastNavState, navState, action);
|
||||
dispatchActionEvents();
|
||||
this._persistNavigationState(navState);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
dispatchActionEvents();
|
||||
return false;
|
||||
};
|
||||
|
||||
_getScreenProps = () => this.props.screenProps;
|
||||
|
||||
render() {
|
||||
let navigation = this.props.navigation;
|
||||
if (this._isStateful()) {
|
||||
const navState = this.state.nav;
|
||||
if (!navState) {
|
||||
return this._renderLoading();
|
||||
}
|
||||
if (!this._navigation || this._navigation.state !== navState) {
|
||||
this._navigation = getNavigation(
|
||||
Component.router,
|
||||
navState,
|
||||
this.dispatch,
|
||||
this._actionEventSubscribers,
|
||||
this._getScreenProps,
|
||||
() => this._navigation
|
||||
);
|
||||
}
|
||||
navigation = this._navigation;
|
||||
}
|
||||
invariant(navigation, 'failed to get navigation');
|
||||
return <Component {...this.props} navigation={navigation} />;
|
||||
}
|
||||
}
|
||||
|
||||
return polyfill(NavigationContainer);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* This is used to extract one children's worth of events from a stream of navigation action events
|
||||
*
|
||||
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
||||
* focus and blur events for this child
|
||||
*/
|
||||
export default function getChildEventSubscriber(addListener, key) {
|
||||
const actionSubscribers = new Set();
|
||||
const willFocusSubscribers = new Set();
|
||||
const didFocusSubscribers = new Set();
|
||||
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':
|
||||
return actionSubscribers;
|
||||
case 'willFocus':
|
||||
return willFocusSubscribers;
|
||||
case 'didFocus':
|
||||
return didFocusSubscribers;
|
||||
case 'willBlur':
|
||||
return willBlurSubscribers;
|
||||
case 'didBlur':
|
||||
return didBlurSubscribers;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const emit = (type, payload) => {
|
||||
const payloadWithType = { ...payload, type };
|
||||
const subscribers = getChildSubscribers(type);
|
||||
subscribers &&
|
||||
subscribers.forEach(subs => {
|
||||
subs(payloadWithType);
|
||||
});
|
||||
};
|
||||
|
||||
// lastEmittedEvent keeps track of focus state for one route. First we assume
|
||||
// we are blurred. If we are focused on initialization, the first 'action'
|
||||
// event will cause onFocus+willFocus events because we had previously been
|
||||
// considered blurred
|
||||
let lastEmittedEvent = 'didBlur';
|
||||
|
||||
const upstreamEvents = [
|
||||
'willFocus',
|
||||
'didFocus',
|
||||
'willBlur',
|
||||
'didBlur',
|
||||
'action',
|
||||
];
|
||||
|
||||
const upstreamSubscribers = upstreamEvents.map(eventName =>
|
||||
addListener(eventName, payload => {
|
||||
const { state, lastState, action } = payload;
|
||||
const lastRoutes = lastState && lastState.routes;
|
||||
const routes = state && state.routes;
|
||||
|
||||
const lastFocusKey =
|
||||
lastState && lastState.routes && lastState.routes[lastState.index].key;
|
||||
const focusKey = routes && routes[state.index].key;
|
||||
|
||||
const isChildFocused = focusKey === key;
|
||||
const lastRoute =
|
||||
lastRoutes && lastRoutes.find(route => route.key === key);
|
||||
const newRoute = routes && routes.find(route => route.key === key);
|
||||
const childPayload = {
|
||||
context: `${key}:${action.type}_${payload.context || 'Root'}`,
|
||||
state: newRoute,
|
||||
lastState: lastRoute,
|
||||
action,
|
||||
type: eventName,
|
||||
};
|
||||
const isTransitioning = !!state && state.isTransitioning;
|
||||
|
||||
const previouslyLastEmittedEvent = lastEmittedEvent;
|
||||
|
||||
if (lastEmittedEvent === 'didBlur') {
|
||||
// The child is currently blurred. Look for willFocus conditions
|
||||
if (eventName === 'willFocus' && isChildFocused) {
|
||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
||||
} else if (eventName === 'action' && isChildFocused) {
|
||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
||||
}
|
||||
}
|
||||
if (lastEmittedEvent === 'willFocus') {
|
||||
// We are currently mid-focus. Look for didFocus conditions.
|
||||
// If state.isTransitioning is false, this child event happens immediately after willFocus
|
||||
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
|
||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
isChildFocused &&
|
||||
!isTransitioning
|
||||
) {
|
||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEmittedEvent === 'didFocus') {
|
||||
// The child is currently focused. Look for blurring events
|
||||
if (!isChildFocused) {
|
||||
// The child is no longer focused within this navigation state
|
||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
||||
} else if (eventName === 'willBlur') {
|
||||
// The parent is getting a willBlur event
|
||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
previouslyLastEmittedEvent === 'didFocus'
|
||||
) {
|
||||
// While focused, pass action events to children for grandchildren focus
|
||||
emit('action', childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEmittedEvent === 'willBlur') {
|
||||
// The child is mid-blur. Wait for transition to end
|
||||
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
|
||||
// The child is done blurring because transitioning is over, or isTransitioning
|
||||
// never began and didBlur fires immediately after willBlur
|
||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||
} else if (eventName === 'didBlur') {
|
||||
// Pass through the parent didBlur event if it happens
|
||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEmittedEvent === 'didBlur' && !newRoute) {
|
||||
removeAll();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
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 };
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import getChildEventSubscriber from './getChildEventSubscriber';
|
||||
import getChildRouter from './getChildRouter';
|
||||
import invariant from './utils/invariant';
|
||||
|
||||
const createParamGetter = route => (paramName, defaultValue) => {
|
||||
const params = route.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
const children =
|
||||
navigation._childrenNavigation || (navigation._childrenNavigation = {});
|
||||
|
||||
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
||||
|
||||
if (children[childKey] && children[childKey].state === childRoute) {
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
const childRouter = getChildRouter(navigation.router, childRoute.routeName);
|
||||
|
||||
// If the route has children, we'll use this to pass in to the action creators
|
||||
// for the childRouter so that any action that depends on the active route will
|
||||
// behave as expected. We don't explicitly require that routers implement routes
|
||||
// and index properties, but if we did then we would put an invariant here to
|
||||
// ensure that a focusedGrandChildRoute exists if childRouter is defined.
|
||||
const focusedGrandChildRoute =
|
||||
childRoute.routes && typeof childRoute.index === 'number'
|
||||
? childRoute.routes[childRoute.index]
|
||||
: null;
|
||||
|
||||
const actionCreators = {
|
||||
...navigation.actions,
|
||||
...navigation.router.getActionCreators(childRoute, navigation.state.key),
|
||||
...(childRouter
|
||||
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
|
||||
: {}),
|
||||
};
|
||||
const actionHelpers = {};
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
actionHelpers[actionName] = (...args) => {
|
||||
const actionCreator = actionCreators[actionName];
|
||||
const action = actionCreator(...args);
|
||||
return navigation.dispatch(action);
|
||||
};
|
||||
});
|
||||
|
||||
if (children[childKey]) {
|
||||
children[childKey] = {
|
||||
...children[childKey],
|
||||
...actionHelpers,
|
||||
state: childRoute,
|
||||
router: childRouter,
|
||||
actions: actionCreators,
|
||||
getParam: createParamGetter(childRoute),
|
||||
};
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
const childSubscriber = getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
childKey
|
||||
);
|
||||
|
||||
children[childKey] = {
|
||||
...actionHelpers,
|
||||
|
||||
state: childRoute,
|
||||
router: childRouter,
|
||||
actions: actionCreators,
|
||||
getParam: createParamGetter(childRoute),
|
||||
|
||||
getChildNavigation: grandChildKey =>
|
||||
getChildNavigation(children[childKey], grandChildKey, () =>
|
||||
getCurrentParentNavigation().getChildNavigation(childKey)
|
||||
),
|
||||
|
||||
isFocused: () => {
|
||||
const currentNavigation = getCurrentParentNavigation();
|
||||
const { routes, index } = currentNavigation.state;
|
||||
if (!currentNavigation.isFocused()) {
|
||||
return false;
|
||||
}
|
||||
if (routes[index].key === childKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
dispatch: navigation.dispatch,
|
||||
getScreenProps: navigation.getScreenProps,
|
||||
dangerouslyGetParent: getCurrentParentNavigation,
|
||||
addListener: childSubscriber.addListener,
|
||||
};
|
||||
return children[childKey];
|
||||
}
|
||||
|
||||
export default getChildNavigation;
|
||||
@@ -1,9 +0,0 @@
|
||||
export default function getChildRouter(router, routeName) {
|
||||
if (router.childRouters && router.childRouters[routeName]) {
|
||||
return router.childRouters[routeName];
|
||||
}
|
||||
|
||||
const Component = router.getComponentForRouteName(routeName);
|
||||
|
||||
return Component.router;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
||||
import getChildNavigation from './getChildNavigation';
|
||||
|
||||
export default function getNavigation(
|
||||
router,
|
||||
state,
|
||||
dispatch,
|
||||
actionSubscribers,
|
||||
getScreenProps,
|
||||
getCurrentNavigation
|
||||
) {
|
||||
const actions = router.getActionCreators(state, null);
|
||||
|
||||
const navigation = {
|
||||
actions,
|
||||
router,
|
||||
state,
|
||||
dispatch,
|
||||
getScreenProps,
|
||||
getChildNavigation: childKey =>
|
||||
getChildNavigation(navigation, childKey, getCurrentNavigation),
|
||||
isFocused: childKey => {
|
||||
const { routes, index } = getCurrentNavigation().state;
|
||||
if (childKey == null || routes[index].key === childKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
addListener: (eventName, handler) => {
|
||||
if (eventName !== 'action') {
|
||||
return { remove: () => {} };
|
||||
}
|
||||
actionSubscribers.add(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
actionSubscribers.delete(handler);
|
||||
},
|
||||
};
|
||||
},
|
||||
dangerouslyGetParent: () => null,
|
||||
};
|
||||
|
||||
const actionCreators = {
|
||||
...getNavigationActionCreators(navigation.state),
|
||||
...actions,
|
||||
};
|
||||
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
navigation[actionName] = (...args) =>
|
||||
navigation.dispatch(actionCreators[actionName](...args));
|
||||
});
|
||||
|
||||
return navigation;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import StackNavigator from '../createContainedStackNavigator';
|
||||
|
||||
const SubNavigator = StackNavigator({
|
||||
Home: {
|
||||
screen: () => null,
|
||||
},
|
||||
});
|
||||
|
||||
const NavNestedDirect = StackNavigator({
|
||||
Sub: {
|
||||
screen: SubNavigator,
|
||||
},
|
||||
});
|
||||
|
||||
const NavNestedIndirect = StackNavigator({
|
||||
Sub: {
|
||||
// eslint-disable-next-line react/display-name
|
||||
screen: props => <SubNavigator {...props} />,
|
||||
},
|
||||
});
|
||||
|
||||
/* Prevent React error boundaries from swallowing the errors */
|
||||
NavNestedIndirect.prototype.componentDidCatch = null;
|
||||
SubNavigator.prototype.componentDidCatch = null;
|
||||
|
||||
describe('Nested navigators', () => {
|
||||
it('renders succesfully as direct child', () => {
|
||||
const rendered = renderer.create(<NavNestedDirect />).toJSON();
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throw when trying to pass navigation prop', () => {
|
||||
const tryRender = () => {
|
||||
renderer.create(<NavNestedIndirect />);
|
||||
};
|
||||
expect(tryRender).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import StackNavigator from '../createContainedStackNavigator';
|
||||
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 }) => ({
|
||||
title: `Welcome ${
|
||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
||||
}`,
|
||||
gesturesEnabled: true,
|
||||
headerStyle: [{ backgroundColor: 'red' }, styles.header],
|
||||
});
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const routeConfig = {
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
},
|
||||
};
|
||||
|
||||
describe('StackNavigator', () => {
|
||||
beforeEach(() => {
|
||||
_TESTING_ONLY_reset_container_count();
|
||||
});
|
||||
|
||||
it('renders successfully', () => {
|
||||
const MyStackNavigator = StackNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('applies correct values when headerRight is present', () => {
|
||||
const MyStackNavigator = StackNavigator({
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
navigationOptions: {
|
||||
headerRight: <View />,
|
||||
},
|
||||
},
|
||||
});
|
||||
const rendered = renderer.create(<MyStackNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('passes navigation to headerRight when wrapped in withNavigation', () => {
|
||||
const spy = jest.fn();
|
||||
|
||||
class TestComponent extends React.Component {
|
||||
render() {
|
||||
return <View>{this.props.onPress(this.props.navigation)}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
const TestComponentWithNavigation = withNavigation(TestComponent);
|
||||
|
||||
class A extends React.Component {
|
||||
static navigationOptions = {
|
||||
headerRight: <TestComponentWithNavigation onPress={spy} />,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
const Nav = StackNavigator({ A: { screen: A } });
|
||||
|
||||
renderer.create(<Nav />);
|
||||
|
||||
expect(spy).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
navigate: expect.any(Function),
|
||||
addListener: expect.any(Function),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import SwitchNavigator from '../createContainedSwitchNavigator';
|
||||
import { createSwitchNavigator } from '@react-navigation/core';
|
||||
import { createAppContainer } from '@react-navigation/native';
|
||||
|
||||
const A = () => <View />;
|
||||
const B = () => <View />;
|
||||
@@ -10,8 +11,9 @@ const routeConfig = { A, B };
|
||||
|
||||
describe('SwitchNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MySwitchNavigator = SwitchNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MySwitchNavigator />).toJSON();
|
||||
const MySwitchNavigator = createSwitchNavigator(routeConfig);
|
||||
const App = createAppContainer(MySwitchNavigator);
|
||||
const rendered = renderer.create(<App />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
const {
|
||||
createTabNavigator,
|
||||
} = require('react-navigation-deprecated-tab-navigator');
|
||||
|
||||
class HomeScreen extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: `Welcome ${
|
||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
||||
}`,
|
||||
gesturesEnabled: true,
|
||||
});
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const routeConfig = {
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
},
|
||||
};
|
||||
|
||||
describe('TabNavigator', () => {
|
||||
it('renders successfully', () => {
|
||||
const MyTabNavigator = createTabNavigator(routeConfig);
|
||||
const rendered = renderer.create(<MyTabNavigator />).toJSON();
|
||||
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,369 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"marginTop": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"marginTop": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#F7F7F7",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#F7F7F7",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`Nested navigators throw when trying to pass navigation prop 1`] = `"No \\"routes\\" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop"`;
|
||||
@@ -1,393 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StackNavigator applies correct values when headerRight is present 1`] = `
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"marginTop": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 70,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 70,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`StackNavigator renders successfully 1`] = `
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column-reverse",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="auto"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E9E9EF",
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"marginTop": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"shadowColor": "black",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 0.2,
|
||||
"shadowRadius": 5,
|
||||
"top": 0,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
Object {
|
||||
"translateY": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateX": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "red",
|
||||
"borderBottomColor": "#A7A7AA",
|
||||
"borderBottomWidth": 0.5,
|
||||
"height": 64,
|
||||
"opacity": 0.5,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "transparent",
|
||||
"bottom": 0,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityTraits="header"
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, .9)",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"marginHorizontal": 16,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -1,209 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TabNavigator renders successfully 1`] = `
|
||||
<View
|
||||
collapsable={false}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"overflow": "hidden",
|
||||
},
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "stretch",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
collapsable={false}
|
||||
removeClippedSubviews={false}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={undefined}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#F7F7F7",
|
||||
"borderTopColor": "rgba(0, 0, 0, .3)",
|
||||
"borderTopWidth": 0.5,
|
||||
"flexDirection": "row",
|
||||
"height": 49,
|
||||
"paddingBottom": 0,
|
||||
"paddingLeft": 0,
|
||||
"paddingRight": 0,
|
||||
"paddingTop": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessibilityComponentType={undefined}
|
||||
accessibilityLabel={undefined}
|
||||
accessibilityTraits={undefined}
|
||||
accessible={true}
|
||||
collapsable={undefined}
|
||||
hitSlop={undefined}
|
||||
nativeID={undefined}
|
||||
onLayout={undefined}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
},
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "flex-end",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 29,
|
||||
},
|
||||
false,
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"alignSelf": "center",
|
||||
"height": "100%",
|
||||
"justifyContent": "center",
|
||||
"minWidth": 30,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"alignSelf": "center",
|
||||
"height": "100%",
|
||||
"justifyContent": "center",
|
||||
"minWidth": 30,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "rgba(52, 120, 246, 1)",
|
||||
"fontSize": 10,
|
||||
"marginBottom": 1.5,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -1,9 +0,0 @@
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import createStackNavigator from './createStackNavigator';
|
||||
|
||||
const StackNavigator = (routeConfigs, config = {}) => {
|
||||
const navigator = createStackNavigator(routeConfigs, config);
|
||||
return createNavigationContainer(navigator);
|
||||
};
|
||||
|
||||
export default StackNavigator;
|
||||
@@ -1,9 +0,0 @@
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import createSwitchNavigator from './createSwitchNavigator';
|
||||
|
||||
const SwitchNavigator = (routeConfigs, config = {}) => {
|
||||
const navigator = createSwitchNavigator(routeConfigs, config);
|
||||
return createNavigationContainer(navigator);
|
||||
};
|
||||
|
||||
export default SwitchNavigator;
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
|
||||
export default Navigator =>
|
||||
class KeyboardAwareNavigator extends React.Component {
|
||||
static router = Navigator.router;
|
||||
_previouslyFocusedTextInput = null;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Navigator
|
||||
{...this.props}
|
||||
onGestureBegin={this._handleGestureBegin}
|
||||
onGestureCanceled={this._handleGestureCanceled}
|
||||
onGestureFinish={this._handleGestureFinish}
|
||||
onTransitionStart={this._handleTransitionStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_handleGestureBegin = () => {
|
||||
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
|
||||
if (this._previouslyFocusedTextInput) {
|
||||
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
|
||||
}
|
||||
this.props.onGestureBegin && this.props.onGestureBegin();
|
||||
};
|
||||
|
||||
_handleGestureCanceled = () => {
|
||||
if (this._previouslyFocusedTextInput) {
|
||||
TextInput.State.focusTextInput(this._previouslyFocusedTextInput);
|
||||
}
|
||||
this.props.onGestureCanceled && this.props.onGestureCanceled();
|
||||
};
|
||||
|
||||
_handleGestureFinish = () => {
|
||||
this._previouslyFocusedTextInput = null;
|
||||
this.props.onGestureFinish && this.props.onGestureFinish();
|
||||
};
|
||||
|
||||
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
|
||||
// TODO: We should not even have received the transition start event
|
||||
// in the case where the index did not change, I believe. We
|
||||
// should revisit this after 2.0 release.
|
||||
if (transitionProps.index !== prevTransitionProps.index) {
|
||||
const currentField = TextInput.State.currentlyFocusedField();
|
||||
if (currentField) {
|
||||
TextInput.State.blurTextInput(currentField);
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onTransitionStart &&
|
||||
this.props.onTransitionStart(transitionProps, prevTransitionProps);
|
||||
};
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
|
||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||
|
||||
function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
class Navigator extends React.Component {
|
||||
static router = router;
|
||||
static navigationOptions = null;
|
||||
|
||||
state = {
|
||||
descriptors: {},
|
||||
screenProps: this.props.screenProps,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const prevDescriptors = prevState.descriptors;
|
||||
const { navigation, screenProps } = nextProps;
|
||||
const { dispatch, state, addListener } = navigation;
|
||||
const { routes } = state;
|
||||
if (typeof routes === 'undefined') {
|
||||
throw new TypeError(
|
||||
'No "routes" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop'
|
||||
);
|
||||
}
|
||||
|
||||
const descriptors = { ...prevState.descriptors };
|
||||
|
||||
routes.forEach(route => {
|
||||
if (
|
||||
prevDescriptors &&
|
||||
prevDescriptors[route.key] &&
|
||||
route === prevDescriptors[route.key].state &&
|
||||
screenProps === prevState.screenProps
|
||||
) {
|
||||
descriptors[route.key] = prevDescriptors[route.key];
|
||||
return;
|
||||
}
|
||||
const getComponent = () =>
|
||||
router.getComponentForRouteName(route.routeName);
|
||||
const childNavigation = navigation.getChildNavigation(route.key);
|
||||
const options = router.getScreenOptions(childNavigation, screenProps);
|
||||
descriptors[route.key] = {
|
||||
key: route.key,
|
||||
getComponent,
|
||||
options,
|
||||
state: route,
|
||||
navigation: childNavigation,
|
||||
};
|
||||
});
|
||||
|
||||
return { descriptors, screenProps };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigatorView
|
||||
{...this.props}
|
||||
screenProps={this.state.screenProps}
|
||||
navigation={this.props.navigation}
|
||||
navigationConfig={navigationConfig}
|
||||
descriptors={this.state.descriptors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return polyfill(Navigator);
|
||||
}
|
||||
|
||||
export default createNavigator;
|
||||
@@ -1,37 +0,0 @@
|
||||
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,
|
||||
getCustomActionCreators,
|
||||
} = stackConfig;
|
||||
|
||||
const stackRouterConfig = {
|
||||
initialRouteKey,
|
||||
initialRouteName,
|
||||
initialRouteParams,
|
||||
paths,
|
||||
navigationOptions,
|
||||
getCustomActionCreators,
|
||||
};
|
||||
|
||||
const router = StackRouter(routeConfigMap, stackRouterConfig);
|
||||
|
||||
// Create a navigator with StackView as the view
|
||||
let Navigator = createNavigator(StackView, router, stackConfig);
|
||||
if (!disableKeyboardHandling) {
|
||||
Navigator = createKeyboardAwareNavigator(Navigator);
|
||||
}
|
||||
|
||||
return Navigator;
|
||||
}
|
||||
|
||||
export default createStackNavigator;
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import createNavigator from '../navigators/createNavigator';
|
||||
import SwitchRouter from '../routers/SwitchRouter';
|
||||
import SwitchView from '../views/SwitchView/SwitchView';
|
||||
|
||||
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
||||
const router = SwitchRouter(routeConfigMap, switchConfig);
|
||||
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
||||
return Navigator;
|
||||
}
|
||||
|
||||
export default createSwitchNavigator;
|
||||
233
src/react-navigation.js
vendored
233
src/react-navigation.js
vendored
@@ -1,62 +1,115 @@
|
||||
/* eslint global-require: 0 */
|
||||
|
||||
module.exports = {
|
||||
// Core
|
||||
// Native
|
||||
get createAppContainer() {
|
||||
return require('@react-navigation/native').createAppContainer;
|
||||
},
|
||||
get createNavigationContainer() {
|
||||
return require('./createNavigationContainer').default;
|
||||
console.warn(
|
||||
'`createNavigationContainer()` has been deprecated, please use `createAppContainer()` instead. You can also import createAppContainer directly from @react-navigation/native'
|
||||
);
|
||||
return require('@react-navigation/native').createAppContainer;
|
||||
},
|
||||
get createKeyboardAwareNavigator() {
|
||||
return require('@react-navigation/native').createKeyboardAwareNavigator;
|
||||
},
|
||||
get createNavigationAwareScrollable() {
|
||||
return require('@react-navigation/native').createNavigationAwareScrollable;
|
||||
},
|
||||
get ScrollView() {
|
||||
return require('@react-navigation/native').ScrollView;
|
||||
},
|
||||
get FlatList() {
|
||||
return require('@react-navigation/native').FlatList;
|
||||
},
|
||||
get SectionList() {
|
||||
return require('@react-navigation/native').SectionList;
|
||||
},
|
||||
get ResourceSavingSceneView() {
|
||||
return require('@react-navigation/native').ResourceSavingSceneView;
|
||||
},
|
||||
get SafeAreaView() {
|
||||
return require('@react-navigation/native').SafeAreaView;
|
||||
},
|
||||
get withOrientation() {
|
||||
return require('@react-navigation/native').withOrientation;
|
||||
},
|
||||
|
||||
// Core
|
||||
get createNavigator() {
|
||||
return require('@react-navigation/core').createNavigator;
|
||||
},
|
||||
get StateUtils() {
|
||||
return require('./StateUtils').default;
|
||||
return require('@react-navigation/core').StateUtils;
|
||||
},
|
||||
get getNavigation() {
|
||||
return require('./getNavigation').default;
|
||||
return require('@react-navigation/core').getNavigation;
|
||||
},
|
||||
get NavigationContext() {
|
||||
return require('@react-navigation/core').NavigationContext;
|
||||
},
|
||||
get NavigationProvider() {
|
||||
return require('@react-navigation/core').NavigationProvider;
|
||||
},
|
||||
get NavigationConsumer() {
|
||||
return require('@react-navigation/core').NavigationConsumer;
|
||||
},
|
||||
get NavigationActions() {
|
||||
return require('@react-navigation/core').NavigationActions;
|
||||
},
|
||||
get StackActions() {
|
||||
return require('@react-navigation/core').StackActions;
|
||||
},
|
||||
get StackRouter() {
|
||||
return require('@react-navigation/core').StackRouter;
|
||||
},
|
||||
get TabRouter() {
|
||||
return require('@react-navigation/core').TabRouter;
|
||||
},
|
||||
get SwitchRouter() {
|
||||
return require('@react-navigation/core').SwitchRouter;
|
||||
},
|
||||
get createConfigGetter() {
|
||||
return require('@react-navigation/core').StackAcreateConfigGetterctions;
|
||||
},
|
||||
get getScreenForRouteName() {
|
||||
return require('@react-navigation/core').getScreenForRouteName;
|
||||
},
|
||||
get validateRouteConfigMap() {
|
||||
return require('@react-navigation/core').validateRouteConfigMap;
|
||||
},
|
||||
get getActiveChildNavigationOptions() {
|
||||
return require('@react-navigation/core').getActiveChildNavigationOptions;
|
||||
},
|
||||
get pathUtils() {
|
||||
return require('@react-navigation/core').pathUtils;
|
||||
},
|
||||
get SceneView() {
|
||||
return require('@react-navigation/core').SceneView;
|
||||
},
|
||||
get SwitchView() {
|
||||
return require('@react-navigation/core').SwitchView;
|
||||
},
|
||||
get NavigationEvents() {
|
||||
return require('@react-navigation/core').NavigationEvents;
|
||||
},
|
||||
get withNavigation() {
|
||||
return require('@react-navigation/core').withNavigation;
|
||||
},
|
||||
get withNavigationFocus() {
|
||||
return require('@react-navigation/core').withNavigationFocus;
|
||||
},
|
||||
|
||||
// Navigators
|
||||
get createNavigator() {
|
||||
return require('./navigators/createNavigator').default;
|
||||
},
|
||||
|
||||
get createStackNavigator() {
|
||||
return require('./navigators/createContainedStackNavigator').default;
|
||||
},
|
||||
get StackNavigator() {
|
||||
console.warn(
|
||||
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
|
||||
);
|
||||
return require('./navigators/createContainedStackNavigator').default;
|
||||
return require('react-navigation-stack').createStackNavigator;
|
||||
},
|
||||
get createSwitchNavigator() {
|
||||
return require('./navigators/createContainedSwitchNavigator').default;
|
||||
},
|
||||
get SwitchNavigator() {
|
||||
console.warn(
|
||||
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
|
||||
);
|
||||
return require('./navigators/createContainedSwitchNavigator').default;
|
||||
},
|
||||
get createDrawerNavigator() {
|
||||
return require('react-navigation-drawer').createDrawerNavigator;
|
||||
},
|
||||
get DrawerNavigator() {
|
||||
console.warn(
|
||||
'The DrawerNavigator function name is deprecated, please use createDrawerNavigator instead'
|
||||
);
|
||||
return require('react-navigation-drawer').createDrawerNavigator;
|
||||
},
|
||||
get createTabNavigator() {
|
||||
console.warn(
|
||||
'createTabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator')
|
||||
.createTabNavigator;
|
||||
},
|
||||
get TabNavigator() {
|
||||
console.warn(
|
||||
'TabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
|
||||
);
|
||||
return require('react-navigation-deprecated-tab-navigator')
|
||||
.createTabNavigator;
|
||||
return require('@react-navigation/core').createSwitchNavigator;
|
||||
},
|
||||
|
||||
get createBottomTabNavigator() {
|
||||
return require('react-navigation-tabs').createBottomTabNavigator;
|
||||
},
|
||||
@@ -64,60 +117,48 @@ module.exports = {
|
||||
return require('react-navigation-tabs').createMaterialTopTabNavigator;
|
||||
},
|
||||
|
||||
// Actions
|
||||
get NavigationActions() {
|
||||
return require('./NavigationActions').default;
|
||||
get createDrawerNavigator() {
|
||||
return require('react-navigation-drawer').createDrawerNavigator;
|
||||
},
|
||||
get StackActions() {
|
||||
return require('./routers/StackActions').default;
|
||||
|
||||
// Routers and Actions
|
||||
|
||||
get DrawerRouter() {
|
||||
return require('react-navigation-drawer').DrawerRouter;
|
||||
},
|
||||
get DrawerActions() {
|
||||
return require('react-navigation-drawer').DrawerActions;
|
||||
},
|
||||
|
||||
// Routers
|
||||
get StackRouter() {
|
||||
return require('./routers/StackRouter').default;
|
||||
},
|
||||
get TabRouter() {
|
||||
return require('./routers/TabRouter').default;
|
||||
},
|
||||
get DrawerRouter() {
|
||||
return require('react-navigation-drawer').DrawerRouter;
|
||||
},
|
||||
get SwitchRouter() {
|
||||
return require('./routers/SwitchRouter').default;
|
||||
},
|
||||
|
||||
// Views
|
||||
get Transitioner() {
|
||||
return require('./views/Transitioner').default;
|
||||
console.warn(
|
||||
'Importing the stack Transitioner directly from react-navigation is now deprecated. Instead, import { Transitioner } from "react-navigation-stack";'
|
||||
);
|
||||
return require('react-navigation-stack').Transitioner;
|
||||
},
|
||||
get StackView() {
|
||||
return require('./views/StackView/StackView').default;
|
||||
return require('react-navigation-stack').StackView;
|
||||
},
|
||||
get StackViewCard() {
|
||||
return require('./views/StackView/StackViewCard').default;
|
||||
return require('react-navigation-stack').StackViewCard;
|
||||
},
|
||||
get SafeAreaView() {
|
||||
return require('react-native-safe-area-view').default;
|
||||
},
|
||||
get SceneView() {
|
||||
return require('./views/SceneView').default;
|
||||
},
|
||||
get ResourceSavingSceneView() {
|
||||
return require('./views/ResourceSavingSceneView').default;
|
||||
get StackViewTransitionConfigs() {
|
||||
return require('react-navigation-stack').StackViewTransitionConfigs;
|
||||
},
|
||||
|
||||
// Header
|
||||
get Header() {
|
||||
return require('./views/Header/Header').default;
|
||||
return require('react-navigation-stack').Header;
|
||||
},
|
||||
get HeaderTitle() {
|
||||
return require('./views/Header/HeaderTitle').default;
|
||||
return require('react-navigation-stack').HeaderTitle;
|
||||
},
|
||||
get HeaderBackButton() {
|
||||
return require('./views/Header/HeaderBackButton').default;
|
||||
return require('react-navigation-stack').HeaderBackButton;
|
||||
},
|
||||
get HeaderStyleInterpolator() {
|
||||
return require('react-navigation-stack').HeaderStyleInterpolator;
|
||||
},
|
||||
|
||||
// DrawerView
|
||||
@@ -131,39 +172,11 @@ module.exports = {
|
||||
return require('react-navigation-drawer').DrawerSidebar;
|
||||
},
|
||||
|
||||
// TabView
|
||||
get TabView() {
|
||||
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;
|
||||
// Tabs
|
||||
get BottomTabBar() {
|
||||
return require('react-navigation-tabs').BottomTabBar;
|
||||
},
|
||||
get TabBarTop() {
|
||||
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() {
|
||||
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;
|
||||
get MaterialTopTabBar() {
|
||||
return require('react-navigation-tabs').MaterialTopTabBar;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/* eslint global-require: 0 */
|
||||
|
||||
module.exports = {
|
||||
// Core
|
||||
get createNavigationContainer() {
|
||||
return require('./createNavigationContainer').default;
|
||||
},
|
||||
get StateUtils() {
|
||||
return require('./StateUtils').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;
|
||||
},
|
||||
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;
|
||||
},
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
let uniqueBaseId = `id-${Date.now()}`;
|
||||
let uuidCount = 0;
|
||||
|
||||
export function _TESTING_ONLY_normalize_keys() {
|
||||
uniqueBaseId = 'id';
|
||||
uuidCount = 0;
|
||||
}
|
||||
|
||||
export function generateKey() {
|
||||
return `${uniqueBaseId}-${uuidCount++}`;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
const POP = 'Navigation/POP';
|
||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
||||
const PUSH = 'Navigation/PUSH';
|
||||
const RESET = 'Navigation/RESET';
|
||||
const REPLACE = 'Navigation/REPLACE';
|
||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
|
||||
const pop = payload => ({
|
||||
type: POP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const popToTop = payload => ({
|
||||
type: POP_TO_TOP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const push = payload => ({
|
||||
type: PUSH,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const reset = payload => ({
|
||||
type: RESET,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const replace = payload => ({
|
||||
type: REPLACE,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const completeTransition = payload => ({
|
||||
type: COMPLETE_TRANSITION,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export default {
|
||||
POP,
|
||||
POP_TO_TOP,
|
||||
PUSH,
|
||||
RESET,
|
||||
REPLACE,
|
||||
COMPLETE_TRANSITION,
|
||||
|
||||
pop,
|
||||
popToTop,
|
||||
push,
|
||||
reset,
|
||||
replace,
|
||||
completeTransition,
|
||||
};
|
||||
@@ -1,678 +0,0 @@
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import StackActions from './StackActions';
|
||||
import createConfigGetter from './createConfigGetter';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import StateUtils from '../StateUtils';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import invariant from '../utils/invariant';
|
||||
import { generateKey } from './KeyGenerator';
|
||||
import getNavigationActionCreators from './getNavigationActionCreators';
|
||||
|
||||
function isEmpty(obj) {
|
||||
if (!obj) return true;
|
||||
for (let key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function behavesLikePushAction(action) {
|
||||
return (
|
||||
action.type === NavigationActions.NAVIGATE ||
|
||||
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);
|
||||
|
||||
const childRouters = {};
|
||||
const routeNames = Object.keys(routeConfigs);
|
||||
|
||||
// Loop through routes and find child routers
|
||||
routeNames.forEach(routeName => {
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
if (screen && screen.router) {
|
||||
// If it has a router it's a navigator.
|
||||
childRouters[routeName] = screen.router;
|
||||
} else {
|
||||
// If it doesn't have router it's an ordinary React component.
|
||||
childRouters[routeName] = null;
|
||||
}
|
||||
});
|
||||
|
||||
const { initialRouteParams } = stackConfig;
|
||||
const getCustomActionCreators =
|
||||
stackConfig.getCustomActionCreators || defaultActionCreators;
|
||||
|
||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
||||
|
||||
const initialChildRouter = childRouters[initialRouteName];
|
||||
const pathsByRouteNames = { ...stackConfig.paths } || {};
|
||||
let paths = [];
|
||||
|
||||
function getInitialState(action) {
|
||||
let route = {};
|
||||
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 =
|
||||
pathsByRouteNames[routeName] || routeConfigs[routeName].path;
|
||||
let matchExact = !!pathPattern && !childRouters[routeName];
|
||||
if (pathPattern === undefined) {
|
||||
pathPattern = routeName;
|
||||
}
|
||||
const keys = [];
|
||||
let re, toPath, priority;
|
||||
if (typeof pathPattern === 'string') {
|
||||
// pathPattern may be either a string or a regexp object according to path-to-regexp docs.
|
||||
re = pathToRegexp(pathPattern, keys);
|
||||
toPath = pathToRegexp.compile(pathPattern);
|
||||
priority = 0;
|
||||
} else {
|
||||
// for wildcard match
|
||||
re = pathToRegexp('*', keys);
|
||||
toPath = () => '';
|
||||
matchExact = true;
|
||||
priority = -1;
|
||||
}
|
||||
if (!matchExact) {
|
||||
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
|
||||
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
|
||||
}
|
||||
pathsByRouteNames[routeName] = { re, keys, toPath, priority };
|
||||
});
|
||||
|
||||
paths = Object.entries(pathsByRouteNames);
|
||||
paths.sort((a, b) => b[1].priority - a[1].priority);
|
||||
|
||||
return {
|
||||
childRouters,
|
||||
|
||||
getComponentForState(state) {
|
||||
const activeChildRoute = state.routes[state.index];
|
||||
const { routeName } = activeChildRoute;
|
||||
if (childRouters[routeName]) {
|
||||
return childRouters[routeName].getComponentForState(activeChildRoute);
|
||||
}
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getComponentForRouteName(routeName) {
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getActionCreators(route, navStateKey) {
|
||||
return {
|
||||
...getNavigationActionCreators(route),
|
||||
...getCustomActionCreators(route, navStateKey),
|
||||
pop: (n, params) =>
|
||||
StackActions.pop({
|
||||
n,
|
||||
...params,
|
||||
}),
|
||||
popToTop: params => StackActions.popToTop(params),
|
||||
push: (routeName, params, action) =>
|
||||
StackActions.push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
}),
|
||||
replace: (replaceWith, params, action, newKey) => {
|
||||
if (typeof replaceWith === 'string') {
|
||||
return StackActions.replace({
|
||||
routeName: replaceWith,
|
||||
params,
|
||||
action,
|
||||
key: route.key,
|
||||
newKey,
|
||||
});
|
||||
}
|
||||
invariant(
|
||||
typeof replaceWith === 'object',
|
||||
'Must replaceWith an object or a string'
|
||||
);
|
||||
invariant(
|
||||
params == null,
|
||||
'Params must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
action == null,
|
||||
'Child action must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
invariant(
|
||||
newKey == null,
|
||||
'Child action must not be provided to .replace() when specifying an object'
|
||||
);
|
||||
return StackActions.replace(replaceWith);
|
||||
},
|
||||
reset: (actions, index) =>
|
||||
StackActions.reset({
|
||||
actions,
|
||||
index: index == null ? actions.length - 1 : index,
|
||||
key: navStateKey,
|
||||
}),
|
||||
dismiss: () =>
|
||||
NavigationActions.back({
|
||||
key: navStateKey,
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
getStateForAction(action, state) {
|
||||
// Set up the initial state if needed
|
||||
if (!state) {
|
||||
return getInitialState(action);
|
||||
}
|
||||
|
||||
// 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;
|
||||
const childIndex = keyIndex >= 0 ? keyIndex : state.index;
|
||||
const childRoute = state.routes[childIndex];
|
||||
invariant(
|
||||
childRoute,
|
||||
`StateUtils erroneously thought index ${childIndex} exists`
|
||||
);
|
||||
const childRouter = childRouters[childRoute.routeName];
|
||||
if (childRouter) {
|
||||
const route = childRouter.getStateForAction(action, childRoute);
|
||||
if (route === null) {
|
||||
return state;
|
||||
}
|
||||
if (route && route !== childRoute) {
|
||||
return StateUtils.replaceAt(state, childRoute.key, route);
|
||||
}
|
||||
}
|
||||
} else if (action.type === NavigationActions.NAVIGATE) {
|
||||
// Traverse routes from the top of the stack to the bottom, so the
|
||||
// active route has the first opportunity, then the one before it, etc.
|
||||
for (let childRoute of state.routes.slice().reverse()) {
|
||||
let childRouter = childRouters[childRoute.routeName];
|
||||
let childAction =
|
||||
action.routeName === childRoute.routeName && action.action
|
||||
? action.action
|
||||
: action;
|
||||
|
||||
if (childRouter) {
|
||||
const nextRouteState = childRouter.getStateForAction(
|
||||
childAction,
|
||||
childRoute
|
||||
);
|
||||
|
||||
if (nextRouteState === null || nextRouteState !== childRoute) {
|
||||
const newState = StateUtils.replaceAndPrune(
|
||||
state,
|
||||
nextRouteState ? nextRouteState.key : childRoute.key,
|
||||
nextRouteState ? nextRouteState : childRoute
|
||||
);
|
||||
return {
|
||||
...newState,
|
||||
isTransitioning:
|
||||
state.index !== newState.index
|
||||
? action.immediate !== true
|
||||
: state.isTransitioning,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
) {
|
||||
const childRouter = childRouters[action.routeName];
|
||||
let route;
|
||||
|
||||
invariant(
|
||||
action.type !== StackActions.PUSH || action.key == null,
|
||||
'StackRouter does not support key on the push action'
|
||||
);
|
||||
|
||||
// Before pushing a new route we first try to find one in the existing route stack
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
|
||||
if (childRouter) {
|
||||
const childAction =
|
||||
action.action || NavigationActions.init({ params: action.params });
|
||||
route = {
|
||||
params: action.params,
|
||||
// merge the child state in this order to allow params override
|
||||
...childRouter.getStateForAction(childAction),
|
||||
routeName: action.routeName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
} else {
|
||||
route = {
|
||||
params: action.params,
|
||||
routeName: action.routeName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...StateUtils.push(state, route),
|
||||
isTransitioning: action.immediate !== true,
|
||||
};
|
||||
} else if (
|
||||
action.type === StackActions.PUSH &&
|
||||
childRouters[action.routeName] === undefined
|
||||
) {
|
||||
// Return the state identity to bubble the action up
|
||||
return state;
|
||||
}
|
||||
|
||||
// Handle navigation to other child routers that are not yet pushed
|
||||
if (behavesLikePushAction(action)) {
|
||||
const childRouterNames = Object.keys(childRouters);
|
||||
for (let i = 0; i < childRouterNames.length; i++) {
|
||||
const childRouterName = childRouterNames[i];
|
||||
const childRouter = childRouters[childRouterName];
|
||||
if (childRouter) {
|
||||
// For each child router, start with a blank state
|
||||
const initChildRoute = childRouter.getStateForAction(
|
||||
NavigationActions.init()
|
||||
);
|
||||
// Then check to see if the router handles our navigate action
|
||||
const navigatedChildRoute = childRouter.getStateForAction(
|
||||
action,
|
||||
initChildRoute
|
||||
);
|
||||
let routeToPush = null;
|
||||
if (navigatedChildRoute === null) {
|
||||
// Push the route if the router has 'handled' the action and returned null
|
||||
routeToPush = initChildRoute;
|
||||
} else if (navigatedChildRoute !== initChildRoute) {
|
||||
// Push the route if the state has changed in response to this navigation
|
||||
routeToPush = navigatedChildRoute;
|
||||
}
|
||||
if (routeToPush) {
|
||||
const route = {
|
||||
...routeToPush,
|
||||
routeName: childRouterName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
return {
|
||||
...StateUtils.push(state, route),
|
||||
isTransitioning: action.immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
||||
if (action.type === 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);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.params,
|
||||
...action.params,
|
||||
};
|
||||
const routes = [...state.routes];
|
||||
routes[state.routes.indexOf(lastRoute)] = {
|
||||
...lastRoute,
|
||||
params,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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 newStackActions = action.actions;
|
||||
|
||||
return {
|
||||
...state,
|
||||
routes: newStackActions.map(newStackAction => {
|
||||
const router = childRouters[newStackAction.routeName];
|
||||
|
||||
let childState = {};
|
||||
|
||||
if (router) {
|
||||
const childAction =
|
||||
newStackAction.action ||
|
||||
NavigationActions.init({ params: newStackAction.params });
|
||||
|
||||
childState = router.getStateForAction(childAction);
|
||||
}
|
||||
|
||||
return {
|
||||
params: newStackAction.params,
|
||||
...childState,
|
||||
routeName: newStackAction.routeName,
|
||||
key: newStackAction.key || generateKey(),
|
||||
};
|
||||
}),
|
||||
index: action.index,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
action.type === NavigationActions.BACK ||
|
||||
action.type === StackActions.POP
|
||||
) {
|
||||
const { key, n, immediate } = action;
|
||||
let backRouteIndex = state.index;
|
||||
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);
|
||||
} else if (key) {
|
||||
const backRoute = state.routes.find(route => route.key === key);
|
||||
backRouteIndex = state.routes.indexOf(backRoute);
|
||||
}
|
||||
|
||||
if (backRouteIndex > 0) {
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.slice(0, backRouteIndex),
|
||||
index: backRouteIndex - 1,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
getPathAndParamsForState(state) {
|
||||
const route = state.routes[state.index];
|
||||
const routeName = route.routeName;
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
const subPath = pathsByRouteNames[routeName].toPath(route.params);
|
||||
let path = subPath;
|
||||
let params = route.params;
|
||||
if (screen && screen.router) {
|
||||
const stateRoute = route;
|
||||
// If it has a router it's a navigator.
|
||||
// If it doesn't have router it's an ordinary React component.
|
||||
const child = screen.router.getPathAndParamsForState(stateRoute);
|
||||
path = subPath ? `${subPath}/${child.path}` : child.path;
|
||||
params = child.params ? { ...params, ...child.params } : params;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
};
|
||||
},
|
||||
|
||||
getActionForPathAndParams(pathToResolve, inputParams) {
|
||||
// If the path is empty (null or empty string)
|
||||
// just return the initial route action
|
||||
if (!pathToResolve) {
|
||||
return NavigationActions.navigate({
|
||||
routeName: initialRouteName,
|
||||
params: inputParams,
|
||||
});
|
||||
}
|
||||
|
||||
const [pathNameToResolve, queryString] = pathToResolve.split('?');
|
||||
|
||||
// Attempt to match `pathNameToResolve` with a route in this router's
|
||||
// routeConfigs
|
||||
let matchedRouteName;
|
||||
let pathMatch;
|
||||
let pathMatchKeys;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [routeName, path] of paths) {
|
||||
const { re, keys } = path;
|
||||
pathMatch = re.exec(pathNameToResolve);
|
||||
if (pathMatch && pathMatch.length) {
|
||||
pathMatchKeys = keys;
|
||||
matchedRouteName = routeName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't match -- return null
|
||||
if (!matchedRouteName) {
|
||||
// If the path is empty (null or empty string)
|
||||
// just return the initial route action
|
||||
if (!pathToResolve) {
|
||||
return NavigationActions.navigate({
|
||||
routeName: initialRouteName,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine nested actions:
|
||||
// If our matched route for this router is a child router,
|
||||
// get the action for the path AFTER the matched path for this
|
||||
// router
|
||||
let nestedAction;
|
||||
let nestedQueryString = queryString ? '?' + queryString : '';
|
||||
if (childRouters[matchedRouteName]) {
|
||||
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
|
||||
pathMatch.slice(pathMatchKeys.length).join('/') + nestedQueryString
|
||||
);
|
||||
if (!nestedAction) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// reduce the items of the query string. any query params may
|
||||
// be overridden by path params
|
||||
const queryParams = !isEmpty(inputParams)
|
||||
? inputParams
|
||||
: (queryString || '').split('&').reduce((result, item) => {
|
||||
if (item !== '') {
|
||||
const nextResult = result || {};
|
||||
const [key, value] = item.split('=');
|
||||
nextResult[key] = value;
|
||||
return nextResult;
|
||||
}
|
||||
return result;
|
||||
}, null);
|
||||
|
||||
// reduce the matched pieces of the path into the params
|
||||
// of the route. `params` is null if there are no params.
|
||||
const params = pathMatch.slice(1).reduce((result, matchResult, i) => {
|
||||
const key = pathMatchKeys[i];
|
||||
if (key.asterisk || !key) {
|
||||
return result;
|
||||
}
|
||||
const nextResult = result || inputParams || {};
|
||||
const paramName = key.name;
|
||||
|
||||
let decodedMatchResult;
|
||||
try {
|
||||
decodedMatchResult = decodeURIComponent(matchResult);
|
||||
} catch (e) {
|
||||
// ignore `URIError: malformed URI`
|
||||
}
|
||||
|
||||
nextResult[paramName] = decodedMatchResult || matchResult;
|
||||
return nextResult;
|
||||
}, queryParams);
|
||||
|
||||
return NavigationActions.navigate({
|
||||
routeName: matchedRouteName,
|
||||
...(params ? { params } : {}),
|
||||
...(nestedAction ? { action: nestedAction } : {}),
|
||||
});
|
||||
},
|
||||
|
||||
getScreenOptions: createConfigGetter(
|
||||
routeConfigs,
|
||||
stackConfig.navigationOptions
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -1,390 +0,0 @@
|
||||
import invariant from '../utils/invariant';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import createConfigGetter from './createConfigGetter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import StackActions from './StackActions';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import getNavigationActionCreators from './getNavigationActionCreators';
|
||||
|
||||
const defaultActionCreators = (route, navStateKey) => ({});
|
||||
|
||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||
return [
|
||||
NavigationActions.SET_PARAMS,
|
||||
// Todo: make SwitchRouter not depend on StackActions..
|
||||
StackActions.COMPLETE_TRANSITION,
|
||||
].includes(actionType);
|
||||
}
|
||||
|
||||
export default (routeConfigs, config = {}) => {
|
||||
// Fail fast on invalid route definitions
|
||||
validateRouteConfigMap(routeConfigs);
|
||||
|
||||
const order = config.order || Object.keys(routeConfigs);
|
||||
const paths = config.paths || {};
|
||||
const getCustomActionCreators =
|
||||
config.getCustomActionCreators || defaultActionCreators;
|
||||
|
||||
const initialRouteParams = config.initialRouteParams;
|
||||
const initialRouteName = config.initialRouteName || order[0];
|
||||
const backBehavior = config.backBehavior || 'none';
|
||||
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
||||
? config.resetOnBlur
|
||||
: true;
|
||||
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||
const childRouters = {};
|
||||
order.forEach(routeName => {
|
||||
const routeConfig = routeConfigs[routeName];
|
||||
if (!paths[routeName]) {
|
||||
paths[routeName] =
|
||||
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
||||
}
|
||||
childRouters[routeName] = null;
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
if (screen.router) {
|
||||
childRouters[routeName] = screen.router;
|
||||
}
|
||||
});
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
function resetChildRoute(routeName) {
|
||||
const params =
|
||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||
const childRouter = childRouters[routeName];
|
||||
if (childRouter) {
|
||||
const childAction = NavigationActions.init();
|
||||
return {
|
||||
...childRouter.getStateForAction(childAction),
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
function getNextState(prevState, possibleNextState) {
|
||||
if (!prevState) {
|
||||
return possibleNextState;
|
||||
}
|
||||
|
||||
let nextState;
|
||||
if (prevState.index !== possibleNextState.index && resetOnBlur) {
|
||||
const prevRouteName = prevState.routes[prevState.index].routeName;
|
||||
const nextRoutes = [...possibleNextState.routes];
|
||||
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
|
||||
|
||||
return {
|
||||
...possibleNextState,
|
||||
routes: nextRoutes,
|
||||
};
|
||||
} else {
|
||||
nextState = possibleNextState;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
}
|
||||
|
||||
function getInitialState() {
|
||||
const routes = order.map(resetChildRoute);
|
||||
return {
|
||||
routes,
|
||||
index: initialRouteIndex,
|
||||
isTransitioning: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
childRouters,
|
||||
|
||||
getActionCreators(route, stateKey) {
|
||||
return {
|
||||
...getNavigationActionCreators(route),
|
||||
...getCustomActionCreators(route, stateKey),
|
||||
};
|
||||
},
|
||||
|
||||
getStateForAction(action, inputState) {
|
||||
let prevState = inputState ? { ...inputState } : inputState;
|
||||
let state = inputState || getInitialState();
|
||||
let activeChildIndex = state.index;
|
||||
|
||||
if (action.type === NavigationActions.INIT) {
|
||||
// NOTE(brentvatne): this seems weird... why are we merging these
|
||||
// params into child routes?
|
||||
// ---------------------------------------------------------------
|
||||
// Merge any params from the action into all the child routes
|
||||
const { params } = action;
|
||||
if (params) {
|
||||
state.routes = state.routes.map(route => ({
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...params,
|
||||
...(route.routeName === initialRouteName
|
||||
? initialRouteParams
|
||||
: null),
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Let the current child handle it
|
||||
const activeChildLastState = state.routes[state.index];
|
||||
const activeChildRouter = childRouters[order[state.index]];
|
||||
if (activeChildRouter) {
|
||||
const activeChildState = activeChildRouter.getStateForAction(
|
||||
action,
|
||||
activeChildLastState
|
||||
);
|
||||
if (!activeChildState && inputState) {
|
||||
return null;
|
||||
}
|
||||
if (activeChildState && activeChildState !== activeChildLastState) {
|
||||
const routes = [...state.routes];
|
||||
routes[state.index] = activeChildState;
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tab changing. Do this after letting the current tab try to
|
||||
// handle the action, to allow inner children to change first
|
||||
const isBackEligible =
|
||||
action.key == null || action.key === activeChildLastState.key;
|
||||
if (action.type === NavigationActions.BACK) {
|
||||
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||
activeChildIndex = initialRouteIndex;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
let didNavigate = false;
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
didNavigate = !!order.find((childId, i) => {
|
||||
if (childId === action.routeName) {
|
||||
activeChildIndex = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (didNavigate) {
|
||||
const childState = state.routes[activeChildIndex];
|
||||
const childRouter = childRouters[action.routeName];
|
||||
let newChildState;
|
||||
|
||||
if (action.action) {
|
||||
newChildState = childRouter
|
||||
? childRouter.getStateForAction(action.action, childState)
|
||||
: null;
|
||||
} else if (!action.action && !childRouter && action.params) {
|
||||
newChildState = {
|
||||
...childState,
|
||||
params: {
|
||||
...(childState.params || {}),
|
||||
...action.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (newChildState && newChildState !== childState) {
|
||||
const routes = [...state.routes];
|
||||
routes[activeChildIndex] = newChildState;
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
index: activeChildIndex,
|
||||
});
|
||||
} else if (
|
||||
!newChildState &&
|
||||
state.index === activeChildIndex &&
|
||||
prevState
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === NavigationActions.SET_PARAMS) {
|
||||
const key = action.key;
|
||||
const lastRoute = state.routes.find(route => route.key === key);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.params,
|
||||
...action.params,
|
||||
};
|
||||
const routes = [...state.routes];
|
||||
routes[state.routes.indexOf(lastRoute)] = {
|
||||
...lastRoute,
|
||||
params,
|
||||
};
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (activeChildIndex !== state.index) {
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
index: activeChildIndex,
|
||||
});
|
||||
} else if (didNavigate && !inputState) {
|
||||
return state;
|
||||
} else if (didNavigate) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
// Let other children handle it and switch to the first child that returns a new state
|
||||
let index = state.index;
|
||||
let routes = state.routes;
|
||||
order.find((childId, i) => {
|
||||
const childRouter = childRouters[childId];
|
||||
if (i === index) {
|
||||
return false;
|
||||
}
|
||||
let childState = routes[i];
|
||||
if (childRouter) {
|
||||
childState = childRouter.getStateForAction(action, childState);
|
||||
}
|
||||
if (!childState) {
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
if (childState !== routes[i]) {
|
||||
routes = [...routes];
|
||||
routes[i] = childState;
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Nested routers can be updated after switching children with actions such as SET_PARAMS
|
||||
// and COMPLETE_TRANSITION.
|
||||
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
||||
// that can be handled by child routers without automatically changing index.
|
||||
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
||||
index = state.index;
|
||||
}
|
||||
|
||||
if (index !== state.index || routes !== state.routes) {
|
||||
return getNextState(prevState, {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
});
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
getComponentForState(state) {
|
||||
const routeName = state.routes[state.index].routeName;
|
||||
invariant(
|
||||
routeName,
|
||||
`There is no route defined for index ${state.index}. Check that
|
||||
that you passed in a navigation state with a valid tab/screen index.`
|
||||
);
|
||||
const childRouter = childRouters[routeName];
|
||||
if (childRouter) {
|
||||
return childRouter.getComponentForState(state.routes[state.index]);
|
||||
}
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getComponentForRouteName(routeName) {
|
||||
return getScreenForRouteName(routeConfigs, routeName);
|
||||
},
|
||||
|
||||
getPathAndParamsForState(state) {
|
||||
const route = state.routes[state.index];
|
||||
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
|
||||
),
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user