mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
153 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75ed888b33 | ||
|
|
ee82ab1d1b | ||
|
|
301c35ec32 | ||
|
|
22cb675608 | ||
|
|
c41c824aae | ||
|
|
a93a81e5d3 | ||
|
|
57b411cea8 | ||
|
|
3a4c38bb72 | ||
|
|
e6a06ac56e | ||
|
|
2ef5ad4cc2 | ||
|
|
74ee216ed4 | ||
|
|
77f29d374f | ||
|
|
5a34764404 | ||
|
|
738f226535 | ||
|
|
5cd69401ec | ||
|
|
c55d4511ff | ||
|
|
c07d61dc41 | ||
|
|
67fd69adf6 | ||
|
|
3de9edbe72 | ||
|
|
12d597fcc0 | ||
|
|
50dea65377 | ||
|
|
3d9db6ff25 | ||
|
|
fb749ac064 | ||
|
|
58f7115298 | ||
|
|
3a77107968 | ||
|
|
aa40130266 | ||
|
|
7635373366 | ||
|
|
941f4e2dec | ||
|
|
876c3183e1 | ||
|
|
1cc31bf900 | ||
|
|
8f16085808 | ||
|
|
58fbfdf5c3 | ||
|
|
07bfc86327 | ||
|
|
e54d87c41c | ||
|
|
225e760a54 | ||
|
|
f9cfbd01d8 | ||
|
|
3bf46e1ad2 | ||
|
|
de2b6d8715 | ||
|
|
f22abb726c | ||
|
|
031c4d2378 | ||
|
|
2b5955efbe | ||
|
|
70f7e7a7c0 | ||
|
|
6cf1a041b2 | ||
|
|
0d8cdc8a27 | ||
|
|
2680b461a2 | ||
|
|
fb726eede3 | ||
|
|
3aaf6eb648 | ||
|
|
477c08858d | ||
|
|
300791ab49 | ||
|
|
3e92e22941 | ||
|
|
a543f1bfc3 | ||
|
|
cd5f355bd0 | ||
|
|
cab616069f | ||
|
|
f90e00cc93 | ||
|
|
731cf7d5b1 | ||
|
|
c6d0c19b49 | ||
|
|
442b95d9e4 | ||
|
|
01277575f2 | ||
|
|
386d1c0888 | ||
|
|
9d9fe31f02 | ||
|
|
0a5fb3e268 | ||
|
|
1d7a37cd4a | ||
|
|
2b57702a62 | ||
|
|
c7da1e4145 | ||
|
|
6a0ca90873 | ||
|
|
e9a1cfcec3 | ||
|
|
b71f4e52a3 | ||
|
|
9ce8ec59fb | ||
|
|
d685e78fa9 | ||
|
|
7a901af5b5 | ||
|
|
ba3f718ab3 | ||
|
|
42beb660ca | ||
|
|
e789846692 | ||
|
|
b32cda2446 | ||
|
|
708dde0e47 | ||
|
|
5303e8ffb5 | ||
|
|
ba6b6ae025 | ||
|
|
16079d1050 | ||
|
|
b4a76814c6 | ||
|
|
8294efc8f4 | ||
|
|
a27ade8881 | ||
|
|
615b523d26 | ||
|
|
070c46ba64 | ||
|
|
d8394cf919 | ||
|
|
a7c4a4d7cd | ||
|
|
282b465ae1 | ||
|
|
6f5f4b7d35 | ||
|
|
d75915d1f3 | ||
|
|
832ed882bc | ||
|
|
8318c49331 | ||
|
|
ece6e3899e | ||
|
|
da944ccef9 | ||
|
|
bc3586ae3e | ||
|
|
19be2b4bbc | ||
|
|
a8851b730d | ||
|
|
7a5bcb446b | ||
|
|
1345a8fec6 | ||
|
|
7393464515 | ||
|
|
9810a06061 | ||
|
|
655a220137 | ||
|
|
e61f5941d7 | ||
|
|
8b78d617c0 | ||
|
|
1fd5a868a3 | ||
|
|
1153d5575b | ||
|
|
76de32f441 | ||
|
|
4ee19bcdb9 | ||
|
|
06cc8b9142 | ||
|
|
2f66556b10 | ||
|
|
f39813626a | ||
|
|
089390ce87 | ||
|
|
d09c9ae643 | ||
|
|
8920da68a7 | ||
|
|
98a9487cda | ||
|
|
ff502820c7 | ||
|
|
30e510d123 | ||
|
|
114a5dc888 | ||
|
|
b4bbf9b0c3 | ||
|
|
98f0de088f | ||
|
|
2537145d81 | ||
|
|
6af8400421 | ||
|
|
d83d3de697 | ||
|
|
88a560aacb | ||
|
|
e0f28a432d | ||
|
|
438692d661 | ||
|
|
849d952703 | ||
|
|
17045f5b6d | ||
|
|
d16c20cd39 | ||
|
|
9563a27284 | ||
|
|
f57a91c417 | ||
|
|
8e8b7eef36 | ||
|
|
44020452fb | ||
|
|
ceaf18edd6 | ||
|
|
faaf855717 | ||
|
|
196cce0803 | ||
|
|
a022860317 | ||
|
|
167d58ce27 | ||
|
|
798a905d0a | ||
|
|
aa6313c0e9 | ||
|
|
2563924f53 | ||
|
|
55ec815247 | ||
|
|
c7a79a62fa | ||
|
|
5c6e85cec8 | ||
|
|
3f853d458f | ||
|
|
06b3e6bda7 | ||
|
|
3c840bbae3 | ||
|
|
d4ad9d48f9 | ||
|
|
a0e9784d98 | ||
|
|
eff0c0464f | ||
|
|
fe9ba2bf71 | ||
|
|
b0a3756d18 | ||
|
|
4e0ebb05f9 | ||
|
|
bf0b408238 | ||
|
|
1b2983eaa9 |
@@ -14,11 +14,11 @@ jobs:
|
||||
at: ~/project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
- v1-dependencies-
|
||||
- run: yarn install
|
||||
- run: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
paths: node_modules
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"@react-navigation/core",
|
||||
"@react-navigation/native",
|
||||
"@react-navigation/routers",
|
||||
"@react-navigation/compat",
|
||||
"@react-navigation/stack",
|
||||
"@react-navigation/native-stack",
|
||||
"@react-navigation/drawer",
|
||||
"@react-navigation/bottom-tabs",
|
||||
"@react-navigation/material-top-tabs",
|
||||
|
||||
40
.github/ISSUE_TEMPLATE.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
## The issue tracker is reserved for bug reports only.
|
||||
|
||||
If you have a question, feature request, or an idea for improving the library or its related tools, please try one of the following resources:
|
||||
|
||||
- [Read the documentation](https://reactnavigation.org/)
|
||||
- [Post an issue to the website repository if you'd like to see a documentation change](http://github.com/react-navigation/website)
|
||||
- [Post a feature request to Canny](https://react-navigation.canny.io/feature-requests)
|
||||
- [Write a RFC if you have ideas for how to implement a feature request](https://github.com/react-navigation/rfcs)
|
||||
- [Get help on Discord chat (#react-navigation on Reactiflux)](https://discord.gg/4xEK3nD) or [on StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation)
|
||||
- Search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue.
|
||||
|
||||
Bugs with react-navigation must be reproducible *without any external libraries that operate on it*. This means that if you are attempting to use Redux or MobX with it and you think you have found a bug, you must be able to reproduce it without Redux or MobX in this report.
|
||||
|
||||
---
|
||||
|
||||
### Current Behavior
|
||||
|
||||
- What code are you running and what is happening?
|
||||
- Include a screenshot if it makes sense.
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
- What do you expect should be happening?
|
||||
- Include a screenshot if it makes sense.
|
||||
|
||||
### 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 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.
|
||||
|
||||
### Your Environment
|
||||
|
||||
| software | version
|
||||
| ---------------- | -------
|
||||
| react-navigation |
|
||||
| react-native |
|
||||
| expo |
|
||||
| node |
|
||||
| npm or yarn |
|
||||
17
.github/PULL_REQUEST.md
vendored
Normal file
17
.github/PULL_REQUEST.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
Please provide enough information so that others can review your pull request:
|
||||
|
||||
## Motivation
|
||||
|
||||
Explain the **motivation** for making this change. What existing problem does the pull request solve?
|
||||
|
||||
## Test plan
|
||||
|
||||
Demonstrate the code is solid. Example: the exact commands you ran and their output, screenshots / videos if the pull request changes UI.
|
||||
|
||||
Make sure you test on both platforms if your change affects both platforms.
|
||||
|
||||
The code must pass tests.
|
||||
|
||||
## Code formatting
|
||||
|
||||
Look around. Match the style of the rest of the codebase. Run `yarn lint --fix` before committing.
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -3,17 +3,24 @@
|
||||
.vscode
|
||||
.idea
|
||||
.expo
|
||||
.gradle
|
||||
|
||||
local.properties
|
||||
|
||||
/coverage/
|
||||
/types/
|
||||
/dist/
|
||||
/lib/
|
||||
/packages/**/lib/
|
||||
|
||||
node_modules/
|
||||
web-build/
|
||||
web-report/
|
||||
|
||||
xcuserdata
|
||||
generated
|
||||
lib
|
||||
build
|
||||
|
||||
npm-debug.*
|
||||
|
||||
*.log
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore: release %s",
|
||||
"tagName": "v%s"
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "angular"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
};
|
||||
|
||||
30
package.json
30
package.json
@@ -23,27 +23,26 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-env": "^7.6.2",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@commitlint/config-conventional": "^8.0.0",
|
||||
"@babel/preset-typescript": "^7.6.0",
|
||||
"@babel/runtime": "^7.6.2",
|
||||
"@commitlint/config-conventional": "^8.2.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"codecov": "^3.5.0",
|
||||
"commitlint": "^8.0.0",
|
||||
"core-js": "^3.1.4",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-satya164": "^2.4.1",
|
||||
"husky": "^2.4.0",
|
||||
"codecov": "^3.6.1",
|
||||
"commitlint": "^8.2.0",
|
||||
"core-js": "^3.2.1",
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-config-satya164": "^3.1.2",
|
||||
"husky": "^3.0.8",
|
||||
"jest": "^24.8.0",
|
||||
"lerna": "^3.16.4",
|
||||
"prettier": "^1.18.2",
|
||||
"typescript": "^3.5.3"
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "16.8.3",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
|
||||
"react-native-safe-area-view": "0.14.6"
|
||||
"react": "~16.8.3",
|
||||
"react-native": "~0.59.10"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -69,6 +68,5 @@
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"name": "react-navigation"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,140 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.18...@react-navigation/bottom-tabs@5.0.0-alpha.19) (2019-11-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix default BottomTabBar button ([#161](https://github.com/react-navigation/navigation-ex/issues/161)) ([22cb675](https://github.com/react-navigation/navigation-ex/commit/22cb675))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.17...@react-navigation/bottom-tabs@5.0.0-alpha.18) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.16...@react-navigation/bottom-tabs@5.0.0-alpha.17) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hide inactive pages from screen reader in tabs ([#148](https://github.com/react-navigation/navigation-ex/issues/148)) ([58f7115](https://github.com/react-navigation/navigation-ex/commit/58f7115))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an 'unmountInactiveScreens' option ([12d597f](https://github.com/react-navigation/navigation-ex/commit/12d597f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.15...@react-navigation/bottom-tabs@5.0.0-alpha.16) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.14...@react-navigation/bottom-tabs@5.0.0-alpha.15) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.13...@react-navigation/bottom-tabs@5.0.0-alpha.14) (2019-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve keyboard handling with bottom tab bar ([42beb66](https://github.com/react-navigation/navigation-ex/commit/42beb66))
|
||||
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.12...@react-navigation/bottom-tabs@5.0.0-alpha.13) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.11...@react-navigation/bottom-tabs@5.0.0-alpha.12) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.10...@react-navigation/bottom-tabs@5.0.0-alpha.11) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.9...@react-navigation/bottom-tabs@5.0.0-alpha.10) (2019-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export BottomTabBar props type ([#109](https://github.com/react-navigation/navigation-ex/issues/109)) ([1fd5a86](https://github.com/react-navigation/navigation-ex/commit/1fd5a86))
|
||||
* export some more type aliases ([8b78d61](https://github.com/react-navigation/navigation-ex/commit/8b78d61))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.8...@react-navigation/bottom-tabs@5.0.0-alpha.9) (2019-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.7...@react-navigation/bottom-tabs@5.0.0-alpha.8) (2019-09-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.6...@react-navigation/bottom-tabs@5.0.0-alpha.7) (2019-08-31)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.5...@react-navigation/bottom-tabs@5.0.0-alpha.6) (2019-08-29)
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,40 @@ Open a Terminal in your project's folder and run,
|
||||
yarn add @react-navigation/core @react-navigation/bottom-tabs
|
||||
```
|
||||
|
||||
Now we need to install [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
|
||||
|
||||
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
|
||||
|
||||
```sh
|
||||
expo install react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are not using Expo, run the following:
|
||||
|
||||
```sh
|
||||
yarn add react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are using Expo, you are done. Otherwise, continue to the next steps.
|
||||
|
||||
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
|
||||
|
||||
```sh
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { MaterialCommunityIcons } from 'react-native-vector-icons';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
|
||||
const getTabBarIcon = name => ({ color, size }) => (
|
||||
<MaterialCommunityIcons name={name} color={color} size={size} />
|
||||
);
|
||||
|
||||
const BottomTabs = createBottomTabNavigator();
|
||||
|
||||
export default function App() {
|
||||
@@ -25,7 +54,7 @@ export default function App() {
|
||||
component={Article}
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: 'chrome-reader-mode',
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
@@ -33,7 +62,7 @@ export default function App() {
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: 'chat-bubble',
|
||||
tabBarIcon: getTabBarIcon('message-reply'),
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
@@ -41,7 +70,7 @@ export default function App() {
|
||||
component={Contacts}
|
||||
options={{
|
||||
tabBarLabel: 'Contacts',
|
||||
tabBarIcon: 'contacts',
|
||||
tabBarIcon: getTabBarIcon('contacts'),
|
||||
}}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"android",
|
||||
"tab"
|
||||
],
|
||||
"version": "5.0.0-alpha.6",
|
||||
"version": "5.0.0-alpha.19",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,22 +33,23 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.6",
|
||||
"react-native-safe-area-view": "^0.14.6"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
"@types/react": "^16.8.24",
|
||||
"@types/react-native": "^0.60.2",
|
||||
"del-cli": "^2.0.0",
|
||||
"react": "16.8.3",
|
||||
"react-native": "^0.59.8",
|
||||
"typescript": "^3.5.3"
|
||||
"@types/react": "^16.9.4",
|
||||
"@types/react-native": "^0.60.17",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.8.3",
|
||||
"react-native": "~0.59.10",
|
||||
"react-native-safe-area-context": "^0.3.6",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/core": "^5.0.0-alpha.0",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": "^0.3.6"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -13,4 +13,9 @@ export { default as BottomTabBar } from './views/BottomTabBar';
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
export { BottomTabNavigationOptions, BottomTabNavigationProp } from './types';
|
||||
export {
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationProp,
|
||||
BottomTabBarProps,
|
||||
BottomTabBarOptions,
|
||||
} from './types';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
TouchableWithoutFeedbackProps,
|
||||
AccessibilityRole,
|
||||
AccessibilityStates,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
NavigationProp,
|
||||
@@ -67,26 +67,22 @@ export type BottomTabNavigationOptions = {
|
||||
|
||||
/**
|
||||
* Title string of a tab displayed in the tab bar or React Element
|
||||
* or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in tab bar.
|
||||
* or a function that given { focused: boolean, color: string } returns a React.Node, to display in tab bar.
|
||||
* When undefined, scene title is used. To hide, see tabBarOptions.showLabel in the previous section.
|
||||
*/
|
||||
tabBarLabel?:
|
||||
| React.ReactNode
|
||||
| ((props: {
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
}) => React.ReactNode);
|
||||
| ((props: { focused: boolean; color: string }) => React.ReactNode);
|
||||
|
||||
/**
|
||||
* React Element or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in the tab bar.
|
||||
* React Element or a function that given { focused: boolean, color: string } returns a React.Node, to display in the tab bar.
|
||||
*/
|
||||
tabBarIcon?:
|
||||
| React.ReactNode
|
||||
| ((props: {
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
color: string;
|
||||
size: number;
|
||||
}) => React.ReactNode);
|
||||
|
||||
/**
|
||||
@@ -106,9 +102,10 @@ export type BottomTabNavigationOptions = {
|
||||
tabBarVisible?: boolean;
|
||||
|
||||
/**
|
||||
* Buttton component to render for the tab items instead of the default `TouchableWithoutFeedback`
|
||||
* Function which returns a React element to render as the tab bar button.
|
||||
* Renders `TouchableWithoutFeedback` by default.
|
||||
*/
|
||||
tabBarButtonComponent?: React.ComponentType<any>;
|
||||
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
||||
};
|
||||
|
||||
export type BottomTabDescriptor = Descriptor<
|
||||
@@ -129,9 +126,14 @@ export type BottomTabNavigationConfig = {
|
||||
*/
|
||||
lazy?: boolean;
|
||||
/**
|
||||
* Custom tab bar component.
|
||||
* Whether a screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
tabBarComponent?: React.ComponentType<BottomTabBarProps>;
|
||||
unmountInactiveScreens?: boolean;
|
||||
/**
|
||||
* Function that returns a React element to display as the tab bar.
|
||||
*/
|
||||
tabBar?: (props: BottomTabBarProps) => React.ReactNode;
|
||||
/**
|
||||
* Options for the tab bar which will be passed as props to the tab bar component.
|
||||
*/
|
||||
@@ -140,7 +142,7 @@ export type BottomTabNavigationConfig = {
|
||||
|
||||
export type BottomTabBarOptions = {
|
||||
/**
|
||||
* Whether the tab bar gets hidden when the keyboard is shown.
|
||||
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
|
||||
*/
|
||||
keyboardHidesTabBar?: boolean;
|
||||
/**
|
||||
@@ -199,6 +201,7 @@ export type BottomTabBarOptions = {
|
||||
|
||||
export type BottomTabBarProps = BottomTabBarOptions & {
|
||||
state: TabNavigationState;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
onTabPress: (props: { route: Route<string> }) => void;
|
||||
onTabLongPress: (props: { route: Route<string> }) => void;
|
||||
@@ -212,24 +215,26 @@ export type BottomTabBarProps = BottomTabBarOptions & {
|
||||
route: Route<string>;
|
||||
focused: boolean;
|
||||
}) => AccessibilityStates[];
|
||||
getButtonComponent: (props: {
|
||||
route: Route<string>;
|
||||
}) => React.ComponentType<any> | undefined;
|
||||
getLabelText: (props: {
|
||||
route: Route<string>;
|
||||
}) =>
|
||||
| ((scene: {
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
orientation: 'horizontal' | 'vertical';
|
||||
color: string;
|
||||
}) => React.ReactNode | undefined)
|
||||
| React.ReactNode;
|
||||
getTestID: (props: { route: Route<string> }) => string | undefined;
|
||||
renderButton: (
|
||||
props: { route: Route<string> } & BottomTabBarButtonProps
|
||||
) => React.ReactNode;
|
||||
renderIcon: (props: {
|
||||
route: Route<string>;
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
color: string;
|
||||
size: number;
|
||||
}) => React.ReactNode;
|
||||
safeAreaInset?: React.ComponentProps<typeof SafeAreaView>['forceInset'];
|
||||
};
|
||||
|
||||
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Keyboard,
|
||||
@@ -8,11 +9,10 @@ import {
|
||||
ScaledSize,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { Route } from '@react-navigation/core';
|
||||
import { Route, NavigationContext } from '@react-navigation/core';
|
||||
import { SafeAreaConsumer } from 'react-native-safe-area-context';
|
||||
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
import TouchableWithoutFeedbackWrapper from './TouchableWithoutFeedbackWrapper';
|
||||
import { BottomTabBarProps } from '../types';
|
||||
|
||||
type State = {
|
||||
@@ -25,7 +25,6 @@ type State = {
|
||||
type Props = BottomTabBarProps & {
|
||||
activeTintColor: string;
|
||||
inactiveTintColor: string;
|
||||
safeAreaInset: React.ComponentProps<typeof SafeAreaView>['forceInset'];
|
||||
};
|
||||
|
||||
const majorVersion = parseInt(Platform.Version as string, 10);
|
||||
@@ -36,7 +35,7 @@ const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
||||
|
||||
export default class TabBarBottom extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
keyboardHidesTabBar: true,
|
||||
keyboardHidesTabBar: false,
|
||||
activeTintColor: '#007AFF',
|
||||
activeBackgroundColor: 'transparent',
|
||||
inactiveTintColor: '#8E8E93',
|
||||
@@ -45,9 +44,6 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
showIcon: true,
|
||||
allowFontScaling: true,
|
||||
adaptive: isIOS11,
|
||||
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps<
|
||||
typeof SafeAreaView
|
||||
>['forceInset'],
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -89,7 +85,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
this.setState({ keyboard: true }, () =>
|
||||
Animated.timing(this.state.visible, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}).start()
|
||||
);
|
||||
@@ -97,10 +93,12 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
private handleKeyboardHide = () =>
|
||||
Animated.timing(this.state.visible, {
|
||||
toValue: 1,
|
||||
duration: 100,
|
||||
duration: 250,
|
||||
useNativeDriver: true,
|
||||
}).start(() => {
|
||||
this.setState({ keyboard: false });
|
||||
}).start(({ finished }) => {
|
||||
if (finished) {
|
||||
this.setState({ keyboard: false });
|
||||
}
|
||||
});
|
||||
|
||||
private handleLayout = (e: LayoutChangeEvent) => {
|
||||
@@ -141,7 +139,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
|
||||
const label = this.props.getLabelText({ route });
|
||||
const horizontal = this.shouldUseHorizontalLabels();
|
||||
const tintColor = focused ? activeTintColor : inactiveTintColor;
|
||||
const color = focused ? activeTintColor : inactiveTintColor;
|
||||
|
||||
if (typeof label === 'string') {
|
||||
return (
|
||||
@@ -149,7 +147,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
styles.label,
|
||||
{ color: tintColor },
|
||||
{ color },
|
||||
showIcon && horizontal ? styles.labelBeside : styles.labelBeneath,
|
||||
labelStyle,
|
||||
]}
|
||||
@@ -161,11 +159,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
if (typeof label === 'function') {
|
||||
return label({
|
||||
focused,
|
||||
tintColor,
|
||||
orientation: horizontal ? 'horizontal' : 'vertical',
|
||||
});
|
||||
return label({ focused, color });
|
||||
}
|
||||
|
||||
return label;
|
||||
@@ -198,7 +192,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
return (
|
||||
<TabBarIcon
|
||||
route={route}
|
||||
horizontal={horizontal}
|
||||
size={horizontal ? 17 : 24}
|
||||
activeOpacity={activeOpacity}
|
||||
inactiveOpacity={inactiveOpacity}
|
||||
activeTintColor={activeTintColor}
|
||||
@@ -261,6 +255,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
render() {
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
keyboardHidesTabBar,
|
||||
activeBackgroundColor,
|
||||
inactiveBackgroundColor,
|
||||
@@ -269,98 +264,104 @@ export default class TabBarBottom extends React.Component<Props, State> {
|
||||
getAccessibilityLabel,
|
||||
getAccessibilityRole,
|
||||
getAccessibilityStates,
|
||||
getButtonComponent,
|
||||
renderButton,
|
||||
getTestID,
|
||||
safeAreaInset,
|
||||
style,
|
||||
tabStyle,
|
||||
} = this.props;
|
||||
const { routes } = state;
|
||||
|
||||
const tabBarStyle = [
|
||||
styles.tabBar,
|
||||
// @ts-ignore
|
||||
this.shouldUseHorizontalLabels() && !Platform.isPad
|
||||
? styles.tabBarCompact
|
||||
: styles.tabBarRegular,
|
||||
style,
|
||||
];
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.container,
|
||||
keyboardHidesTabBar
|
||||
? // eslint-disable-next-line react-native/no-inline-styles
|
||||
<SafeAreaConsumer>
|
||||
{insets => (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: this.state.visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [this.state.layout.height, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: this.state.keyboard ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: this.state.visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [this.state.layout.height, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: this.state.keyboard ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
]}
|
||||
pointerEvents={
|
||||
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
|
||||
}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const scene = { route, focused };
|
||||
const accessibilityLabel = getAccessibilityLabel({
|
||||
route,
|
||||
});
|
||||
height:
|
||||
// @ts-ignore
|
||||
(this.shouldUseHorizontalLabels() && !Platform.isPad
|
||||
? COMPACT_HEIGHT
|
||||
: DEFAULT_HEIGHT) + (insets ? insets.bottom : 0),
|
||||
paddingBottom: insets ? insets.bottom : 0,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={
|
||||
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
|
||||
}
|
||||
>
|
||||
<View style={styles.content} onLayout={this.handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const scene = { route, focused };
|
||||
const accessibilityLabel = getAccessibilityLabel({
|
||||
route,
|
||||
});
|
||||
|
||||
const accessibilityRole = getAccessibilityRole({
|
||||
route,
|
||||
});
|
||||
const accessibilityRole = getAccessibilityRole({
|
||||
route,
|
||||
});
|
||||
|
||||
const accessibilityStates = getAccessibilityStates(scene);
|
||||
const accessibilityStates = getAccessibilityStates(scene);
|
||||
|
||||
const testID = getTestID({ route });
|
||||
const testID = getTestID({ route });
|
||||
|
||||
const backgroundColor = focused
|
||||
? activeBackgroundColor
|
||||
: inactiveBackgroundColor;
|
||||
const backgroundColor = focused
|
||||
? activeBackgroundColor
|
||||
: inactiveBackgroundColor;
|
||||
|
||||
const ButtonComponent =
|
||||
getButtonComponent({ route }) || TouchableWithoutFeedbackWrapper;
|
||||
|
||||
return (
|
||||
<ButtonComponent
|
||||
key={route.key}
|
||||
onPress={() => onTabPress({ route })}
|
||||
onLongPress={() => onTabLongPress({ route })}
|
||||
testID={testID}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityStates={accessibilityStates}
|
||||
style={[
|
||||
styles.tab,
|
||||
{ backgroundColor },
|
||||
this.shouldUseHorizontalLabels()
|
||||
? styles.tabLandscape
|
||||
: styles.tabPortrait,
|
||||
tabStyle,
|
||||
]}
|
||||
>
|
||||
{this.renderIcon(scene)}
|
||||
{this.renderLabel(scene)}
|
||||
</ButtonComponent>
|
||||
);
|
||||
})}
|
||||
</SafeAreaView>
|
||||
</Animated.View>
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={route.key}
|
||||
value={descriptors[route.key].navigation}
|
||||
>
|
||||
{renderButton({
|
||||
route,
|
||||
onPress: () => onTabPress({ route }),
|
||||
onLongPress: () => onTabLongPress({ route }),
|
||||
testID,
|
||||
accessibilityLabel,
|
||||
accessibilityRole,
|
||||
accessibilityStates,
|
||||
style: [
|
||||
styles.tab,
|
||||
{ backgroundColor },
|
||||
this.shouldUseHorizontalLabels()
|
||||
? styles.tabLandscape
|
||||
: styles.tabPortrait,
|
||||
tabStyle,
|
||||
],
|
||||
children: (
|
||||
<React.Fragment>
|
||||
{this.renderIcon(scene)}
|
||||
{this.renderLabel(scene)}
|
||||
</React.Fragment>
|
||||
),
|
||||
})}
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</SafeAreaConsumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -370,22 +371,17 @@ const COMPACT_HEIGHT = 29;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, .3)',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
container: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, .3)',
|
||||
elevation: 8,
|
||||
},
|
||||
tabBarCompact: {
|
||||
height: COMPACT_HEIGHT,
|
||||
},
|
||||
tabBarRegular: {
|
||||
height: DEFAULT_HEIGHT,
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
View,
|
||||
TouchableWithoutFeedback,
|
||||
StyleSheet,
|
||||
AccessibilityRole,
|
||||
AccessibilityStates,
|
||||
} from 'react-native';
|
||||
|
||||
import { Route, CommonActions } from '@react-navigation/core';
|
||||
import { TabNavigationState } from '@react-navigation/routers';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import BottomTabBar from './BottomTabBar';
|
||||
import {
|
||||
BottomTabNavigationConfig,
|
||||
BottomTabDescriptorMap,
|
||||
BottomTabNavigationHelpers,
|
||||
BottomTabBarProps,
|
||||
BottomTabBarButtonProps,
|
||||
} from '../types';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
|
||||
type Props = BottomTabNavigationConfig & {
|
||||
state: TabNavigationState;
|
||||
@@ -48,28 +53,37 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
loaded: [this.props.state.index],
|
||||
};
|
||||
|
||||
private getButtonComponent = ({ route }: { route: Route<string> }) => {
|
||||
private renderButton = ({
|
||||
route,
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: { route: Route<string> } & BottomTabBarButtonProps) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = descriptor.options;
|
||||
|
||||
if (options.tabBarButtonComponent) {
|
||||
return options.tabBarButtonComponent;
|
||||
if (options.tabBarButton) {
|
||||
return options.tabBarButton({ children, style, ...rest });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return (
|
||||
<TouchableWithoutFeedback {...rest}>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
private renderIcon = ({
|
||||
route,
|
||||
focused,
|
||||
tintColor,
|
||||
horizontal,
|
||||
color,
|
||||
size,
|
||||
}: {
|
||||
route: Route<string>;
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
color: string;
|
||||
size: number;
|
||||
}) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
@@ -77,7 +91,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
|
||||
if (options.tabBarIcon) {
|
||||
return typeof options.tabBarIcon === 'function'
|
||||
? options.tabBarIcon({ focused, tintColor, horizontal })
|
||||
? options.tabBarIcon({ focused, color, size })
|
||||
: options.tabBarIcon;
|
||||
}
|
||||
|
||||
@@ -158,7 +172,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
|
||||
private renderTabBar = () => {
|
||||
const {
|
||||
tabBarComponent: TabBarComponent = BottomTabBar,
|
||||
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
||||
tabBarOptions,
|
||||
state,
|
||||
navigation,
|
||||
@@ -173,53 +187,66 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TabBarComponent
|
||||
{...tabBarOptions}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
onTabPress={this.handleTabPress}
|
||||
onTabLongPress={this.handleTabLongPress}
|
||||
getLabelText={this.getLabelText}
|
||||
getButtonComponent={this.getButtonComponent}
|
||||
getAccessibilityLabel={this.getAccessibilityLabel}
|
||||
getAccessibilityRole={this.getAccessibilityRole}
|
||||
getAccessibilityStates={this.getAccessibilityStates}
|
||||
getTestID={this.getTestID}
|
||||
renderIcon={this.renderIcon}
|
||||
/>
|
||||
);
|
||||
return tabBar({
|
||||
...tabBarOptions,
|
||||
state: state,
|
||||
descriptors: descriptors,
|
||||
navigation: navigation,
|
||||
onTabPress: this.handleTabPress,
|
||||
onTabLongPress: this.handleTabLongPress,
|
||||
getLabelText: this.getLabelText,
|
||||
getAccessibilityLabel: this.getAccessibilityLabel,
|
||||
getAccessibilityRole: this.getAccessibilityRole,
|
||||
getAccessibilityStates: this.getAccessibilityStates,
|
||||
getTestID: this.getTestID,
|
||||
renderButton: this.renderButton,
|
||||
renderIcon: this.renderIcon,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, descriptors, lazy } = this.props;
|
||||
const { state, descriptors, lazy, unmountInactiveScreens } = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
<SafeAreaProvider>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
if (unmountInactiveScreens && index !== state.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFocused = state.index === index;
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
{descriptors[route.key].render()}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
const isFocused = state.index === index;
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
<View
|
||||
accessibilityElementsHidden={!isFocused}
|
||||
importantForAccessibility={
|
||||
isFocused ? 'auto' : 'no-hide-descendants'
|
||||
}
|
||||
style={styles.content}
|
||||
>
|
||||
{descriptors[route.key].render()}
|
||||
</View>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -232,4 +259,7 @@ const styles = StyleSheet.create({
|
||||
pages: {
|
||||
flex: 1,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,12 +24,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
style,
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
{ opacity: isVisible ? 1 : 0 },
|
||||
]}
|
||||
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Route } from '@react-navigation/core';
|
||||
|
||||
type Props = {
|
||||
route: Route<string>;
|
||||
horizontal: boolean;
|
||||
size: number;
|
||||
activeOpacity: number;
|
||||
inactiveOpacity: number;
|
||||
activeTintColor: string;
|
||||
@@ -12,8 +12,8 @@ type Props = {
|
||||
renderIcon: (props: {
|
||||
route: Route<string>;
|
||||
focused: boolean;
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
color: string;
|
||||
size: number;
|
||||
}) => React.ReactNode;
|
||||
style: StyleProp<ViewStyle>;
|
||||
};
|
||||
@@ -25,7 +25,7 @@ export default function TabBarIcon({
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
renderIcon,
|
||||
horizontal,
|
||||
size,
|
||||
style,
|
||||
}: Props) {
|
||||
// We render the icon twice at the same position on top of each other:
|
||||
@@ -36,16 +36,16 @@ export default function TabBarIcon({
|
||||
{renderIcon({
|
||||
route,
|
||||
focused: true,
|
||||
horizontal,
|
||||
tintColor: activeTintColor,
|
||||
size,
|
||||
color: activeTintColor,
|
||||
})}
|
||||
</View>
|
||||
<View style={[styles.icon, { opacity: inactiveOpacity }]}>
|
||||
{renderIcon({
|
||||
route,
|
||||
focused: false,
|
||||
horizontal,
|
||||
tintColor: inactiveTintColor,
|
||||
size,
|
||||
color: inactiveTintColor,
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TouchableWithoutFeedback, View } from 'react-native';
|
||||
|
||||
export default function TouchableWithoutFeedbackWrapper({
|
||||
onPress,
|
||||
onLongPress,
|
||||
testID,
|
||||
accessibilityLabel,
|
||||
accessibilityRole,
|
||||
accessibilityStates,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof TouchableWithoutFeedback> & {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
testID={testID}
|
||||
hitSlop={{ left: 15, right: 15, top: 0, bottom: 5 }}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityStates={accessibilityStates}
|
||||
>
|
||||
<View {...rest} />
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
123
packages/compat/CHANGELOG.md
Normal file
123
packages/compat/CHANGELOG.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.11...@react-navigation/compat@5.0.0-alpha.12) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.10...@react-navigation/compat@5.0.0-alpha.11) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop isFirstRouteInParent method ([#145](https://github.com/react-navigation/navigation-ex/issues/145)) ([3a77107](https://github.com/react-navigation/navigation-ex/commit/3a77107))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.9...@react-navigation/compat@5.0.0-alpha.10) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.8...@react-navigation/compat@5.0.0-alpha.9) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.7...@react-navigation/compat@5.0.0-alpha.8) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.2...@react-navigation/compat@5.0.0-alpha.3) (2019-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw when wrapping a compat navigatofor compat ([8920da6](https://github.com/react-navigation/navigation-ex/commit/8920da6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.1...@react-navigation/compat@5.0.0-alpha.2) (2019-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add createSwitchNavigator ([ff50282](https://github.com/react-navigation/navigation-ex/commit/ff50282))
|
||||
* add withNavigation and withNavigationFocus ([114a5dc](https://github.com/react-navigation/navigation-ex/commit/114a5dc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 5.0.0-alpha.1 (2019-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix dispatching compat actions ([88a560a](https://github.com/react-navigation/navigation-ex/commit/88a560a))
|
||||
* support legacy goBack method ([d83d3de](https://github.com/react-navigation/navigation-ex/commit/d83d3de))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))
|
||||
28
packages/compat/README.md
Normal file
28
packages/compat/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# `@react-navigation/compat`
|
||||
|
||||
Compatibility layer to write navigator definitions in static configuration format.
|
||||
|
||||
## Installation
|
||||
|
||||
Open a Terminal in your project's folder and run,
|
||||
|
||||
```sh
|
||||
yarn add @react-navigation/core @react-navigation/compat
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { createCompatNavigatorFactory } from '@react-navigation/compat';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
const RootStack = createCompatNavigatorFactory(createStackNavigator)(
|
||||
{
|
||||
Home: { screen: HomeScreen },
|
||||
Profile: { screen: ProfileScreen },
|
||||
},
|
||||
{
|
||||
initialRouteName: 'Profile',
|
||||
}
|
||||
);
|
||||
```
|
||||
47
packages/compat/package.json
Normal file
47
packages/compat/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.0.0-alpha.12",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/react-navigation/navigation-ex.git",
|
||||
"directory": "packages/compat"
|
||||
},
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/compat/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "bob build",
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.4",
|
||||
"react": "~16.8.3",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/core": "^5.0.0-alpha.0",
|
||||
"react": "~16.8.3"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
"output": "lib",
|
||||
"targets": [
|
||||
"commonjs",
|
||||
"module",
|
||||
"typescript"
|
||||
]
|
||||
}
|
||||
}
|
||||
30
packages/compat/src/CompatScreen.tsx
Normal file
30
packages/compat/src/CompatScreen.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
RouteProp,
|
||||
} from '@react-navigation/core';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
|
||||
type Props<ParamList extends ParamListBase> = {
|
||||
navigation: NavigationProp<ParamList>;
|
||||
route: RouteProp<ParamList, string>;
|
||||
component: React.ComponentType<any>;
|
||||
};
|
||||
|
||||
function ScreenComponent<ParamList extends ParamListBase>(
|
||||
props: Props<ParamList>
|
||||
) {
|
||||
const navigation = React.useMemo(
|
||||
() =>
|
||||
createCompatNavigationProp(props.navigation as any, props.route as any),
|
||||
[props.navigation, props.route]
|
||||
);
|
||||
|
||||
const screenProps = React.useContext(ScreenPropsContext);
|
||||
|
||||
return <props.component navigation={navigation} screenProps={screenProps} />;
|
||||
}
|
||||
|
||||
export default React.memo(ScreenComponent);
|
||||
13
packages/compat/src/DrawerActions.tsx
Normal file
13
packages/compat/src/DrawerActions.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DrawerActions, DrawerActionType } from '@react-navigation/routers';
|
||||
|
||||
export function openDrawer(): DrawerActionType {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer(): DrawerActionType {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer(): DrawerActionType {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
48
packages/compat/src/NavigationActions.tsx
Normal file
48
packages/compat/src/NavigationActions.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { CommonActions, NavigationState } from '@react-navigation/core';
|
||||
|
||||
export function navigate({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
action?: never;
|
||||
}): CommonActions.Action {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `navigate`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return CommonActions.navigate({
|
||||
name: routeName,
|
||||
key: key,
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function back(options?: { key?: null | string }) {
|
||||
return options && options.key != null
|
||||
? (state: NavigationState) => ({
|
||||
...CommonActions.goBack(),
|
||||
source: options.key,
|
||||
target: state.key,
|
||||
})
|
||||
: CommonActions.goBack();
|
||||
}
|
||||
|
||||
export function setParams({
|
||||
params,
|
||||
key,
|
||||
}: {
|
||||
params: object;
|
||||
key?: string;
|
||||
}): CommonActions.Action {
|
||||
return {
|
||||
...CommonActions.setParams(params),
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
3
packages/compat/src/ScreenPropsContext.tsx
Normal file
3
packages/compat/src/ScreenPropsContext.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<unknown>(undefined);
|
||||
68
packages/compat/src/StackActions.tsx
Normal file
68
packages/compat/src/StackActions.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { CommonActions } from '@react-navigation/core';
|
||||
import { StackActions, StackActionType } from '@react-navigation/routers';
|
||||
|
||||
export function reset(): CommonActions.Action {
|
||||
throw new Error(
|
||||
'The legacy `reset` action is not supported. Use the new reset API by accessing the original navigation object at `navigation.original`.'
|
||||
);
|
||||
}
|
||||
|
||||
export function replace({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
newKey,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
newKey?: string;
|
||||
action?: never;
|
||||
}): CommonActions.Action {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'REPLACE',
|
||||
payload: {
|
||||
name: routeName,
|
||||
key: newKey,
|
||||
params,
|
||||
},
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
|
||||
export function push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
action?: never;
|
||||
}): StackActionType {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `push`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return StackActions.push(routeName, params);
|
||||
}
|
||||
|
||||
export function pop({ n = 1 }: { n: number }): StackActionType {
|
||||
return StackActions.pop(n);
|
||||
}
|
||||
|
||||
export function popToTop(): StackActionType {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss(): CommonActions.Action {
|
||||
throw new Error('The legacy `dismiss` action is not supported.');
|
||||
}
|
||||
18
packages/compat/src/SwitchActions.tsx
Normal file
18
packages/compat/src/SwitchActions.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TabActions, TabActionType } from '@react-navigation/routers';
|
||||
|
||||
export function jumpTo({
|
||||
routeName,
|
||||
key,
|
||||
}: {
|
||||
routeName: string;
|
||||
key?: string;
|
||||
}): TabActionType {
|
||||
if (key === undefined) {
|
||||
return TabActions.jumpTo(routeName);
|
||||
} else {
|
||||
return {
|
||||
...TabActions.jumpTo(routeName),
|
||||
target: key,
|
||||
};
|
||||
}
|
||||
}
|
||||
195
packages/compat/src/createCompatNavigationProp.tsx
Normal file
195
packages/compat/src/createCompatNavigationProp.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
} from '@react-navigation/core';
|
||||
import * as helpers from './helpers';
|
||||
import { CompatNavigationProp } from './types';
|
||||
|
||||
type EventName = 'willFocus' | 'willBlur' | 'didFocus' | 'didBlur' | 'refocus';
|
||||
|
||||
const focusSubscriptions = new WeakMap<() => void, () => void>();
|
||||
const blurSubscriptions = new WeakMap<() => void, () => void>();
|
||||
const refocusSubscriptions = new WeakMap<() => void, () => void>();
|
||||
|
||||
export default function createCompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
>(
|
||||
navigation: NavigationPropType,
|
||||
state:
|
||||
| (RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
})
|
||||
| NavigationState
|
||||
| PartialState<NavigationState>
|
||||
): CompatNavigationProp<NavigationPropType> {
|
||||
return {
|
||||
...navigation,
|
||||
...Object.entries(helpers).reduce<{
|
||||
[key: string]: (...args: any[]) => void;
|
||||
}>((acc, [name, method]: [string, Function]) => {
|
||||
if (name in navigation) {
|
||||
acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
original: navigation,
|
||||
addListener(type: EventName, callback: () => void) {
|
||||
let unsubscribe: () => void;
|
||||
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
unsubscribe = navigation.addListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
unsubscribe = navigation.addListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
focusSubscriptions.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const listener = () => {
|
||||
if (!navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
blurSubscriptions.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
unsubscribe = navigation.addListener('tabPress', listener);
|
||||
refocusSubscriptions.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// @ts-ignore
|
||||
unsubscribe = navigation.addListener(type, callback);
|
||||
}
|
||||
|
||||
const subscription = () => unsubscribe();
|
||||
|
||||
subscription.remove = unsubscribe;
|
||||
|
||||
return subscription;
|
||||
},
|
||||
removeListener(type: EventName, callback: () => void) {
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
navigation.removeListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
navigation.removeListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const unsubscribe = focusSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const unsubscribe = blurSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const unsubscribe = refocusSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// @ts-ignore
|
||||
navigation.removeListener(type, callback);
|
||||
}
|
||||
},
|
||||
state: {
|
||||
...state,
|
||||
// @ts-ignore
|
||||
routeName: state.name,
|
||||
get index() {
|
||||
// @ts-ignore
|
||||
if (state.index !== undefined) {
|
||||
// @ts-ignore
|
||||
return state.index;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Accessing child navigation state for a route is not safe and won't work correctly."
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return state.state ? state.state.index : undefined;
|
||||
},
|
||||
get routes() {
|
||||
// @ts-ignore
|
||||
if (state.routes !== undefined) {
|
||||
// @ts-ignore
|
||||
return state.routes;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Accessing child navigation state for a route is not safe and won't work correctly."
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return state.state ? state.state.routes : undefined;
|
||||
},
|
||||
},
|
||||
getParam<T extends keyof ParamList>(
|
||||
paramName: T,
|
||||
defaultValue: ParamList[T]
|
||||
): ParamList[T] {
|
||||
// @ts-ignore
|
||||
const params = state.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
isFirstRouteInParent(): boolean {
|
||||
const { routes } = navigation.dangerouslyGetState();
|
||||
|
||||
// @ts-ignore
|
||||
return routes[0].key === state.key;
|
||||
},
|
||||
dangerouslyGetParent() {
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
if (parent) {
|
||||
return createCompatNavigationProp(
|
||||
parent,
|
||||
navigation.dangerouslyGetState()
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
171
packages/compat/src/createCompatNavigatorFactory.tsx
Normal file
171
packages/compat/src/createCompatNavigatorFactory.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
TypedNavigator,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
} from '@react-navigation/core';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import { CompatScreenType, CompatRouteConfig } from './types';
|
||||
|
||||
export default function createCompatNavigatorFactory<
|
||||
CreateNavigator extends () => TypedNavigator<
|
||||
ParamListBase,
|
||||
{},
|
||||
React.ComponentType<any>
|
||||
>
|
||||
>(createNavigator: CreateNavigator) {
|
||||
// @ts-ignore
|
||||
if (createNavigator.isCompat) {
|
||||
throw new Error(
|
||||
`The navigator is already in compat mode. You don't need to wrap it in 'createCompatNavigatorFactory'.`
|
||||
);
|
||||
}
|
||||
|
||||
const createCompatNavigator = <
|
||||
NavigationPropType extends NavigationProp<any, any, any, any, any>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {},
|
||||
NavigationConfig extends {} = React.ComponentProps<
|
||||
ReturnType<CreateNavigator>['Navigator']
|
||||
>
|
||||
>(
|
||||
routeConfig: CompatRouteConfig<NavigationPropType>,
|
||||
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
|
||||
order?: Array<Extract<keyof ParamList, string>>;
|
||||
defaultNavigationOptions?: ScreenOptions;
|
||||
navigationOptions?: Record<string, any>;
|
||||
} = {}
|
||||
) => {
|
||||
const Pair = createNavigator();
|
||||
|
||||
const {
|
||||
order,
|
||||
defaultNavigationOptions,
|
||||
navigationOptions: parentNavigationOptions,
|
||||
...restConfig
|
||||
} = navigationConfig;
|
||||
|
||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map(name => {
|
||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||
|
||||
let initialParams;
|
||||
|
||||
const routeConfigItem = routeConfig[name];
|
||||
|
||||
if ('getScreen' in routeConfigItem) {
|
||||
getScreenComponent = routeConfigItem.getScreen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else if ('screen' in routeConfigItem) {
|
||||
getScreenComponent = () => routeConfigItem.screen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else {
|
||||
getScreenComponent = () => routeConfigItem;
|
||||
}
|
||||
|
||||
const screenOptions = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: NavigationPropType;
|
||||
route: RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
}) => {
|
||||
// @ts-ignore
|
||||
const routeNavigationOptions = routeConfigItem.navigationOptions;
|
||||
const screenNavigationOptions = getScreenComponent()
|
||||
.navigationOptions;
|
||||
|
||||
if (
|
||||
routeNavigationOptions == null &&
|
||||
screenNavigationOptions == null
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options =
|
||||
typeof routeNavigationOptions === 'function' ||
|
||||
typeof screenNavigationOptions === 'function'
|
||||
? {
|
||||
navigation: createCompatNavigationProp<
|
||||
NavigationPropType
|
||||
>(navigation, route),
|
||||
navigationOptions: defaultNavigationOptions || {},
|
||||
screenProps,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...(typeof routeNavigationOptions === 'function'
|
||||
? routeNavigationOptions(options)
|
||||
: routeNavigationOptions),
|
||||
...(typeof screenNavigationOptions === 'function'
|
||||
? (screenNavigationOptions as (o: any) => ScreenOptions)(
|
||||
options
|
||||
)
|
||||
: screenNavigationOptions),
|
||||
} as ScreenOptions;
|
||||
};
|
||||
|
||||
return (
|
||||
<Pair.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
initialParams={initialParams}
|
||||
options={screenOptions}
|
||||
>
|
||||
{({ navigation, route }) => (
|
||||
<CompatScreen
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
component={getScreenComponent()}
|
||||
/>
|
||||
)}
|
||||
</Pair.Screen>
|
||||
);
|
||||
}),
|
||||
[screenProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenPropsContext.Provider value={screenProps}>
|
||||
<Pair.Navigator
|
||||
{...(restConfig as NavigationConfig)}
|
||||
screenOptions={defaultNavigationOptions}
|
||||
>
|
||||
{screens}
|
||||
</Pair.Navigator>
|
||||
</ScreenPropsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.navigationOtions = parentNavigationOptions;
|
||||
|
||||
return Navigator;
|
||||
};
|
||||
|
||||
createCompatNavigator.isCompat = true;
|
||||
|
||||
return createCompatNavigator;
|
||||
}
|
||||
28
packages/compat/src/createSwitchNavigator.tsx
Normal file
28
packages/compat/src/createSwitchNavigator.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigator,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/core';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
|
||||
|
||||
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
|
||||
|
||||
function SwitchNavigator(props: Props) {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
TabNavigationState,
|
||||
TabRouterOptions,
|
||||
{},
|
||||
{}
|
||||
>(TabRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigator<{}, typeof SwitchNavigator>(SwitchNavigator)
|
||||
);
|
||||
86
packages/compat/src/helpers.tsx
Normal file
86
packages/compat/src/helpers.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
|
||||
type NavigateActionPayload = Parameters<typeof NavigationActions.navigate>['0'];
|
||||
|
||||
type NavigateActionType = ReturnType<typeof NavigationActions.navigate>;
|
||||
|
||||
export function navigate(
|
||||
routeName: string,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType;
|
||||
export function navigate(options: NavigateActionPayload): NavigateActionType;
|
||||
export function navigate(
|
||||
options: string | NavigateActionPayload,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType {
|
||||
if (typeof options === 'string') {
|
||||
return NavigationActions.navigate({
|
||||
routeName: options,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
return NavigationActions.navigate(options);
|
||||
}
|
||||
|
||||
export function goBack(fromKey?: null | string) {
|
||||
return NavigationActions.back({ key: fromKey });
|
||||
}
|
||||
|
||||
export function setParams(params: object) {
|
||||
return NavigationActions.setParams({ params });
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
return StackActions.reset();
|
||||
}
|
||||
|
||||
export function replace(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.replace({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function push(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function pop(n: number) {
|
||||
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
||||
}
|
||||
|
||||
export function popToTop() {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss() {
|
||||
return StackActions.dismiss();
|
||||
}
|
||||
|
||||
export function jumpTo(routeName: string) {
|
||||
return SwitchActions.jumpTo({ routeName });
|
||||
}
|
||||
|
||||
export function openDrawer() {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer() {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
21
packages/compat/src/index.tsx
Normal file
21
packages/compat/src/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
|
||||
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
|
||||
|
||||
export {
|
||||
default as createCompatNavigatorFactory,
|
||||
} from './createCompatNavigatorFactory';
|
||||
|
||||
export {
|
||||
default as createCompatNavigationProp,
|
||||
} from './createCompatNavigationProp';
|
||||
|
||||
export { default as createSwitchNavigator } from './createSwitchNavigator';
|
||||
|
||||
export { default as withNavigation } from './withNavigation';
|
||||
export { default as withNavigationFocus } from './withNavigationFocus';
|
||||
|
||||
export * from './types';
|
||||
80
packages/compat/src/types.tsx
Normal file
80
packages/compat/src/types.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ParamListBase, NavigationProp, Route } from '@react-navigation/core';
|
||||
import * as helpers from './helpers';
|
||||
|
||||
export type CompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
RouteName extends Extract<keyof ParamList, string> = Extract<
|
||||
NavigationPropType extends NavigationProp<any, infer R> ? R : string,
|
||||
string
|
||||
>
|
||||
> = Omit<NavigationPropType, keyof typeof helpers> &
|
||||
{
|
||||
[method in Extract<keyof NavigationPropType, keyof typeof helpers>]: (
|
||||
...args: Parameters<typeof helpers[method]>
|
||||
) => void;
|
||||
} & {
|
||||
state: Route<RouteName> & {
|
||||
routeName: RouteName;
|
||||
};
|
||||
getParam<T extends keyof ParamList[RouteName]>(
|
||||
paramName: T,
|
||||
defaultValue?: ParamList[RouteName][T]
|
||||
): ParamList[RouteName][T];
|
||||
isFirstRouteInParent(): boolean;
|
||||
dangerouslyGetParent<
|
||||
T = NavigationProp<ParamListBase> | undefined
|
||||
>(): T extends NavigationProp<ParamListBase>
|
||||
? CompatNavigationProp<T>
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export type CompatNavigationOptions<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {}
|
||||
> =
|
||||
| ((options: {
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
navigationOptions: Partial<ScreenOptions>;
|
||||
screenProps: unknown;
|
||||
}) => ScreenOptions)
|
||||
| ScreenOptions;
|
||||
|
||||
export type CompatScreenType<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>
|
||||
> = React.ComponentType<{
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
screenProps: unknown;
|
||||
}> & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
};
|
||||
|
||||
export type CompatRouteConfig<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
> = {
|
||||
[RouteName in keyof ParamList]:
|
||||
| React.ComponentType<any>
|
||||
| ((
|
||||
| { screen: React.ComponentType<any> }
|
||||
| { getScreen(): React.ComponentType<any> }
|
||||
) & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
params?: ParamList[RouteName];
|
||||
});
|
||||
};
|
||||
25
packages/compat/src/useCompatNavigation.tsx
Normal file
25
packages/compat/src/useCompatNavigation.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
useNavigation,
|
||||
useRoute,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import { CompatNavigationProp } from './types';
|
||||
|
||||
export default function useCompatNavigation<
|
||||
T extends NavigationProp<ParamListBase>
|
||||
>() {
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
|
||||
return React.useMemo(
|
||||
() =>
|
||||
createCompatNavigationProp(
|
||||
navigation,
|
||||
route as any
|
||||
) as CompatNavigationProp<T>,
|
||||
[navigation, route]
|
||||
);
|
||||
}
|
||||
33
packages/compat/src/withNavigation.tsx
Normal file
33
packages/compat/src/withNavigation.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationProp, ParamListBase } from '@react-navigation/core';
|
||||
import useCompatNavigation from './useCompatNavigation';
|
||||
import { CompatNavigationProp } from './types';
|
||||
|
||||
type InjectedProps<T extends NavigationProp<ParamListBase>> = {
|
||||
navigation: CompatNavigationProp<T>;
|
||||
};
|
||||
|
||||
export default function withNavigation<
|
||||
T extends NavigationProp<ParamListBase>,
|
||||
P extends InjectedProps<T>,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps<T>> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const navigation = useCompatNavigation<T>();
|
||||
|
||||
// @ts-ignore
|
||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
30
packages/compat/src/withNavigationFocus.tsx
Normal file
30
packages/compat/src/withNavigationFocus.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import { useIsFocused } from '@react-navigation/core';
|
||||
|
||||
type InjectedProps = {
|
||||
isFocused: boolean;
|
||||
};
|
||||
|
||||
export default function withNavigationFocus<
|
||||
P extends InjectedProps,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
// @ts-ignore
|
||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
3
packages/compat/tsconfig.json
Normal file
3
packages/compat/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig"
|
||||
}
|
||||
@@ -3,6 +3,181 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.19...@react-navigation/core@5.0.0-alpha.20) (2019-11-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass rehydrated state in onStateChange and devtools ([5a34764](https://github.com/react-navigation/navigation-ex/commit/5a34764))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.18...@react-navigation/core@5.0.0-alpha.19) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop isFirstRouteInParent method ([#145](https://github.com/react-navigation/navigation-ex/issues/145)) ([3a77107](https://github.com/react-navigation/navigation-ex/commit/3a77107))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.17...@react-navigation/core@5.0.0-alpha.18) (2019-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve type annotation for screens ([8f16085](https://github.com/react-navigation/navigation-ex/commit/8f16085))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.16...@react-navigation/core@5.0.0-alpha.17) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.15...@react-navigation/core@5.0.0-alpha.16) (2019-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* rehydrate state before using it ([3e92e22](https://github.com/react-navigation/navigation-ex/commit/3e92e22))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make it easier to navigate to a specific route in navigator ([#114](https://github.com/react-navigation/navigation-ex/issues/114)) ([a543f1b](https://github.com/react-navigation/navigation-ex/commit/a543f1b)), closes [#90](https://github.com/react-navigation/navigation-ex/issues/90)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.14...@react-navigation/core@5.0.0-alpha.15) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.13...@react-navigation/core@5.0.0-alpha.14) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.12...@react-navigation/core@5.0.0-alpha.13) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.11...@react-navigation/core@5.0.0-alpha.12) (2019-10-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't merge state with existing state during reset. fixes [#111](https://github.com/react-navigation/navigation-ex/issues/111) ([7393464](https://github.com/react-navigation/navigation-ex/commit/7393464))
|
||||
* don't throw when switching navigator. fixes [#91](https://github.com/react-navigation/navigation-ex/issues/91) ([19be2b4](https://github.com/react-navigation/navigation-ex/commit/19be2b4))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a getRootState method ([#119](https://github.com/react-navigation/navigation-ex/issues/119)) ([7a5bcb4](https://github.com/react-navigation/navigation-ex/commit/7a5bcb4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.10...@react-navigation/core@5.0.0-alpha.11) (2019-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fire blur event when a route is removed with a delay ([1153d55](https://github.com/react-navigation/navigation-ex/commit/1153d55)), closes [#110](https://github.com/react-navigation/navigation-ex/issues/110)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a method to reset root navigator state ([e61f594](https://github.com/react-navigation/navigation-ex/commit/e61f594))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.9...@react-navigation/core@5.0.0-alpha.10) (2019-09-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.8...@react-navigation/core@5.0.0-alpha.9) (2019-09-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))
|
||||
* make deep link handling more flexible ([849d952](https://github.com/react-navigation/navigation-ex/commit/849d952))
|
||||
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.7...@react-navigation/core@5.0.0-alpha.8) (2019-09-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.7) (2019-08-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix navigation object changing too often ([3c840bb](https://github.com/react-navigation/navigation-ex/commit/3c840bb))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
|
||||
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.6) (2019-08-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
|
||||
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.4...@react-navigation/core@5.0.0-alpha.5) (2019-08-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"react-native",
|
||||
"react-navigation"
|
||||
],
|
||||
"version": "5.0.0-alpha.5",
|
||||
"version": "5.0.0-alpha.20",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,22 +29,24 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"shortid": "^2.2.14",
|
||||
"use-subscription": "^1.0.0"
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"query-string": "^6.8.3",
|
||||
"shortid": "^2.2.15",
|
||||
"use-subscription": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/core": "^7.6.2",
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
"@types/react": "^16.8.19",
|
||||
"@types/react": "^16.9.4",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"del-cli": "^2.0.0",
|
||||
"react": "^16.8.3",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.8.3",
|
||||
"react-native-testing-library": "^1.9.1",
|
||||
"react-test-renderer": "^16.8.3",
|
||||
"typescript": "^3.5.3"
|
||||
"react-test-renderer": "~16.8.3",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3"
|
||||
"react": "~16.8.3"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import shortid from 'shortid';
|
||||
import { CommonAction, NavigationState } from './types';
|
||||
import { CommonAction, NavigationState, PartialState } from './types';
|
||||
|
||||
/**
|
||||
* Base router object that can be used when writing custom routers.
|
||||
@@ -9,7 +9,7 @@ const BaseRouter = {
|
||||
getStateForAction<State extends NavigationState>(
|
||||
state: State,
|
||||
action: CommonAction
|
||||
): State | null {
|
||||
): State | PartialState<State> | null {
|
||||
switch (action.type) {
|
||||
case 'REPLACE': {
|
||||
const index = action.source
|
||||
@@ -20,14 +20,16 @@ const BaseRouter = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name, key, params } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.map((route, i) =>
|
||||
i === index
|
||||
? {
|
||||
key: `${action.payload.name}-${shortid()}`,
|
||||
name: action.payload.name,
|
||||
params: action.payload.params,
|
||||
key: key !== undefined ? key : `${name}-${shortid()}`,
|
||||
name,
|
||||
params,
|
||||
}
|
||||
: route
|
||||
),
|
||||
@@ -54,12 +56,7 @@ const BaseRouter = {
|
||||
}
|
||||
|
||||
case 'RESET':
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
key: state.key,
|
||||
routeNames: state.routeNames,
|
||||
};
|
||||
return action.payload as PartialState<State>;
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NavigationState, PartialState } from './types';
|
||||
|
||||
export type Action =
|
||||
@@ -9,15 +10,14 @@ export type Action =
|
||||
| {
|
||||
type: 'NAVIGATE';
|
||||
payload:
|
||||
| { name: string; key?: undefined; params?: object }
|
||||
| { key: string; name?: undefined; params?: object }
|
||||
| { key: string; name: string; params?: object };
|
||||
| { name: string; key?: string; params?: object };
|
||||
source?: string;
|
||||
target?: string;
|
||||
}
|
||||
| {
|
||||
type: 'REPLACE';
|
||||
payload: { name: string; params?: object };
|
||||
payload: { name: string; key?: string; params?: object };
|
||||
source?: string;
|
||||
target?: string;
|
||||
}
|
||||
@@ -41,8 +41,7 @@ export function goBack(): Action {
|
||||
export function navigate(
|
||||
route:
|
||||
| { key: string; params?: object }
|
||||
| { name: string; params?: object }
|
||||
| { name: string; key: string; params?: object }
|
||||
| { name: string; key?: string; params?: object }
|
||||
): Action;
|
||||
export function navigate(name: string, params?: object): Action;
|
||||
export function navigate(...args: any): Action {
|
||||
|
||||
@@ -18,25 +18,30 @@ export const SingleNavigatorContext = React.createContext<
|
||||
* Component which ensures that there's only one navigator nested under it.
|
||||
*/
|
||||
export default function EnsureSingleNavigator({ children }: Props) {
|
||||
const [currentKey, setCurrentKey] = React.useState<string | undefined>();
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
register(key: string) {
|
||||
const currentKey = navigatorKeyRef.current;
|
||||
|
||||
if (currentKey !== undefined && key !== currentKey) {
|
||||
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
|
||||
}
|
||||
|
||||
setCurrentKey(key);
|
||||
navigatorKeyRef.current = key;
|
||||
},
|
||||
unregister(key: string) {
|
||||
if (currentKey !== undefined && key !== currentKey) {
|
||||
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
|
||||
const currentKey = navigatorKeyRef.current;
|
||||
|
||||
if (key !== currentKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentKey(undefined);
|
||||
navigatorKeyRef.current = undefined;
|
||||
},
|
||||
}),
|
||||
[currentKey]
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationAction, NavigationHelpers, ParamListBase } from './types';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
} from './types';
|
||||
|
||||
export type ChildActionListener = (
|
||||
action: NavigationAction,
|
||||
@@ -14,6 +19,8 @@ export type FocusedNavigationListener = <T>(
|
||||
callback: FocusedNavigationCallback<T>
|
||||
) => { handled: boolean; result: T };
|
||||
|
||||
export type NavigatorStateGetter = () => NavigationState;
|
||||
|
||||
/**
|
||||
* Context which holds the required helpers needed to build nested navigators.
|
||||
*/
|
||||
@@ -25,6 +32,7 @@ const NavigationBuilderContext = React.createContext<{
|
||||
addActionListener?: (listener: ChildActionListener) => void;
|
||||
addFocusedListener?: (listener: FocusedNavigationListener) => void;
|
||||
onRouteFocus?: (key: string) => void;
|
||||
addStateGetter?: (key: string, getter: NavigatorStateGetter) => void;
|
||||
trackAction: (action: NavigationAction) => void;
|
||||
}>({
|
||||
trackAction: () => undefined,
|
||||
|
||||
@@ -2,8 +2,10 @@ import * as React from 'react';
|
||||
import * as CommonActions from './CommonActions';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import ResetRootContext from './ResetRootContext';
|
||||
import useFocusedListeners from './useFocusedListeners';
|
||||
import useDevTools from './useDevTools';
|
||||
import useStateGetters from './useStateGetters';
|
||||
|
||||
import {
|
||||
Route,
|
||||
@@ -23,7 +25,9 @@ const MISSING_CONTEXT_ERROR =
|
||||
export const NavigationStateContext = React.createContext<{
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
getState: () => NavigationState | PartialState<NavigationState> | undefined;
|
||||
setState: (state: NavigationState | undefined) => void;
|
||||
setState: (
|
||||
state: NavigationState | PartialState<NavigationState> | undefined
|
||||
) => void;
|
||||
key?: string;
|
||||
performTransaction: (action: () => void) => void;
|
||||
}>({
|
||||
@@ -86,46 +90,6 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
getPartialState(initialState)
|
||||
);
|
||||
|
||||
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
|
||||
|
||||
const dispatch = (action: NavigationAction) => {
|
||||
listeners[0](navigation => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
const { result, handled } = listeners[0](navigation =>
|
||||
navigation.canGoBack()
|
||||
);
|
||||
|
||||
if (handled) {
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
|
||||
any
|
||||
>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
dispatch(
|
||||
// eslint-disable-next-line import/namespace
|
||||
CommonActions[name](
|
||||
// @ts-ignore
|
||||
...args
|
||||
)
|
||||
);
|
||||
return acc;
|
||||
}, {}),
|
||||
resetRoot: (state: PartialState<NavigationState> | NavigationState) => {
|
||||
trackAction('@@RESET_ROOT');
|
||||
setNavigationState(state);
|
||||
},
|
||||
dispatch,
|
||||
canGoBack,
|
||||
}));
|
||||
|
||||
const navigationStateRef = React.useRef<State>();
|
||||
const transactionStateRef = React.useRef<State | null>(null);
|
||||
const isTransactionActiveRef = React.useRef<boolean>(false);
|
||||
@@ -143,12 +107,66 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
state,
|
||||
});
|
||||
|
||||
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
|
||||
|
||||
const { getStateForRoute, addStateGetter } = useStateGetters();
|
||||
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationAction)
|
||||
) => {
|
||||
listeners[0](navigation => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
const { result, handled } = listeners[0](navigation =>
|
||||
navigation.canGoBack()
|
||||
);
|
||||
|
||||
if (handled) {
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetRoot = React.useCallback(
|
||||
(state: PartialState<NavigationState> | NavigationState) => {
|
||||
trackAction('@@RESET_ROOT');
|
||||
setNavigationState(state);
|
||||
},
|
||||
[trackAction]
|
||||
);
|
||||
|
||||
const getRootState = React.useCallback(() => {
|
||||
return getStateForRoute('root');
|
||||
}, [getStateForRoute]);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
|
||||
any
|
||||
>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
dispatch(
|
||||
CommonActions[name](
|
||||
// @ts-ignore
|
||||
...args
|
||||
)
|
||||
);
|
||||
return acc;
|
||||
}, {}),
|
||||
resetRoot,
|
||||
dispatch,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
}));
|
||||
|
||||
const builderContext = React.useMemo(
|
||||
() => ({
|
||||
addFocusedListener,
|
||||
addStateGetter,
|
||||
trackAction,
|
||||
}),
|
||||
[addFocusedListener, trackAction]
|
||||
[addFocusedListener, trackAction, addStateGetter]
|
||||
);
|
||||
|
||||
const performTransaction = React.useCallback((callback: () => void) => {
|
||||
@@ -202,23 +220,25 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
if (skipTrackingRef.current) {
|
||||
skipTrackingRef.current = false;
|
||||
} else {
|
||||
trackState(state);
|
||||
trackState(getRootState);
|
||||
}
|
||||
|
||||
navigationStateRef.current = state;
|
||||
transactionStateRef.current = null;
|
||||
|
||||
if (!isFirstMountRef.current && onStateChange) {
|
||||
onStateChange(state);
|
||||
onStateChange(getRootState());
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
}, [state, onStateChange, trackState]);
|
||||
}, [state, onStateChange, trackState, getRootState]);
|
||||
|
||||
return (
|
||||
<NavigationBuilderContext.Provider value={builderContext}>
|
||||
<NavigationStateContext.Provider value={context}>
|
||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||
<ResetRootContext.Provider value={resetRoot}>
|
||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||
</ResetRootContext.Provider>
|
||||
</NavigationStateContext.Provider>
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
|
||||
11
packages/core/src/NavigationRouteContext.tsx
Normal file
11
packages/core/src/NavigationRouteContext.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { Route } from './types';
|
||||
|
||||
/**
|
||||
* Context which holds the route prop for a screen.
|
||||
*/
|
||||
const NavigationContext = React.createContext<Route<string> | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export default NavigationContext;
|
||||
15
packages/core/src/ResetRootContext.tsx
Normal file
15
packages/core/src/ResetRootContext.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState, PartialState } from './types';
|
||||
|
||||
/**
|
||||
* Context which holds the method to reset root navigator state.
|
||||
*/
|
||||
const ResetRootContext = React.createContext<
|
||||
(state: PartialState<NavigationState> | NavigationState) => void
|
||||
>(() => {
|
||||
throw new Error(
|
||||
"We couldn't find a way to reset root state. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
export default ResetRootContext;
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationStateContext } from './NavigationContainer';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import {
|
||||
@@ -18,8 +19,8 @@ type Props<State extends NavigationState, ScreenOptions extends object> = {
|
||||
route: Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
getState: () => State;
|
||||
setState: (state: State) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,7 @@ export default function SceneView<
|
||||
}, [getState, route.key]);
|
||||
|
||||
const setCurrentState = React.useCallback(
|
||||
(child: NavigationState | undefined) => {
|
||||
(child: NavigationState | PartialState<NavigationState> | undefined) => {
|
||||
const state = getState();
|
||||
|
||||
setState({
|
||||
@@ -56,7 +57,7 @@ export default function SceneView<
|
||||
),
|
||||
});
|
||||
},
|
||||
[getState, route, setState]
|
||||
[getState, route.key, setState]
|
||||
);
|
||||
|
||||
const context = React.useMemo(
|
||||
@@ -78,23 +79,27 @@ export default function SceneView<
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={navigation}>
|
||||
<NavigationStateContext.Provider value={context}>
|
||||
<EnsureSingleNavigator>
|
||||
<StaticContainer
|
||||
name={screen.name}
|
||||
// @ts-ignore
|
||||
render={screen.component || screen.children}
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
>
|
||||
{'component' in screen && screen.component !== undefined ? (
|
||||
<screen.component navigation={navigation} route={route} />
|
||||
) : 'children' in screen && screen.children !== undefined ? (
|
||||
screen.children({ navigation, route })
|
||||
) : null}
|
||||
</StaticContainer>
|
||||
</EnsureSingleNavigator>
|
||||
</NavigationStateContext.Provider>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<NavigationStateContext.Provider value={context}>
|
||||
<EnsureSingleNavigator>
|
||||
<StaticContainer
|
||||
name={screen.name}
|
||||
// @ts-ignore
|
||||
render={screen.component || screen.children}
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
>
|
||||
{'component' in screen && screen.component !== undefined ? (
|
||||
// @ts-ignore
|
||||
<screen.component navigation={navigation} route={route} />
|
||||
) : 'children' in screen && screen.children !== undefined ? (
|
||||
// @ts-ignore
|
||||
screen.children({ navigation, route })
|
||||
) : null}
|
||||
</StaticContainer>
|
||||
</EnsureSingleNavigator>
|
||||
</NavigationStateContext.Provider>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import * as CommonActions from '../CommonActions';
|
||||
jest.mock('shortid', () => () => 'test');
|
||||
|
||||
const STATE = {
|
||||
stale: false as false,
|
||||
stale: false as const,
|
||||
type: 'test',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routes: [
|
||||
@@ -23,6 +24,7 @@ it('replaces focused screen with REPLACE', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routes: [
|
||||
@@ -42,6 +44,7 @@ it('replaces source screen with REPLACE', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routes: [
|
||||
@@ -70,6 +73,7 @@ it('sets params for the focused screen with SET_PARAMS', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routes: [
|
||||
@@ -89,6 +93,7 @@ it('sets params for the source screen with SET_PARAMS', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routes: [
|
||||
@@ -125,15 +130,5 @@ it('resets state to new state with RESET', () => {
|
||||
})
|
||||
);
|
||||
|
||||
expect(result).toEqual({ ...STATE, index: 0, routes });
|
||||
});
|
||||
|
||||
it('ignores key and routeNames when resetting with RESET', () => {
|
||||
const result = BaseRouter.getStateForAction(
|
||||
STATE,
|
||||
// @ts-ignore
|
||||
CommonActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
|
||||
);
|
||||
|
||||
expect(result).toEqual({ ...STATE, index: 2 });
|
||||
expect(result).toEqual({ index: 0, routes });
|
||||
});
|
||||
|
||||
@@ -233,6 +233,7 @@ it('handle dispatching with ref', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'foo2', 'bar', 'baz'],
|
||||
@@ -242,6 +243,7 @@ it('handle dispatching with ref', () => {
|
||||
name: 'baz',
|
||||
state: {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['qux', 'lex'],
|
||||
@@ -319,5 +321,81 @@ it('handle resetting state with ref', () => {
|
||||
});
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith(state);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 1,
|
||||
key: '5',
|
||||
routeNames: ['foo', 'foo2', 'bar', 'baz'],
|
||||
routes: [
|
||||
{
|
||||
key: 'baz',
|
||||
name: 'baz',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '6',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('handle getRootState', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<NavigationContainer ref={ref}>
|
||||
<TestNavigator initialRouteName="foo">
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux" component={() => null} />
|
||||
<Screen name="lex" component={() => null} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={() => null} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
let state;
|
||||
if (ref.current) {
|
||||
state = ref.current.getRootState();
|
||||
}
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
key: '7',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '8',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ export const MockRouterKey = { current: 0 };
|
||||
|
||||
export default function MockRouter(options: DefaultRouterOptions) {
|
||||
const router: Router<NavigationState, MockActions> = {
|
||||
type: 'test',
|
||||
|
||||
getInitialState({ routeNames, routeParamList }) {
|
||||
const index =
|
||||
options.initialRouteName === undefined
|
||||
@@ -21,6 +23,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
|
||||
return {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: String(MockRouterKey.current++),
|
||||
index,
|
||||
routeNames,
|
||||
@@ -58,6 +61,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
|
||||
return {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: String(MockRouterKey.current++),
|
||||
index:
|
||||
typeof state.index === 'number' && state.index < routes.length
|
||||
@@ -108,7 +112,24 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...state, index };
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
routes:
|
||||
action.payload.params !== undefined
|
||||
? state.routes.map((route, i) =>
|
||||
i === index
|
||||
? {
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
...action.payload.params,
|
||||
},
|
||||
}
|
||||
: route
|
||||
)
|
||||
: state.routes,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import getPathFromState from '../getPathFromState';
|
||||
|
||||
it('converts path string to initial state', () => {
|
||||
it('converts state to path string', () => {
|
||||
expect(
|
||||
getPathFromState({
|
||||
routes: [
|
||||
@@ -12,11 +12,12 @@ it('converts path string to initial state', () => {
|
||||
{ name: 'boo' },
|
||||
{
|
||||
name: 'bar',
|
||||
params: { fruit: 'apple' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'baz qux',
|
||||
params: { author: 'jane & co', valid: true },
|
||||
params: { author: 'jane', valid: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -26,9 +27,50 @@ it('converts path string to initial state', () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
).toMatchInlineSnapshot(
|
||||
`"/foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true"`
|
||||
);
|
||||
).toMatchInlineSnapshot(`"/foo/bar/baz%20qux?author=jane&valid=true"`);
|
||||
});
|
||||
|
||||
it('converts state to path string with config', () => {
|
||||
expect(
|
||||
getPathFromState(
|
||||
{
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ name: 'boo' },
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet', avaliable: false },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
params: { author: 'Jane', valid: true, id: 10 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Foo: 'few',
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
stringify: {
|
||||
author: author => author.toLowerCase(),
|
||||
id: id => `x${id}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
).toMatchInlineSnapshot(`"/few/bar/sweet/apple/baz/jane?id=x10&valid=true"`);
|
||||
});
|
||||
|
||||
it('handles route without param', () => {
|
||||
|
||||
@@ -2,9 +2,7 @@ import getStateFromPath from '../getStateFromPath';
|
||||
|
||||
it('converts path string to initial state', () => {
|
||||
expect(
|
||||
getStateFromPath(
|
||||
'foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true'
|
||||
)
|
||||
getStateFromPath('foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
|
||||
).toEqual({
|
||||
routes: [
|
||||
{
|
||||
@@ -17,7 +15,55 @@ it('converts path string to initial state', () => {
|
||||
routes: [
|
||||
{
|
||||
name: 'baz qux',
|
||||
params: { author: 'jane & co', valid: true },
|
||||
params: { author: 'jane & co', valid: 'true' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('converts path string to initial state with config', () => {
|
||||
expect(
|
||||
getStateFromPath(
|
||||
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
|
||||
{
|
||||
Foo: 'few',
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -38,7 +84,7 @@ it('handles leading slash when converting', () => {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
params: { count: 42 },
|
||||
params: { count: '42' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -56,7 +102,7 @@ it('handles ending slash when converting', () => {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
params: { count: 42 },
|
||||
params: { count: '42' },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import NavigationContainer from '../NavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import useNavigation from '../useNavigation';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import { NavigationState } from '../types';
|
||||
import { NavigationState, NavigationContainerRef } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -52,6 +52,7 @@ it('initializes state for a navigator on navigation', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
@@ -106,6 +107,66 @@ it('rehydrates state for a navigator on navigation', () => {
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't rehydrate state if the type of state didn't match router", () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const FooScreen = (props: any) => {
|
||||
React.useEffect(() => {
|
||||
props.navigation.dispatch({ type: 'UPDATE' });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
index: 1,
|
||||
type: 'something-else',
|
||||
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
|
||||
};
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const element = (
|
||||
<NavigationContainer
|
||||
initialState={initialState}
|
||||
onStateChange={onStateChange}
|
||||
>
|
||||
<TestNavigator initialRouteName="foo">
|
||||
<Screen
|
||||
name="foo"
|
||||
component={FooScreen}
|
||||
initialParams={{ answer: 42 }}
|
||||
/>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(element).update(element);
|
||||
|
||||
expect(onStateChange).lastCalledWith({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,6 +205,7 @@ it('initializes state for nested screens in React.Fragment', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
@@ -194,6 +256,7 @@ it('initializes state for nested navigator on navigation', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 2,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
@@ -205,6 +268,7 @@ it('initializes state for nested navigator on navigation', () => {
|
||||
name: 'baz',
|
||||
state: {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['qux'],
|
||||
@@ -309,6 +373,7 @@ it('cleans up state when the navigator unmounts', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -323,7 +388,7 @@ it('cleans up state when the navigator unmounts', () => {
|
||||
expect(onStateChange).lastCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('allows arbitrary state updates by dispatching a function', () => {
|
||||
it('allows state updates by dispatching a function returning an action', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
@@ -332,11 +397,11 @@ it('allows arbitrary state updates by dispatching a function', () => {
|
||||
|
||||
const FooScreen = (props: any) => {
|
||||
React.useEffect(() => {
|
||||
props.navigation.dispatch((state: any) => ({
|
||||
...state,
|
||||
routes: state.routes.slice().reverse(),
|
||||
index: 1,
|
||||
}));
|
||||
props.navigation.dispatch((state: NavigationState) =>
|
||||
state.index === 0
|
||||
? { type: 'NAVIGATE', payload: { name: state.routeNames[1] } }
|
||||
: { type: 'NOOP' }
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -361,10 +426,11 @@ it('allows arbitrary state updates by dispatching a function', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [{ key: 'bar', name: 'bar' }, { key: 'foo', name: 'foo' }],
|
||||
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -399,6 +465,7 @@ it('updates route params with setParams', () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -413,6 +480,7 @@ it('updates route params with setParams', () => {
|
||||
expect(onStateChange).toBeCalledTimes(2);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -466,10 +534,23 @@ it('updates route params with setParams applied to parent', () => {
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
params: { username: 'alice' },
|
||||
state: {
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['baz'],
|
||||
routes: [{ key: 'baz', name: 'baz' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
|
||||
act(() => setParams({ age: 25 }));
|
||||
@@ -480,10 +561,23 @@ it('updates route params with setParams applied to parent', () => {
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
params: { username: 'alice', age: 25 },
|
||||
state: {
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['baz'],
|
||||
routes: [{ key: 'baz', name: 'baz' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -516,6 +610,7 @@ it('handles change in route names', () => {
|
||||
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'baz', 'qux'],
|
||||
@@ -523,6 +618,80 @@ it('handles change in route names', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates to nested child in a navigator', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestComponent = ({ route }: any): any =>
|
||||
`[${route.name}, ${JSON.stringify(route.params)}]`;
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = render(
|
||||
<NavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo-a" component={TestComponent} />
|
||||
<Screen name="foo-b" component={TestComponent} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestComponent}
|
||||
initialParams={{ lol: 'why' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestComponent}
|
||||
initialParams={{ some: 'stuff' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
navigation.current &&
|
||||
navigation.current.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
);
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||
);
|
||||
|
||||
act(
|
||||
() =>
|
||||
navigation.current &&
|
||||
navigation.current.navigate('bar', {
|
||||
screen: 'bar-a',
|
||||
params: { whoa: 'test' },
|
||||
})
|
||||
);
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
});
|
||||
|
||||
it('gives access to internal state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -554,6 +723,7 @@ it('gives access to internal state', () => {
|
||||
routeNames: ['bar'],
|
||||
routes: [{ key: 'bar', name: 'bar' }],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -655,6 +825,7 @@ it('throws when a React Element is not the direct children', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("doesn't throw when direct children is Screen or empty element", () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
@@ -694,3 +865,30 @@ it('throws when multiple screens with same name are defined', () => {
|
||||
"A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named 'foo')"
|
||||
);
|
||||
});
|
||||
|
||||
it('switches rendered navigators', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator key="a">
|
||||
<Screen name="foo" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
root.update(
|
||||
<NavigationContainer>
|
||||
<TestNavigator key="b">
|
||||
<Screen name="foo" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
)
|
||||
).not.toThrowError(
|
||||
'Another navigator is already registered for this container.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -46,11 +46,320 @@ it('sets options with options prop as an object', () => {
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1>
|
||||
Hello world
|
||||
</h1>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
it('sets options with options prop as a fuction', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = (): any => 'Test screen';
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen
|
||||
name="foo"
|
||||
component={TestScreen}
|
||||
options={({ route }: any) => ({ title: route.params.author })}
|
||||
initialParams={{ author: 'Jane' }}
|
||||
/>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1>
|
||||
Jane
|
||||
</h1>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
it('sets options with screenOptions prop as an object', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
<main key={route.key}>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreenA = (): any => 'Test screen A';
|
||||
|
||||
const TestScreenB = (): any => 'Test screen B';
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator screenOptions={{ title: 'Hello world' }}>
|
||||
<Screen name="foo" component={TestScreenA} />
|
||||
<Screen name="bar" component={TestScreenB} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
<main>
|
||||
<h1>
|
||||
Hello world
|
||||
</h1>
|
||||
<div>
|
||||
Test screen A
|
||||
</div>
|
||||
</main>,
|
||||
<main>
|
||||
<h1>
|
||||
Hello world
|
||||
</h1>
|
||||
<div>
|
||||
Test screen B
|
||||
</div>
|
||||
</main>,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('sets options with screenOptions prop as a fuction', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
<main key={route.key}>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreenA = (): any => 'Test screen A';
|
||||
|
||||
const TestScreenB = (): any => 'Test screen B';
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator
|
||||
screenOptions={({ route }: any) => ({
|
||||
title: `${route.name}: ${route.params.author || route.params.fruit}`,
|
||||
})}
|
||||
>
|
||||
<Screen
|
||||
name="foo"
|
||||
component={TestScreenA}
|
||||
initialParams={{ author: 'Jane' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar"
|
||||
component={TestScreenB}
|
||||
initialParams={{ fruit: 'Apple' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
<main>
|
||||
<h1>
|
||||
foo: Jane
|
||||
</h1>
|
||||
<div>
|
||||
Test screen A
|
||||
</div>
|
||||
</main>,
|
||||
<main>
|
||||
<h1>
|
||||
bar: Apple
|
||||
</h1>
|
||||
<div>
|
||||
Test screen B
|
||||
</div>
|
||||
</main>,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('sets initial options with setOptions', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{
|
||||
title?: string;
|
||||
color?: string;
|
||||
},
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1 color={options.color}>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
navigation.setOptions({
|
||||
title: 'Hello world',
|
||||
});
|
||||
|
||||
return 'Test screen';
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1
|
||||
color="blue"
|
||||
>
|
||||
Hello world
|
||||
</h1>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
it('updates options with setOptions', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1 color={options.color}>{options.title}</h1>
|
||||
<p>{options.description}</p>
|
||||
<caption>{options.author}</caption>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
navigation.setOptions({
|
||||
title: 'Hello world',
|
||||
description: 'Something here',
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() =>
|
||||
navigation.setOptions({
|
||||
title: 'Hello again',
|
||||
author: 'Jane',
|
||||
})
|
||||
);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
|
||||
return 'Test screen';
|
||||
};
|
||||
|
||||
const element = (
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
const root = render(element);
|
||||
|
||||
act(() => jest.runAllTimers());
|
||||
|
||||
root.update(element);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1>
|
||||
Hello world
|
||||
<h1
|
||||
color="blue"
|
||||
>
|
||||
Hello again
|
||||
</h1>
|
||||
<p>
|
||||
Something here
|
||||
</p>
|
||||
<caption>
|
||||
Jane
|
||||
</caption>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
@@ -295,180 +604,3 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('sets options with options prop as a fuction', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = (): any => 'Test screen';
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen
|
||||
name="foo"
|
||||
component={TestScreen}
|
||||
options={({ route }: any) => ({ title: route.params.author })}
|
||||
initialParams={{ author: 'Jane' }}
|
||||
/>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1>
|
||||
Jane
|
||||
</h1>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
it('sets initial options with setOptions', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{
|
||||
title?: string;
|
||||
color?: string;
|
||||
},
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1 color={options.color}>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
navigation.setOptions({
|
||||
title: 'Hello world',
|
||||
});
|
||||
|
||||
return 'Test screen';
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1
|
||||
color="blue"
|
||||
>
|
||||
Hello world
|
||||
</h1>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
it('updates options with setOptions', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1 color={options.color}>{options.title}</h1>
|
||||
<p>{options.description}</p>
|
||||
<caption>{options.author}</caption>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
navigation.setOptions({
|
||||
title: 'Hello world',
|
||||
description: 'Something here',
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() =>
|
||||
navigation.setOptions({
|
||||
title: 'Hello again',
|
||||
author: 'Jane',
|
||||
})
|
||||
);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
|
||||
return 'Test screen';
|
||||
};
|
||||
|
||||
const element = (
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
const root = render(element);
|
||||
|
||||
act(() => jest.runAllTimers());
|
||||
|
||||
root.update(element);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`
|
||||
<main>
|
||||
<h1
|
||||
color="blue"
|
||||
>
|
||||
Hello again
|
||||
</h1>
|
||||
<p>
|
||||
Something here
|
||||
</p>
|
||||
<caption>
|
||||
Jane
|
||||
</caption>
|
||||
<div>
|
||||
Test screen
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter from './__fixtures__/MockRouter';
|
||||
import { Router, NavigationState } from '../types';
|
||||
|
||||
it('fires focus and blur events in root navigator', () => {
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
@@ -200,6 +201,136 @@ it('fires focus and blur events in nested navigator', () => {
|
||||
expect(thirdFocusCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires blur event when a route is removed with a delay', async () => {
|
||||
const TestRouter = (options: any): Router<NavigationState, any> => {
|
||||
const router = MockRouter(options);
|
||||
|
||||
return {
|
||||
...router,
|
||||
|
||||
getInitialState({ routeNames, routeParamList }) {
|
||||
const initialRouteName =
|
||||
options.initialRouteName !== undefined
|
||||
? options.initialRouteName
|
||||
: routeNames[0];
|
||||
|
||||
return {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
key: 'stack',
|
||||
index: 0,
|
||||
routeNames,
|
||||
routes: [
|
||||
{
|
||||
key: initialRouteName,
|
||||
name: initialRouteName,
|
||||
params: routeParamList[initialRouteName],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
getStateForAction(state, action) {
|
||||
switch (action.type) {
|
||||
case 'PUSH':
|
||||
return {
|
||||
...state,
|
||||
index: state.index + 1,
|
||||
routes: [...state.routes, action.payload],
|
||||
};
|
||||
case 'POP': {
|
||||
const routes = state.routes.slice(0, -1);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return router.getStateForAction(state, action);
|
||||
}
|
||||
},
|
||||
|
||||
actionCreators: {
|
||||
push(payload) {
|
||||
return { type: 'PUSH', payload };
|
||||
},
|
||||
|
||||
pop() {
|
||||
return { type: 'POP' };
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||
TestRouter,
|
||||
props
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||
|
||||
const [previous, dispatch] = React.useReducer(
|
||||
(state, action) => {
|
||||
if (state.routes !== action.routes) {
|
||||
return { ...state, ...action };
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
{ routes: state.routes, descriptors }
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch({ routes: state.routes, descriptors });
|
||||
}, [descriptors, state.routes]);
|
||||
|
||||
return previous.routes.map((route: any) =>
|
||||
previous.descriptors[route.key].render()
|
||||
);
|
||||
});
|
||||
|
||||
const blurCallback = jest.fn();
|
||||
|
||||
const First = () => null;
|
||||
|
||||
const Second = ({ navigation }: any) => {
|
||||
React.useEffect(() => navigation.addListener('blur', blurCallback), [
|
||||
navigation,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<NavigationContainer>
|
||||
<TestNavigator ref={navigation}>
|
||||
<Screen name="first" component={First} />
|
||||
<Screen name="second" component={Second} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
act(() =>
|
||||
navigation.current.push({
|
||||
name: 'second',
|
||||
key: 'second',
|
||||
})
|
||||
);
|
||||
|
||||
expect(blurCallback).toBeCalledTimes(0);
|
||||
|
||||
act(() => navigation.current.pop());
|
||||
|
||||
expect(blurCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires custom events', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ it("lets parent handle the action if child didn't", () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 2,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
@@ -191,6 +192,7 @@ it("lets children handle the action if parent didn't", () => {
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).lastCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
@@ -200,6 +202,7 @@ it("lets children handle the action if parent didn't", () => {
|
||||
name: 'baz',
|
||||
state: {
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['qux', 'lex'],
|
||||
@@ -296,6 +299,7 @@ it("action doesn't bubble if target is specified", () => {
|
||||
expect(onStateChange).not.toBeCalled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("doesn't crash if no navigator handled the action", () => {
|
||||
const TestRouter = MockRouter;
|
||||
|
||||
|
||||
34
packages/core/src/__tests__/useRoute.test.tsx
Normal file
34
packages/core/src/__tests__/useRoute.test.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import useRoute from '../useRoute';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter from './__fixtures__/MockRouter';
|
||||
import { RouteProp } from '../types';
|
||||
|
||||
it('gets route prop from context', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
|
||||
|
||||
expect(route && route.params && route.params.x).toEqual(1);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={Test} initialParams={{ x: 1 }} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
});
|
||||
@@ -1,14 +1,45 @@
|
||||
import queryString from 'query-string';
|
||||
import { NavigationState, PartialState, Route } from './types';
|
||||
|
||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||
|
||||
type StringifyConfig = Record<string, (value: any) => string>;
|
||||
|
||||
type Options = {
|
||||
[routeName: string]: string | { path: string; stringify?: StringifyConfig };
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to serialize a navigation state object to a path string.
|
||||
*
|
||||
* Example:
|
||||
* ```js
|
||||
* getPathFromState(
|
||||
* {
|
||||
* routes: [
|
||||
* {
|
||||
* name: 'Chat',
|
||||
* params: { author: 'Jane', id: 42 },
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* {
|
||||
* Chat: {
|
||||
* path: 'chat/:author/:id',
|
||||
* stringify: { author: author => author.toLowerCase() }
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @param state Navigation state to serialize.
|
||||
* @param options Extra options to fine-tune how to serialize the path.
|
||||
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
||||
*/
|
||||
export default function getPathFromState(state: State): string {
|
||||
export default function getPathFromState(
|
||||
state: State,
|
||||
options: Options = {}
|
||||
): string {
|
||||
let path = '/';
|
||||
|
||||
let current: State | undefined = state;
|
||||
@@ -19,24 +50,51 @@ export default function getPathFromState(state: State): string {
|
||||
state?: State | undefined;
|
||||
};
|
||||
|
||||
path += encodeURIComponent(route.name);
|
||||
const config =
|
||||
options[route.name] !== undefined
|
||||
? (options[route.name] as { stringify?: StringifyConfig }).stringify
|
||||
: undefined;
|
||||
|
||||
const params = route.params
|
||||
? // Stringify all of the param values before we use them
|
||||
Object.entries(route.params).reduce<{
|
||||
[key: string]: string;
|
||||
}>((acc, [key, value]) => {
|
||||
acc[key] = config && config[key] ? config[key](value) : String(value);
|
||||
return acc;
|
||||
}, {})
|
||||
: undefined;
|
||||
|
||||
if (options[route.name] !== undefined) {
|
||||
const pattern =
|
||||
typeof options[route.name] === 'string'
|
||||
? (options[route.name] as string)
|
||||
: (options[route.name] as { path: string }).path;
|
||||
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map(p => {
|
||||
const name = p.replace(/^:/, '');
|
||||
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
if (params && name in params && p.startsWith(':')) {
|
||||
const value = params[name];
|
||||
// Remove the used value from the params object since we'll use the rest for query string
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
}
|
||||
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} else if (route.params) {
|
||||
const query = [];
|
||||
|
||||
for (const param in route.params) {
|
||||
const value = (route.params as { [key: string]: any })[param];
|
||||
|
||||
query.push(
|
||||
`${encodeURIComponent(param)}=${encodeURIComponent(
|
||||
JSON.stringify(value)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
path += `?${query.join('&')}`;
|
||||
} else if (params) {
|
||||
path += `?${queryString.stringify(params)}`;
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
|
||||
@@ -1,50 +1,149 @@
|
||||
import escape from 'escape-string-regexp';
|
||||
import queryString from 'query-string';
|
||||
import { NavigationState, PartialState } from './types';
|
||||
|
||||
type ParseConfig = Record<string, (value: string) => any>;
|
||||
|
||||
type Options = {
|
||||
[routeName: string]: string | { path: string; parse?: ParseConfig };
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to parse a path string to initial state object accepted by the container.
|
||||
* This is useful for deep linking when we need to handle the incoming URL.
|
||||
*
|
||||
* Example:
|
||||
* ```js
|
||||
* getStateFromPath(
|
||||
* '/chat/jane/42',
|
||||
* {
|
||||
* Chat: {
|
||||
* path: 'chat/:author/:id',
|
||||
* parse: { id: Number }
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
|
||||
* @param options Extra options to fine-tune how to parse the path.
|
||||
*/
|
||||
export default function getStateFromPath(
|
||||
path: string
|
||||
path: string,
|
||||
options: Options = {}
|
||||
): PartialState<NavigationState> | undefined {
|
||||
const parts = path.split('?');
|
||||
const segments = parts[0].split('/').filter(Boolean);
|
||||
const query = parts[1] ? parts[1].split('&') : undefined;
|
||||
// Create a normalized config array which will be easier to use
|
||||
const routeConfig = Object.keys(options).map(key => {
|
||||
const pattern =
|
||||
typeof options[key] === 'string'
|
||||
? (options[key] as string)
|
||||
: (options[key] as { path: string }).path;
|
||||
|
||||
// Create a regex from the provided path pattern
|
||||
// With the pattern, we can match segements containing params and extract them
|
||||
const match = new RegExp(
|
||||
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
|
||||
);
|
||||
|
||||
return {
|
||||
match,
|
||||
pattern,
|
||||
routeName: key,
|
||||
// @ts-ignore
|
||||
parse: options[key].parse,
|
||||
};
|
||||
});
|
||||
|
||||
let result: PartialState<NavigationState> | undefined;
|
||||
let current: PartialState<NavigationState> | undefined;
|
||||
|
||||
while (segments.length) {
|
||||
let remaining = path
|
||||
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
|
||||
.replace(/^\//, '') // Remove extra leading slash
|
||||
.replace(/\?.*/, ''); // Remove query params which we will handle later
|
||||
|
||||
while (remaining) {
|
||||
let routeName;
|
||||
let params;
|
||||
|
||||
// Go through all configs, and see if the next path segment matches our regex
|
||||
for (const config of routeConfig) {
|
||||
const match = remaining.match(config.match);
|
||||
|
||||
// If our regex matches, we need to extract params from the path
|
||||
if (match) {
|
||||
routeName = config.routeName;
|
||||
|
||||
const paramPatterns = config.pattern
|
||||
.split('/')
|
||||
.filter(p => p.startsWith(':'));
|
||||
|
||||
if (paramPatterns.length) {
|
||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||
const key = p.replace(/^:/, '');
|
||||
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
|
||||
|
||||
acc[key] =
|
||||
config.parse && config.parse[key]
|
||||
? config.parse[key](value)
|
||||
: value;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Remove the matched segment from the remaining path
|
||||
remaining = remaining.replace(match[0], '');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we hadn't matched any segments earlier, use the path as route name
|
||||
if (routeName === undefined) {
|
||||
const segments = remaining.split('/');
|
||||
|
||||
routeName = decodeURIComponent(segments[0]);
|
||||
segments.shift();
|
||||
remaining = segments.join('/');
|
||||
}
|
||||
|
||||
const state = {
|
||||
routes: [{ name: decodeURIComponent(segments[0]) }],
|
||||
routes: [{ name: routeName, params }],
|
||||
};
|
||||
|
||||
if (current) {
|
||||
// The state should be nested inside the route we parsed before
|
||||
current.routes[0].state = state;
|
||||
} else {
|
||||
result = state;
|
||||
}
|
||||
|
||||
current = state;
|
||||
segments.shift();
|
||||
}
|
||||
|
||||
if (current == null || result == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const query = path.split('?')[1];
|
||||
|
||||
if (query) {
|
||||
const params = query.reduce<{ [key: string]: any }>((acc, curr) => {
|
||||
const [key, value] = curr.split('=');
|
||||
const route = current.routes[0];
|
||||
|
||||
acc[decodeURIComponent(key)] = JSON.parse(decodeURIComponent(value));
|
||||
const params = queryString.parse(query);
|
||||
const config = options[route.name]
|
||||
? (options[route.name] as { parse?: ParseConfig }).parse
|
||||
: undefined;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
if (config) {
|
||||
Object.keys(params).forEach(name => {
|
||||
if (config[name] && typeof params[name] === 'string') {
|
||||
params[name] = config[name](params[name] as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
current.routes[0].params = params;
|
||||
route.params = { ...route.params, ...params };
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -7,9 +7,11 @@ export { default as NavigationContainer } from './NavigationContainer';
|
||||
export { default as createNavigator } from './createNavigator';
|
||||
|
||||
export { default as NavigationContext } from './NavigationContext';
|
||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||
|
||||
export { default as useNavigationBuilder } from './useNavigationBuilder';
|
||||
export { default as useNavigation } from './useNavigation';
|
||||
export { default as useRoute } from './useRoute';
|
||||
export { default as useFocusEffect } from './useFocusEffect';
|
||||
export { default as useIsFocused } from './useIsFocused';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import * as CommonActions from './CommonActions';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -22,6 +23,12 @@ export type NavigationState = {
|
||||
routes: Array<
|
||||
Route<string> & { state?: NavigationState | PartialState<NavigationState> }
|
||||
>;
|
||||
/**
|
||||
* Custom type for the state, whether it's for tab, stack, drawer etc.
|
||||
* During rehydration, the state will be discarded if type doesn't match with router type.
|
||||
* It can also be used to detect the type of the navigator we're dealing with.
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Whether the navigation state has been rehydrated.
|
||||
*/
|
||||
@@ -35,9 +42,10 @@ export type InitialState = Partial<
|
||||
};
|
||||
|
||||
export type PartialState<State extends NavigationState> = Partial<
|
||||
Omit<State, 'stale' | 'key' | 'routes' | 'routeNames'>
|
||||
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
|
||||
> & {
|
||||
stale?: true;
|
||||
type?: string;
|
||||
routes: Array<
|
||||
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState }
|
||||
>;
|
||||
@@ -96,7 +104,12 @@ export type DefaultNavigatorOptions<
|
||||
/**
|
||||
* Default options for all screens under this navigator.
|
||||
*/
|
||||
screenOptions?: ScreenOptions;
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
};
|
||||
|
||||
export type RouterFactory<
|
||||
@@ -109,6 +122,12 @@ export type Router<
|
||||
State extends NavigationState,
|
||||
Action extends NavigationAction
|
||||
> = {
|
||||
/**
|
||||
* Type of the router. Should match the `type` property in state.
|
||||
* If the type doesn't match, the state will be discarded during rehydration.
|
||||
*/
|
||||
type: State['type'];
|
||||
|
||||
/**
|
||||
* Initialize the navigation state.
|
||||
*
|
||||
@@ -165,7 +184,10 @@ export type Router<
|
||||
* @param state State object to apply the action on.
|
||||
* @param action Action object to apply.
|
||||
*/
|
||||
getStateForAction(state: State, action: Action): State | null;
|
||||
getStateForAction(
|
||||
state: State,
|
||||
action: Action
|
||||
): State | PartialState<State> | null;
|
||||
|
||||
/**
|
||||
* Whether the action should also change focus in parent navigator
|
||||
@@ -180,7 +202,7 @@ export type Router<
|
||||
actionCreators?: ActionCreators<Action>;
|
||||
};
|
||||
|
||||
export type ParamListBase = { [key: string]: object | undefined };
|
||||
export type ParamListBase = Record<string, object | undefined>;
|
||||
|
||||
export type EventMapBase = {
|
||||
focus: undefined;
|
||||
@@ -206,7 +228,7 @@ export type EventListenerCallback<EventName extends string, Data> = (
|
||||
e: EventArg<EventName, Data>
|
||||
) => void;
|
||||
|
||||
export type EventConsumer<EventMap extends { [key: string]: any }> = {
|
||||
export type EventConsumer<EventMap extends Record<string, any>> = {
|
||||
/**
|
||||
* Subscribe to events from the parent navigator.
|
||||
*
|
||||
@@ -223,7 +245,7 @@ export type EventConsumer<EventMap extends { [key: string]: any }> = {
|
||||
): void;
|
||||
};
|
||||
|
||||
export type EventEmitter<EventMap extends { [key: string]: any }> = {
|
||||
export type EventEmitter<EventMap extends Record<string, any>> = {
|
||||
/**
|
||||
* Emit an event to child screens.
|
||||
*
|
||||
@@ -265,7 +287,9 @@ type NavigationHelpersCommon<
|
||||
*
|
||||
* @param action Action object or update function.
|
||||
*/
|
||||
dispatch(action: NavigationAction | ((state: State) => State)): void;
|
||||
dispatch(
|
||||
action: NavigationAction | ((state: State) => NavigationAction)
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Navigate to a route in current navigation tree.
|
||||
@@ -309,6 +333,13 @@ type NavigationHelpersCommon<
|
||||
*/
|
||||
reset(state: PartialState<State> | State): void;
|
||||
|
||||
/**
|
||||
* Reset the navigation state of the root navigator to the provided state.
|
||||
*
|
||||
* @param state Navigation state object.
|
||||
*/
|
||||
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
|
||||
|
||||
/**
|
||||
* Go back to the previous route in history.
|
||||
*/
|
||||
@@ -331,7 +362,7 @@ type NavigationHelpersCommon<
|
||||
|
||||
export type NavigationHelpers<
|
||||
ParamList extends ParamListBase,
|
||||
EventMap extends { [key: string]: any } = {}
|
||||
EventMap extends Record<string, any> = {}
|
||||
> = NavigationHelpersCommon<ParamList> &
|
||||
EventEmitter<EventMap> & {
|
||||
/**
|
||||
@@ -358,7 +389,7 @@ export type NavigationProp<
|
||||
RouteName extends keyof ParamList = string,
|
||||
State extends NavigationState = NavigationState,
|
||||
ScreenOptions extends object = {},
|
||||
EventMap extends { [key: string]: any } = {}
|
||||
EventMap extends Record<string, any> = {}
|
||||
> = NavigationHelpersCommon<ParamList, State> & {
|
||||
/**
|
||||
* Update the param object for the route.
|
||||
@@ -376,20 +407,12 @@ export type NavigationProp<
|
||||
*/
|
||||
setOptions(options: Partial<ScreenOptions>): void;
|
||||
|
||||
/**
|
||||
* Check if the screen is the first route in the navigator.
|
||||
* This method returns `true` if the index of the route is `0`, `false` otherwise.
|
||||
* It can be useful to decide whether to display a back button in a stack.
|
||||
*/
|
||||
isFirstRouteInParent(): boolean;
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent():
|
||||
| NavigationProp<ParamListBase, string, any, any>
|
||||
| undefined;
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
@@ -451,7 +474,7 @@ export type Descriptor<
|
||||
RouteName extends keyof ParamList = string,
|
||||
State extends NavigationState = NavigationState,
|
||||
ScreenOptions extends object = {},
|
||||
EventMap extends { [key: string]: any } = {}
|
||||
EventMap extends Record<string, any> = {}
|
||||
> = {
|
||||
/**
|
||||
* Render the component associated with this route.
|
||||
@@ -504,13 +527,19 @@ export type RouteConfig<
|
||||
/**
|
||||
* React component to render for this screen.
|
||||
*/
|
||||
component: React.ComponentType<any>;
|
||||
component: React.ComponentType<{
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
navigation: any;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Render callback to render content of this screen.
|
||||
*/
|
||||
children: (props: any) => React.ReactNode;
|
||||
children: (props: {
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
navigation: any;
|
||||
}) => React.ReactNode;
|
||||
});
|
||||
|
||||
export type NavigationContainerRef =
|
||||
@@ -521,6 +550,7 @@ export type NavigationContainerRef =
|
||||
* @param state Navigation state object.
|
||||
*/
|
||||
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
|
||||
getRootState(): NavigationState;
|
||||
}
|
||||
| undefined
|
||||
| null;
|
||||
@@ -534,11 +564,24 @@ export type TypedNavigator<
|
||||
* Navigator component which manages the child screens.
|
||||
*/
|
||||
Navigator: React.ComponentType<
|
||||
React.ComponentProps<Navigator> & {
|
||||
Omit<
|
||||
React.ComponentProps<Navigator>,
|
||||
'initialRouteName' | 'screenOptions'
|
||||
> & {
|
||||
/**
|
||||
* Route to focus on initial render.
|
||||
* Name of the route to focus by on initial render.
|
||||
* If not specified, usually the first route is used.
|
||||
*/
|
||||
initialRouteName?: keyof ParamList;
|
||||
/**
|
||||
* Default options for all screens under this navigator.
|
||||
*/
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
}
|
||||
>;
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ import SceneView from './SceneView';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
FocusedNavigationListener,
|
||||
NavigatorStateGetter,
|
||||
} from './NavigationBuilderContext';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import useNavigationCache from './useNavigationCache';
|
||||
@@ -13,24 +14,31 @@ import {
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
RouteConfig,
|
||||
RouteProp,
|
||||
Router,
|
||||
} from './types';
|
||||
|
||||
type Options<ScreenOptions extends object> = {
|
||||
state: NavigationState;
|
||||
screens: { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> };
|
||||
type Options<State extends NavigationState, ScreenOptions extends object> = {
|
||||
state: State;
|
||||
screens: Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
screenOptions?: ScreenOptions;
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
onAction: (
|
||||
action: NavigationAction,
|
||||
visitedNavigators?: Set<string>
|
||||
) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
getState: () => State;
|
||||
setState: (state: State) => void;
|
||||
addActionListener: (listener: ChildActionListener) => void;
|
||||
addFocusedListener: (listener: FocusedNavigationListener) => void;
|
||||
addStateGetter: (key: string, getter: NavigatorStateGetter) => void;
|
||||
onRouteFocus: (key: string) => void;
|
||||
router: Router<NavigationState, NavigationAction>;
|
||||
router: Router<State, NavigationAction>;
|
||||
emitter: NavigationEventEmitter;
|
||||
};
|
||||
|
||||
@@ -55,11 +63,12 @@ export default function useDescriptors<
|
||||
setState,
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
addStateGetter,
|
||||
onRouteFocus,
|
||||
router,
|
||||
emitter,
|
||||
}: Options<ScreenOptions>) {
|
||||
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
|
||||
}: Options<State, ScreenOptions>) {
|
||||
const [options, setOptions] = React.useState<Record<string, object>>({});
|
||||
const { trackAction } = React.useContext(NavigationBuilderContext);
|
||||
|
||||
const context = React.useMemo(
|
||||
@@ -68,6 +77,7 @@ export default function useDescriptors<
|
||||
onAction,
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
addStateGetter,
|
||||
onRouteFocus,
|
||||
trackAction,
|
||||
}),
|
||||
@@ -77,6 +87,7 @@ export default function useDescriptors<
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
onRouteFocus,
|
||||
addStateGetter,
|
||||
trackAction,
|
||||
]
|
||||
);
|
||||
@@ -93,13 +104,15 @@ export default function useDescriptors<
|
||||
return state.routes.reduce(
|
||||
(acc, route) => {
|
||||
const screen = screens[route.name];
|
||||
const navigation = navigations[route.key];
|
||||
|
||||
acc[route.key] = {
|
||||
navigation,
|
||||
render() {
|
||||
return (
|
||||
<NavigationBuilderContext.Provider key={route.key} value={context}>
|
||||
<SceneView
|
||||
navigation={navigations[route.key]}
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
screen={screen}
|
||||
getState={getState}
|
||||
@@ -108,22 +121,30 @@ export default function useDescriptors<
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
},
|
||||
options: {
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...screenOptions,
|
||||
// The `options` prop passed to `Screen` elements
|
||||
...(typeof screen.options === 'object' || screen.options == null
|
||||
? screen.options
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
navigation: navigations[route.key],
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
...options[route.key],
|
||||
get options() {
|
||||
return {
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...(typeof screenOptions === 'object' || screenOptions == null
|
||||
? screenOptions
|
||||
: screenOptions({
|
||||
// @ts-ignore
|
||||
route,
|
||||
navigation,
|
||||
})),
|
||||
// The `options` prop passed to `Screen` elements
|
||||
...(typeof screen.options === 'object' || screen.options == null
|
||||
? screen.options
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
navigation,
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
...options[route.key],
|
||||
};
|
||||
},
|
||||
navigation: navigations[route.key],
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as {
|
||||
|
||||
@@ -18,6 +18,7 @@ type DevTools = {
|
||||
};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
__REDUX_DEVTOOLS_EXTENSION__:
|
||||
@@ -61,7 +62,7 @@ export default function useDevTools({ name, reset, state }: Options) {
|
||||
);
|
||||
|
||||
const trackState = React.useCallback(
|
||||
(state: State) => {
|
||||
(getState: () => State) => {
|
||||
if (!devTools) {
|
||||
return;
|
||||
}
|
||||
@@ -70,9 +71,11 @@ export default function useDevTools({ name, reset, state }: Options) {
|
||||
devTools.send(actions.current.shift(), lastStateRef.current);
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
|
||||
if (actions.current.length) {
|
||||
devTools.send(actions.current.pop(), state);
|
||||
} else if (lastStateRef.current !== state) {
|
||||
} else {
|
||||
devTools.send('@@UNKNOWN', state);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { EventEmitter, EventConsumer, EventArg } from './types';
|
||||
|
||||
export type NavigationEventEmitter = EventEmitter<{ [key: string]: any }> & {
|
||||
create: (target: string) => EventConsumer<{ [key: string]: any }>;
|
||||
export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
|
||||
create: (target: string) => EventConsumer<Record<string, any>>;
|
||||
};
|
||||
|
||||
type Listeners = Array<(data: any) => void>;
|
||||
@@ -11,9 +11,7 @@ type Listeners = Array<(data: any) => void>;
|
||||
* Hook to manage the event system used by the navigator to notify screens of various events.
|
||||
*/
|
||||
export default function useEventEmitter(): NavigationEventEmitter {
|
||||
const listeners = React.useRef<{
|
||||
[key: string]: { [key: string]: Listeners };
|
||||
}>({});
|
||||
const listeners = React.useRef<Record<string, Record<string, Listeners>>>({});
|
||||
|
||||
const create = React.useCallback((target: string) => {
|
||||
const removeListener = (type: string, callback: (data: any) => void) => {
|
||||
|
||||
@@ -57,19 +57,19 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.routes.forEach((route, i) => {
|
||||
if (
|
||||
lastFocusedKey === undefined ||
|
||||
(route.key !== lastFocusedKey && route.key !== currentFocusedKey)
|
||||
) {
|
||||
// Only fire events after mount, or if focus state of this route changed
|
||||
return;
|
||||
}
|
||||
if (lastFocusedKey === undefined) {
|
||||
// Only fire events after initial mount
|
||||
return;
|
||||
}
|
||||
|
||||
emitter.emit({
|
||||
type: i === state.index ? 'focus' : 'blur',
|
||||
target: route.key,
|
||||
});
|
||||
emitter.emit({
|
||||
type: 'focus',
|
||||
target: currentFocusedKey,
|
||||
});
|
||||
}, [currentFocusedKey, emitter, navigation, state.index, state.routes]);
|
||||
|
||||
emitter.emit({
|
||||
type: 'blur',
|
||||
target: lastFocusedKey,
|
||||
});
|
||||
}, [currentFocusedKey, emitter, navigation]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationStateContext } from './NavigationContainer';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import Screen from './Screen';
|
||||
import { navigate } from './CommonActions';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useRegisterNavigator from './useRegisterNavigator';
|
||||
import useDescriptors from './useDescriptors';
|
||||
@@ -23,11 +25,20 @@ import {
|
||||
PrivateValueStore,
|
||||
NavigationAction,
|
||||
} from './types';
|
||||
import useStateGetters from './useStateGetters';
|
||||
import useOnGetState from './useOnGetState';
|
||||
|
||||
// This is to make TypeScript compiler happy
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
PrivateValueStore;
|
||||
|
||||
type NavigatorRoute = {
|
||||
params?: {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two arrays with primitive values as the content.
|
||||
* We need to make sure that both values and order match.
|
||||
@@ -87,39 +98,66 @@ export default function useNavigationBuilder<
|
||||
State extends NavigationState,
|
||||
RouterOptions extends DefaultRouterOptions,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends { [key: string]: any }
|
||||
EventMap extends Record<string, any>
|
||||
>(
|
||||
createRouter: RouterFactory<State, any, RouterOptions>,
|
||||
options: DefaultNavigatorOptions<ScreenOptions> & RouterOptions
|
||||
) {
|
||||
useRegisterNavigator();
|
||||
|
||||
const route = React.useContext(NavigationRouteContext) as (
|
||||
| NavigatorRoute
|
||||
| undefined);
|
||||
|
||||
const previousRouteRef = React.useRef(route);
|
||||
|
||||
React.useEffect(() => {
|
||||
previousRouteRef.current = route;
|
||||
}, [route]);
|
||||
|
||||
const { children, ...rest } = options;
|
||||
const { current: router } = React.useRef<Router<State, any>>(
|
||||
createRouter((rest as unknown) as RouterOptions)
|
||||
createRouter({
|
||||
...((rest as unknown) as RouterOptions),
|
||||
...(route && route.params && typeof route.params.screen === 'string'
|
||||
? { initialRouteName: route.params.screen }
|
||||
: null),
|
||||
})
|
||||
);
|
||||
|
||||
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce(
|
||||
(acc, curr) => {
|
||||
if (curr.name in acc) {
|
||||
throw new Error(
|
||||
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${curr.name}')`
|
||||
);
|
||||
}
|
||||
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce<
|
||||
Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>
|
||||
>((acc, curr) => {
|
||||
if (curr.name in acc) {
|
||||
throw new Error(
|
||||
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${curr.name}')`
|
||||
);
|
||||
}
|
||||
|
||||
acc[curr.name] = curr;
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> }
|
||||
);
|
||||
acc[curr.name] = curr;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const routeNames = Object.keys(screens);
|
||||
const routeParamList = routeNames.reduce(
|
||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||
(acc, curr) => {
|
||||
acc[curr] = screens[curr].initialParams;
|
||||
const { initialParams } = screens[curr];
|
||||
const initialParamsFromParams =
|
||||
route && route.params && route.params.screen === curr
|
||||
? route.params.params
|
||||
: undefined;
|
||||
|
||||
acc[curr] =
|
||||
initialParams !== undefined || initialParamsFromParams !== undefined
|
||||
? {
|
||||
...initialParams,
|
||||
...initialParamsFromParams,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: object | undefined }
|
||||
{}
|
||||
);
|
||||
|
||||
if (!routeNames.length) {
|
||||
@@ -128,6 +166,17 @@ export default function useNavigationBuilder<
|
||||
);
|
||||
}
|
||||
|
||||
const isStateValid = React.useCallback(
|
||||
state => state.type === undefined || state.type === router.type,
|
||||
[router.type]
|
||||
);
|
||||
|
||||
const isStateInitialized = React.useCallback(
|
||||
state =>
|
||||
state !== undefined && state.stale === false && isStateValid(state),
|
||||
[isStateValid]
|
||||
);
|
||||
|
||||
const {
|
||||
state: currentState,
|
||||
getState: getCurrentState,
|
||||
@@ -150,7 +199,7 @@ export default function useNavigationBuilder<
|
||||
// Otherwise assume that the state was provided as initial state
|
||||
// So we need to rehydrate it to make it usable
|
||||
initializedStateRef.current =
|
||||
currentState === undefined
|
||||
currentState === undefined || !isStateValid(currentState)
|
||||
? router.getInitialState({
|
||||
routeNames,
|
||||
routeParamList,
|
||||
@@ -169,32 +218,57 @@ export default function useNavigationBuilder<
|
||||
// If the state isn't initialized, or stale, use the state we initialized instead
|
||||
// The state won't update until there's a change needed in the state we have initalized locally
|
||||
// So it'll be `undefined` or stale untill the first navigation event happens
|
||||
currentState === undefined || currentState.stale !== false
|
||||
? (initializedStateRef.current as State)
|
||||
: (currentState as State);
|
||||
isStateInitialized(currentState)
|
||||
? (currentState as State)
|
||||
: (initializedStateRef.current as State);
|
||||
|
||||
let nextState: State = state;
|
||||
|
||||
if (!isArrayEqual(state.routeNames, routeNames)) {
|
||||
// When the list of route names change, the router should handle it to remove invalid routes
|
||||
const nextState = router.getStateForRouteNamesChange(state, {
|
||||
nextState = router.getStateForRouteNamesChange(state, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
});
|
||||
|
||||
if (state !== nextState) {
|
||||
// If the state needs to be updated, we'll schedule an update with React
|
||||
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
|
||||
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
|
||||
performTransaction(() => {
|
||||
setState(nextState);
|
||||
});
|
||||
}
|
||||
|
||||
// The up-to-date state will come in next render, but we don't need to wait for it
|
||||
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
|
||||
// So we override the state objec we return to use the latest state as soon as possible
|
||||
state = nextState;
|
||||
}
|
||||
|
||||
if (
|
||||
previousRouteRef.current &&
|
||||
route &&
|
||||
route.params &&
|
||||
typeof route.params.screen === 'string' &&
|
||||
route.params !== previousRouteRef.current.params
|
||||
) {
|
||||
// If the route was updated with new name and/or params, we should navigate there
|
||||
// The update should be limited to current navigator only, so we call the router manually
|
||||
const updatedState = router.getStateForAction(
|
||||
state,
|
||||
navigate(route.params.screen, route.params.params)
|
||||
);
|
||||
|
||||
nextState =
|
||||
updatedState !== null
|
||||
? router.getRehydratedState(updatedState, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
})
|
||||
: state;
|
||||
}
|
||||
|
||||
if (state !== nextState) {
|
||||
// If the state needs to be updated, we'll schedule an update with React
|
||||
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
|
||||
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
|
||||
performTransaction(() => {
|
||||
setState(nextState);
|
||||
});
|
||||
}
|
||||
|
||||
// The up-to-date state will come in next render, but we don't need to wait for it
|
||||
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
|
||||
// So we override the state objec we return to use the latest state as soon as possible
|
||||
state = nextState;
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
// We need to clean up state for this navigator on unmount
|
||||
@@ -208,10 +282,10 @@ export default function useNavigationBuilder<
|
||||
const getState = React.useCallback((): State => {
|
||||
const currentState = getCurrentState();
|
||||
|
||||
return currentState === undefined || currentState.stale !== false
|
||||
? (initializedStateRef.current as State)
|
||||
: (currentState as State);
|
||||
}, [getCurrentState]);
|
||||
return isStateInitialized(currentState)
|
||||
? (currentState as State)
|
||||
: (initializedStateRef.current as State);
|
||||
}, [getCurrentState, isStateInitialized]);
|
||||
|
||||
const emitter = useEventEmitter();
|
||||
|
||||
@@ -227,6 +301,8 @@ export default function useNavigationBuilder<
|
||||
addListener: addFocusedListener,
|
||||
} = useFocusedListeners();
|
||||
|
||||
const { getStateForRoute, addStateGetter } = useStateGetters();
|
||||
|
||||
const onAction = useOnAction({
|
||||
router,
|
||||
getState,
|
||||
@@ -245,7 +321,6 @@ export default function useNavigationBuilder<
|
||||
const navigation = useNavigationHelpers<State, NavigationAction, EventMap>({
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
emitter,
|
||||
router,
|
||||
});
|
||||
@@ -255,6 +330,11 @@ export default function useNavigationBuilder<
|
||||
focusedListeners,
|
||||
});
|
||||
|
||||
useOnGetState({
|
||||
getState,
|
||||
getStateForRoute,
|
||||
});
|
||||
|
||||
const descriptors = useDescriptors<State, ScreenOptions>({
|
||||
state,
|
||||
screens,
|
||||
@@ -266,6 +346,7 @@ export default function useNavigationBuilder<
|
||||
onRouteFocus,
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
addStateGetter,
|
||||
router,
|
||||
emitter,
|
||||
});
|
||||
|
||||
@@ -12,15 +12,15 @@ import {
|
||||
Router,
|
||||
} from './types';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState;
|
||||
getState: () => NavigationState;
|
||||
type Options<State extends NavigationState> = {
|
||||
state: State;
|
||||
getState: () => State;
|
||||
navigation: NavigationHelpers<ParamListBase> &
|
||||
Partial<NavigationProp<ParamListBase, string, any, any, any>>;
|
||||
setOptions: (
|
||||
cb: (options: { [key: string]: object }) => { [key: string]: object }
|
||||
cb: (options: Record<string, object>) => Record<string, object>
|
||||
) => void;
|
||||
router: Router<NavigationState, NavigationAction>;
|
||||
router: Router<State, NavigationAction>;
|
||||
emitter: NavigationEventEmitter;
|
||||
};
|
||||
|
||||
@@ -39,7 +39,14 @@ type NavigationCache<
|
||||
export default function useNavigationCache<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object
|
||||
>({ state, getState, navigation, setOptions, router, emitter }: Options) {
|
||||
>({
|
||||
state,
|
||||
getState,
|
||||
navigation,
|
||||
setOptions,
|
||||
router,
|
||||
emitter,
|
||||
}: Options<State>) {
|
||||
// Cache object which holds navigation objects for each screen
|
||||
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
||||
// In reality, these deps will rarely change, if ever
|
||||
@@ -59,40 +66,42 @@ export default function useNavigationCache<
|
||||
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
||||
(acc, route, index) => {
|
||||
const previous = cache.current[route.key];
|
||||
const isFirst = route.key === state.routes[0].key;
|
||||
|
||||
if (previous && previous.isFirstRouteInParent() === isFirst) {
|
||||
// If a cached navigation object already exists and has same `isFirstRouteInParent`, reuse it
|
||||
// This method could return different result if the index of the route changes somehow
|
||||
if (previous) {
|
||||
// If a cached navigation object already exists, reuse it
|
||||
acc[route.key] = previous;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { emit, ...rest } = navigation;
|
||||
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: State) => State)
|
||||
) =>
|
||||
navigation.dispatch(
|
||||
typeof action === 'object' && action != null
|
||||
? { source: route.key, ...action }
|
||||
: action
|
||||
);
|
||||
action: NavigationAction | ((state: State) => NavigationAction)
|
||||
) => {
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
|
||||
const helpers = Object.keys(actions).reduce(
|
||||
navigation.dispatch(
|
||||
typeof payload === 'object' && payload != null
|
||||
? { source: route.key, ...payload }
|
||||
: payload
|
||||
);
|
||||
};
|
||||
|
||||
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
||||
(acc, name) => {
|
||||
// @ts-ignore
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: () => void }
|
||||
{}
|
||||
);
|
||||
|
||||
acc[route.key] = {
|
||||
...rest,
|
||||
...helpers,
|
||||
...emitter.create(route.key),
|
||||
dangerouslyGetParent: () => parentNavigation,
|
||||
dangerouslyGetState: getState as () => State,
|
||||
dangerouslyGetParent: () => parentNavigation as any,
|
||||
dangerouslyGetState: getState,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions(o => ({
|
||||
@@ -110,7 +119,6 @@ export default function useNavigationCache<
|
||||
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
||||
return navigation ? navigation.isFocused() : true;
|
||||
},
|
||||
isFirstRouteInParent: () => isFirst,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import * as CommonActions from './CommonActions';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import ResetRootContext from './ResetRootContext';
|
||||
import { NavigationStateContext } from './NavigationContainer';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import {
|
||||
@@ -23,7 +24,6 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
|
||||
visitedNavigators?: Set<string>
|
||||
) => boolean;
|
||||
getState: () => State;
|
||||
setState: (state: State) => void;
|
||||
emitter: NavigationEventEmitter;
|
||||
router: Router<State, Action>;
|
||||
};
|
||||
@@ -35,19 +35,19 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
|
||||
export default function useNavigationHelpers<
|
||||
State extends NavigationState,
|
||||
Action extends NavigationAction,
|
||||
EventMap extends { [key: string]: any }
|
||||
>({ onAction, getState, setState, emitter, router }: Options<State, Action>) {
|
||||
EventMap extends Record<string, any>
|
||||
>({ onAction, getState, emitter, router }: Options<State, Action>) {
|
||||
const resetRoot = React.useContext(ResetRootContext);
|
||||
const parentNavigationHelpers = React.useContext(NavigationContext);
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
|
||||
return React.useMemo(() => {
|
||||
const dispatch = (action: Action | ((state: State) => State)) =>
|
||||
const dispatch = (action: Action | ((state: State) => Action)) =>
|
||||
performTransaction(() => {
|
||||
if (typeof action === 'function') {
|
||||
setState(action(getState()));
|
||||
} else {
|
||||
onAction(action);
|
||||
}
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
|
||||
onAction(payload);
|
||||
});
|
||||
|
||||
const actions = {
|
||||
@@ -55,18 +55,19 @@ export default function useNavigationHelpers<
|
||||
...CommonActions,
|
||||
};
|
||||
|
||||
const helpers = Object.keys(actions).reduce(
|
||||
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
||||
(acc, name) => {
|
||||
// @ts-ignore
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: () => void }
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
...parentNavigationHelpers,
|
||||
...helpers,
|
||||
resetRoot,
|
||||
dispatch,
|
||||
emit: emitter.emit,
|
||||
isFocused: parentNavigationHelpers
|
||||
@@ -85,9 +86,9 @@ export default function useNavigationHelpers<
|
||||
router,
|
||||
getState,
|
||||
parentNavigationHelpers,
|
||||
resetRoot,
|
||||
emitter.emit,
|
||||
performTransaction,
|
||||
setState,
|
||||
onAction,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@ import * as React from 'react';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
} from './NavigationBuilderContext';
|
||||
import { NavigationAction, NavigationState, Router } from './types';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
PartialState,
|
||||
Router,
|
||||
} from './types';
|
||||
|
||||
type Options = {
|
||||
router: Router<NavigationState, NavigationAction>;
|
||||
key?: string;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
setState: (state: NavigationState | PartialState<NavigationState>) => void;
|
||||
listeners: ChildActionListener[];
|
||||
};
|
||||
|
||||
|
||||
31
packages/core/src/useOnGetState.tsx
Normal file
31
packages/core/src/useOnGetState.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import { NavigationState } from './types';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
|
||||
export default function useOnGetState({
|
||||
getStateForRoute,
|
||||
getState,
|
||||
}: {
|
||||
getStateForRoute: (routeName: string) => NavigationState | undefined;
|
||||
getState: () => NavigationState;
|
||||
}) {
|
||||
const { addStateGetter } = React.useContext(NavigationBuilderContext);
|
||||
const route = React.useContext(NavigationRouteContext);
|
||||
const key = route ? route.key : 'root';
|
||||
|
||||
const getRehydratedState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.map(route => ({
|
||||
...route,
|
||||
state: getStateForRoute(route.key),
|
||||
})),
|
||||
};
|
||||
}, [getState, getStateForRoute]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return addStateGetter && addStateGetter(key, getRehydratedState);
|
||||
}, [addStateGetter, getRehydratedState, key]);
|
||||
}
|
||||
22
packages/core/src/useRoute.tsx
Normal file
22
packages/core/src/useRoute.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import { ParamListBase, RouteProp } from './types';
|
||||
|
||||
/**
|
||||
* Hook to access the route prop of the parent screen anywhere.
|
||||
*
|
||||
* @returns Route prop of the parent screen.
|
||||
*/
|
||||
export default function useRoute<
|
||||
T extends RouteProp<ParamListBase, string>
|
||||
>(): T {
|
||||
const route = React.useContext(NavigationRouteContext);
|
||||
|
||||
if (route === undefined) {
|
||||
throw new Error(
|
||||
"We couldn't find a route object. Is your component inside a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return route as T;
|
||||
}
|
||||
35
packages/core/src/useStateGetters.tsx
Normal file
35
packages/core/src/useStateGetters.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import { NavigatorStateGetter } from './NavigationBuilderContext';
|
||||
|
||||
/**
|
||||
* Hook which lets child navigators add getters to be called for obtaining rehydrated state.
|
||||
*/
|
||||
|
||||
export default function useStateGetters() {
|
||||
const stateGetters = React.useRef<Record<string, NavigatorStateGetter>>({});
|
||||
|
||||
const getStateForRoute = React.useCallback(
|
||||
(routeKey: string) =>
|
||||
stateGetters.current[routeKey] === undefined
|
||||
? undefined
|
||||
: stateGetters.current[routeKey](),
|
||||
[stateGetters]
|
||||
);
|
||||
|
||||
const addStateGetter = React.useCallback(
|
||||
(key: string, getter: NavigatorStateGetter) => {
|
||||
stateGetters.current[key] = getter;
|
||||
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
stateGetters.current[key] = undefined;
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
getStateForRoute,
|
||||
addStateGetter,
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,139 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.19...@react-navigation/drawer@5.0.0-alpha.20) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.18...@react-navigation/drawer@5.0.0-alpha.19) (2019-11-02)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.17...@react-navigation/drawer@5.0.0-alpha.18) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hide screen from screen reader when drawer is open ([#147](https://github.com/react-navigation/navigation-ex/issues/147)) ([fb749ac](https://github.com/react-navigation/navigation-ex/commit/fb749ac))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an 'unmountInactiveScreens' option ([12d597f](https://github.com/react-navigation/navigation-ex/commit/12d597f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.16...@react-navigation/drawer@5.0.0-alpha.17) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.15...@react-navigation/drawer@5.0.0-alpha.16) (2019-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* navigation drawer sometimes not closing when pressed outside ([0d8cdc8](https://github.com/react-navigation/navigation-ex/commit/0d8cdc8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.14...@react-navigation/drawer@5.0.0-alpha.15) (2019-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix passing content options in drawer ([cab6160](https://github.com/react-navigation/navigation-ex/commit/cab6160))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.13...@react-navigation/drawer@5.0.0-alpha.14) (2019-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add flex: 1 to drawer content ([2b57702](https://github.com/react-navigation/navigation-ex/commit/2b57702))
|
||||
* fix content component not rendering in drawer ([0a5fb3e](https://github.com/react-navigation/navigation-ex/commit/0a5fb3e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.12...@react-navigation/drawer@5.0.0-alpha.13) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.11...@react-navigation/drawer@5.0.0-alpha.12) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.10...@react-navigation/drawer@5.0.0-alpha.11) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.9...@react-navigation/drawer@5.0.0-alpha.10) (2019-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export some more type aliases ([8b78d61](https://github.com/react-navigation/navigation-ex/commit/8b78d61))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.8...@react-navigation/drawer@5.0.0-alpha.9) (2019-09-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.7...@react-navigation/drawer@5.0.0-alpha.8) (2019-08-31)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.6...@react-navigation/drawer@5.0.0-alpha.7) (2019-08-30)
|
||||
|
||||
|
||||
|
||||
@@ -10,44 +10,29 @@ Open a Terminal in your project's folder and run,
|
||||
yarn add @react-navigation/core @react-navigation/drawer
|
||||
```
|
||||
|
||||
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated).
|
||||
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
|
||||
|
||||
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
|
||||
|
||||
```sh
|
||||
expo install react-native-gesture-handler react-native-reanimated
|
||||
expo install react-native-gesture-handler react-native-reanimated react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are not using Expo, run the following:
|
||||
|
||||
```sh
|
||||
yarn add react-native-reanimated react-native-gesture-handler
|
||||
yarn add react-native-reanimated react-native-gesture-handler react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are using Expo, you are done. Otherwise, continue to the next steps.
|
||||
|
||||
Next, we need to link these libraries. The steps depends on your React Native version:
|
||||
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
|
||||
|
||||
- **React Native 0.60 and higher**
|
||||
|
||||
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md).
|
||||
|
||||
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
|
||||
|
||||
```sh
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
- **React Native 0.59**
|
||||
|
||||
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
|
||||
|
||||
```sh
|
||||
react-native link react-native-reanimated
|
||||
react-native link react-native-gesture-handler
|
||||
```
|
||||
```sh
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"material",
|
||||
"drawer"
|
||||
],
|
||||
"version": "5.0.0-alpha.7",
|
||||
"version": "5.0.0-alpha.20",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,20 +34,20 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.6",
|
||||
"react-native-safe-area-view": "^0.14.6"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
"@types/react": "^16.8.24",
|
||||
"@types/react-native": "^0.60.2",
|
||||
"del-cli": "^2.0.0",
|
||||
"react": "16.8.3",
|
||||
"react-native": "^0.59.8",
|
||||
"@types/react": "^16.9.4",
|
||||
"@types/react-native": "^0.60.17",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.8.3",
|
||||
"react-native": "~0.59.10",
|
||||
"react-native-gesture-handler": "^1.3.0",
|
||||
"react-native-reanimated": "^1.1.0",
|
||||
"react-native-screens": "^1.0.0-alpha.22",
|
||||
"typescript": "^3.5.3"
|
||||
"react-native-reanimated": "^1.3.0",
|
||||
"react-native-safe-area-context": "^0.3.6",
|
||||
"react-native-screens": "^2.0.0-alpha.7",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/core": "^5.0.0-alpha.0",
|
||||
@@ -55,7 +55,8 @@
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "^1.0.0",
|
||||
"react-native-reanimated": "^1.0.0",
|
||||
"react-native-screens": "^1.0.0-alpha.0"
|
||||
"react-native-safe-area-context": "^0.3.6",
|
||||
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -8,8 +8,9 @@ export {
|
||||
/**
|
||||
* Views
|
||||
*/
|
||||
export { default as DrawerNavigatorItems } from './views/DrawerNavigatorItems';
|
||||
export { default as DrawerSidebar } from './views/DrawerSidebar';
|
||||
export { default as DrawerItem } from './views/DrawerItem';
|
||||
export { default as DrawerItemList } from './views/DrawerItemList';
|
||||
export { default as DrawerContent } from './views/DrawerContent';
|
||||
export { default as DrawerView } from './views/DrawerView';
|
||||
|
||||
/**
|
||||
@@ -20,4 +21,9 @@ export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
export { DrawerNavigationOptions, DrawerNavigationProp } from './types';
|
||||
export {
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationProp,
|
||||
DrawerContentOptions,
|
||||
DrawerContentComponentProps,
|
||||
} from './types';
|
||||
|
||||
@@ -12,16 +12,11 @@ import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
|
||||
export type Scene = {
|
||||
route: Route<string>;
|
||||
index: number;
|
||||
focused: boolean;
|
||||
tintColor?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type DrawerNavigationConfig = {
|
||||
/**
|
||||
* Custom background color for the drawer. Defaults to `white`.
|
||||
*/
|
||||
drawerBackgroundColor: string;
|
||||
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
/**
|
||||
* Position of the drawer on the screen. Defaults to `left`.
|
||||
*/
|
||||
@@ -33,11 +28,6 @@ export type DrawerNavigationConfig = {
|
||||
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
|
||||
*/
|
||||
drawerType: 'front' | 'back' | 'slide';
|
||||
/**
|
||||
* Number or a function which returns the width of the drawer.
|
||||
* If a function is provided, it'll be called again when the screen's dimensions change.
|
||||
*/
|
||||
drawerWidth: number | (() => number);
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
*/
|
||||
@@ -77,54 +67,56 @@ export type DrawerNavigationConfig = {
|
||||
* Whether a screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
unmountInactiveRoutes?: boolean;
|
||||
unmountInactiveScreens?: boolean;
|
||||
/**
|
||||
* Custom component used to render as the content of the drawer, for example, navigation items.
|
||||
* Defaults to `DrawerItems`.
|
||||
* Function that returns React element to render as the content of the drawer, for example, navigation items.
|
||||
* Defaults to `DrawerContent`.
|
||||
*/
|
||||
contentComponent: React.ComponentType<ContentComponentProps>;
|
||||
drawerContent: (props: DrawerContentComponentProps<T>) => React.ReactNode;
|
||||
/**
|
||||
* Options for the content component which will be passed as props.
|
||||
*/
|
||||
contentOptions?: object;
|
||||
drawerContentOptions?: T;
|
||||
/**
|
||||
* Style object for the component wrapping the screen content.
|
||||
*/
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object for the drawer component.
|
||||
* You can pass a custom background color for a drawer or a custom width here.
|
||||
*/
|
||||
drawerStyle?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type DrawerNavigationOptions = {
|
||||
title?: string;
|
||||
drawerLabel?:
|
||||
| string
|
||||
| ((props: { tintColor?: string; focused: boolean }) => React.ReactElement);
|
||||
| ((props: { color: string; focused: boolean }) => React.ReactNode);
|
||||
drawerIcon?: (props: {
|
||||
tintColor?: string;
|
||||
color: string;
|
||||
size: number;
|
||||
focused: boolean;
|
||||
}) => React.ReactElement;
|
||||
}) => React.ReactNode;
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open';
|
||||
};
|
||||
|
||||
export type ContentComponentProps = DrawerNavigationItemsProps & {
|
||||
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
|
||||
state: DrawerNavigationState;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
descriptors: { [key: string]: any };
|
||||
descriptors: DrawerDescriptorMap;
|
||||
/**
|
||||
* Animated node which represents the current progress of the drawer's open state.
|
||||
* `0` is closed, `1` is open.
|
||||
*/
|
||||
drawerOpenProgress: Animated.Node<number>;
|
||||
progress: Animated.Node<number>;
|
||||
/**
|
||||
* Position of the drawer on the screen.
|
||||
*/
|
||||
drawerPosition: 'left' | 'right';
|
||||
};
|
||||
|
||||
export type DrawerNavigationItemsProps = {
|
||||
/**
|
||||
* The array of routes, can be modified or overridden to control what's shown in the drawer.
|
||||
*/
|
||||
items: Route<string>[];
|
||||
/**
|
||||
* Route key identifying the currently active route.
|
||||
*/
|
||||
activeItemKey?: string | null;
|
||||
export type DrawerContentOptions = {
|
||||
/**
|
||||
* Color for the icon and label in the active item in the drawer.
|
||||
*/
|
||||
@@ -141,37 +133,22 @@ export type DrawerNavigationItemsProps = {
|
||||
* Background color for the inactive items in the drawer.
|
||||
*/
|
||||
inactiveBackgroundColor?: string;
|
||||
/**
|
||||
* Style object for the content section.
|
||||
*/
|
||||
itemsContainerStyle?: ViewStyle;
|
||||
/**
|
||||
* Style object for the single item, which can contain an icon and/or a label.
|
||||
*/
|
||||
itemStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object to overwrite `Text` style inside content section which renders a label.
|
||||
* Style object to apply to the `Text` inside content section which renders a label.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object to overwrite `Text` style of the active label.
|
||||
* Style object for the content section.
|
||||
*/
|
||||
activeLabelStyle?: StyleProp<TextStyle>;
|
||||
contentContainerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object to overwrite `Text` style of the inactive label.
|
||||
* Style object for the wrapper view.
|
||||
*/
|
||||
inactiveLabelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the wrapper `View` of the icon.
|
||||
*/
|
||||
iconContainerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Position of the drawer on the screen.
|
||||
*/
|
||||
drawerPosition: 'left' | 'right';
|
||||
getLabel: (scene: Scene) => React.ReactNode;
|
||||
renderIcon: (scene: Scene) => React.ReactNode;
|
||||
onItemPress: (scene: { route: Route<string>; focused: boolean }) => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type DrawerNavigationEventMap = {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Keyboard,
|
||||
StatusBar,
|
||||
StyleProp,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
@@ -428,7 +429,14 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
x: this.touchX,
|
||||
translationX: this.gestureX,
|
||||
velocityX: this.velocityX,
|
||||
state: this.gestureState,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
private handleGestureStateChange = event([
|
||||
{
|
||||
nativeEvent: {
|
||||
state: (s: Animated.Value<number>) => set(this.gestureState, s),
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -519,7 +527,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureStateChange}
|
||||
hitSlop={hitSlop}
|
||||
enabled={!locked}
|
||||
{...gestureHandlerProps}
|
||||
@@ -537,7 +545,13 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
sceneContainerStyle as any,
|
||||
]}
|
||||
>
|
||||
{renderSceneContent({ progress: this.progress })}
|
||||
<View
|
||||
accessibilityElementsHidden={open}
|
||||
importantForAccessibility={open ? 'no-hide-descendants' : 'auto'}
|
||||
style={styles.content}
|
||||
>
|
||||
{renderSceneContent({ progress: this.progress })}
|
||||
</View>
|
||||
<TapGestureHandler onHandlerStateChange={this.handleTapStateChange}>
|
||||
<Animated.View
|
||||
style={[
|
||||
@@ -578,7 +592,6 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
style={[
|
||||
styles.container,
|
||||
right ? { right: offset } : { left: offset },
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
{
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
opacity: this.drawerOpacity,
|
||||
|
||||
36
packages/drawer/src/views/DrawerContent.tsx
Normal file
36
packages/drawer/src/views/DrawerContent.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StyleSheet } from 'react-native';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
import DrawerItemList from './DrawerItemList';
|
||||
import { DrawerContentComponentProps } from '../types';
|
||||
|
||||
export default function DrawerContent({
|
||||
contentContainerStyle,
|
||||
style,
|
||||
drawerPosition,
|
||||
...rest
|
||||
}: DrawerContentComponentProps) {
|
||||
const insets = useSafeArea();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
{
|
||||
paddingTop: insets.top + 4,
|
||||
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
|
||||
paddingRight: drawerPosition === 'right' ? insets.right : 0,
|
||||
},
|
||||
contentContainerStyle,
|
||||
]}
|
||||
style={[styles.container, style]}
|
||||
>
|
||||
<DrawerItemList {...rest} />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
139
packages/drawer/src/views/DrawerItem.tsx
Normal file
139
packages/drawer/src/views/DrawerItem.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
StyleSheet,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
import TouchableItem from './TouchableItem';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* The label text of the item.
|
||||
*/
|
||||
label:
|
||||
| string
|
||||
| ((props: { focused: boolean; color: string }) => React.ReactNode);
|
||||
/**
|
||||
* Icon to display for the `DrawerItem`.
|
||||
*/
|
||||
icon?: (props: {
|
||||
focused: boolean;
|
||||
size: number;
|
||||
color: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* Whether to highlight the drawer item as active.
|
||||
*/
|
||||
focused?: boolean;
|
||||
/**
|
||||
* Function to execute on press.
|
||||
*/
|
||||
onPress: () => void;
|
||||
/**
|
||||
* Color for the icon and label when the item is active.
|
||||
*/
|
||||
activeTintColor?: string;
|
||||
/**
|
||||
* Color for the icon and label when the item is inactive.
|
||||
*/
|
||||
inactiveTintColor?: string;
|
||||
/**
|
||||
* Background color for item when its active.
|
||||
*/
|
||||
activeBackgroundColor?: string;
|
||||
/**
|
||||
* Background color for item when its inactive.
|
||||
*/
|
||||
inactiveBackgroundColor?: string;
|
||||
/**
|
||||
* Style object for the label element.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the wrapper element.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component used to show an action item with an icon and a label in a navigation drawer.
|
||||
*/
|
||||
export default function DrawerItem({
|
||||
icon,
|
||||
label,
|
||||
focused = false,
|
||||
activeTintColor = '#6200ee',
|
||||
inactiveTintColor = 'rgba(0, 0, 0, .68)',
|
||||
activeBackgroundColor = 'rgba(98, 0, 238, 0.12)',
|
||||
inactiveBackgroundColor = 'transparent',
|
||||
style,
|
||||
onPress,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { borderRadius = 4 } = StyleSheet.flatten(style || {});
|
||||
const color = focused ? activeTintColor : inactiveTintColor;
|
||||
const backgroundColor = focused
|
||||
? activeBackgroundColor
|
||||
: inactiveBackgroundColor;
|
||||
|
||||
const iconNode = icon ? icon({ size: 24, focused, color }) : null;
|
||||
|
||||
return (
|
||||
<View
|
||||
collapsable={false}
|
||||
{...rest}
|
||||
style={[styles.container, { borderRadius, backgroundColor }, style]}
|
||||
>
|
||||
<TouchableItem
|
||||
borderless
|
||||
delayPressIn={0}
|
||||
onPress={onPress}
|
||||
style={[styles.wrapper, { borderRadius }]}
|
||||
accessibilityTraits={focused ? ['button', 'selected'] : 'button'}
|
||||
accessibilityComponentType="button"
|
||||
accessibilityRole="button"
|
||||
accessibilityStates={focused ? ['selected'] : []}
|
||||
>
|
||||
<React.Fragment>
|
||||
{iconNode}
|
||||
{typeof label === 'function' ? (
|
||||
label({ color, focused })
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
styles.label,
|
||||
{
|
||||
color,
|
||||
fontWeight: '500',
|
||||
marginLeft: iconNode ? 32 : 0,
|
||||
marginVertical: 5,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</TouchableItem>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginHorizontal: 10,
|
||||
marginVertical: 4,
|
||||
},
|
||||
wrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 8,
|
||||
},
|
||||
label: {
|
||||
marginRight: 32,
|
||||
},
|
||||
});
|
||||
67
packages/drawer/src/views/DrawerItemList.tsx
Normal file
67
packages/drawer/src/views/DrawerItemList.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import { CommonActions } from '@react-navigation/core';
|
||||
import {
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
import DrawerItem from './DrawerItem';
|
||||
import {
|
||||
DrawerNavigationHelpers,
|
||||
DrawerDescriptorMap,
|
||||
DrawerContentOptions,
|
||||
} from '../types';
|
||||
|
||||
type Props = Omit<DrawerContentOptions, 'contentContainerStyle' | 'style'> & {
|
||||
state: DrawerNavigationState;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders the navigation list in the drawer.
|
||||
*/
|
||||
export default function DrawerItemList({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
activeBackgroundColor,
|
||||
inactiveBackgroundColor,
|
||||
itemStyle,
|
||||
labelStyle,
|
||||
}: Props) {
|
||||
return (state.routes.map((route, i) => {
|
||||
const focused = i === state.index;
|
||||
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
key={route.key}
|
||||
label={
|
||||
drawerLabel !== undefined
|
||||
? drawerLabel
|
||||
: title !== undefined
|
||||
? title
|
||||
: route.name
|
||||
}
|
||||
icon={drawerIcon}
|
||||
focused={focused}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
labelStyle={labelStyle}
|
||||
style={itemStyle}
|
||||
onPress={() => {
|
||||
navigation.dispatch({
|
||||
...(focused
|
||||
? DrawerActions.closeDrawer()
|
||||
: CommonActions.navigate(route.name)),
|
||||
target: state.key,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}) as React.ReactNode) as React.ReactElement;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import TouchableItem from './TouchableItem';
|
||||
import { DrawerNavigationItemsProps } from '../types';
|
||||
|
||||
/**
|
||||
* Component that renders the navigation list in the drawer.
|
||||
*/
|
||||
const DrawerNavigatorItems = ({
|
||||
items,
|
||||
activeItemKey,
|
||||
activeTintColor,
|
||||
activeBackgroundColor,
|
||||
inactiveTintColor,
|
||||
inactiveBackgroundColor,
|
||||
getLabel,
|
||||
renderIcon,
|
||||
onItemPress,
|
||||
itemsContainerStyle,
|
||||
itemStyle,
|
||||
labelStyle,
|
||||
activeLabelStyle,
|
||||
inactiveLabelStyle,
|
||||
iconContainerStyle,
|
||||
drawerPosition,
|
||||
}: DrawerNavigationItemsProps) => (
|
||||
<View style={[styles.container, itemsContainerStyle]}>
|
||||
{items.map((route, index: number) => {
|
||||
const focused = activeItemKey === route.key;
|
||||
const color = focused ? activeTintColor : inactiveTintColor;
|
||||
const backgroundColor = focused
|
||||
? activeBackgroundColor
|
||||
: inactiveBackgroundColor;
|
||||
const scene = { route, index, focused, tintColor: color };
|
||||
const icon = renderIcon(scene);
|
||||
const label = getLabel(scene);
|
||||
const accessibilityLabel = typeof label === 'string' ? label : undefined;
|
||||
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
|
||||
return (
|
||||
<TouchableItem
|
||||
key={route.key}
|
||||
accessible
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
onPress={() => {
|
||||
onItemPress({ route, focused });
|
||||
}}
|
||||
delayPressIn={0}
|
||||
>
|
||||
<SafeAreaView
|
||||
style={[{ backgroundColor }, styles.item, itemStyle]}
|
||||
forceInset={{
|
||||
[drawerPosition]: 'always',
|
||||
[drawerPosition === 'left' ? 'right' : 'left']: 'never',
|
||||
vertical: 'never',
|
||||
}}
|
||||
>
|
||||
{icon ? (
|
||||
<View
|
||||
style={[
|
||||
styles.icon,
|
||||
focused ? null : styles.inactiveIcon,
|
||||
iconContainerStyle,
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
</View>
|
||||
) : null}
|
||||
{typeof label === 'string' ? (
|
||||
<Text
|
||||
style={[styles.label, { color }, labelStyle, extraLabelStyle]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
</SafeAreaView>
|
||||
</TouchableItem>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
|
||||
/* Material design specs - https://material.io/guidelines/patterns/navigation-drawer.html#navigation-drawer-specs */
|
||||
DrawerNavigatorItems.defaultProps = {
|
||||
activeTintColor: '#2196f3',
|
||||
activeBackgroundColor: 'rgba(0, 0, 0, .04)',
|
||||
inactiveTintColor: 'rgba(0, 0, 0, .87)',
|
||||
inactiveBackgroundColor: 'transparent',
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingVertical: 4,
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
marginHorizontal: 16,
|
||||
width: 24,
|
||||
alignItems: 'center',
|
||||
},
|
||||
inactiveIcon: {
|
||||
/*
|
||||
* Icons have 0.54 opacity according to guidelines
|
||||
* 100/87 * 54 ~= 62
|
||||
*/
|
||||
opacity: 0.62,
|
||||
},
|
||||
label: {
|
||||
margin: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export default DrawerNavigatorItems;
|
||||
@@ -1,128 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, View, ViewStyle, StyleProp } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { Route, CommonActions } from '@react-navigation/core';
|
||||
import {
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
import {
|
||||
Scene,
|
||||
ContentComponentProps,
|
||||
DrawerDescriptorMap,
|
||||
DrawerNavigationHelpers,
|
||||
} from '../types';
|
||||
|
||||
type Props = {
|
||||
contentComponent?: React.ComponentType<ContentComponentProps>;
|
||||
contentOptions?: object;
|
||||
state: DrawerNavigationState;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
drawerOpenProgress: Animated.Node<number>;
|
||||
drawerPosition: 'left' | 'right';
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders the sidebar screen of the drawer.
|
||||
*/
|
||||
class DrawerSidebar extends React.PureComponent<Props> {
|
||||
private getScreenOptions = (routeKey: string) => {
|
||||
const descriptor = this.props.descriptors[routeKey];
|
||||
|
||||
if (!descriptor.options) {
|
||||
throw new Error(
|
||||
'Cannot access screen descriptor options from drawer sidebar'
|
||||
);
|
||||
}
|
||||
|
||||
return descriptor.options;
|
||||
};
|
||||
|
||||
private getLabel = ({ focused, tintColor, route }: Scene) => {
|
||||
const { drawerLabel, title } = this.getScreenOptions(route.key);
|
||||
if (drawerLabel) {
|
||||
return typeof drawerLabel === 'function'
|
||||
? drawerLabel({ tintColor, focused })
|
||||
: drawerLabel;
|
||||
}
|
||||
|
||||
if (typeof title === 'string') {
|
||||
return title;
|
||||
}
|
||||
|
||||
return route.name;
|
||||
};
|
||||
|
||||
private renderIcon = ({ focused, tintColor, route }: Scene) => {
|
||||
const { drawerIcon } = this.getScreenOptions(route.key);
|
||||
if (drawerIcon) {
|
||||
return typeof drawerIcon === 'function'
|
||||
? drawerIcon({ tintColor, focused })
|
||||
: drawerIcon;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
private handleItemPress = ({
|
||||
route,
|
||||
focused,
|
||||
}: {
|
||||
route: Route<string>;
|
||||
focused: boolean;
|
||||
}) => {
|
||||
const { state, navigation } = this.props;
|
||||
|
||||
navigation.dispatch({
|
||||
...(focused
|
||||
? DrawerActions.closeDrawer()
|
||||
: CommonActions.navigate(route.name)),
|
||||
target: state.key,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const ContentComponent = this.props.contentComponent;
|
||||
|
||||
if (!ContentComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { state } = this.props;
|
||||
|
||||
if (typeof state.index !== 'number') {
|
||||
throw new Error(
|
||||
'The index of the route should be state in the navigation state'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, this.props.style]}>
|
||||
<ContentComponent
|
||||
{...this.props.contentOptions}
|
||||
navigation={this.props.navigation}
|
||||
descriptors={this.props.descriptors}
|
||||
drawerOpenProgress={this.props.drawerOpenProgress}
|
||||
items={state.routes}
|
||||
activeItemKey={
|
||||
state.routes[state.index] ? state.routes[state.index].key : null
|
||||
}
|
||||
getLabel={this.getLabel}
|
||||
renderIcon={this.renderIcon}
|
||||
onItemPress={this.handleItemPress}
|
||||
drawerPosition={this.props.drawerPosition}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DrawerSidebar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -1,30 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native';
|
||||
import {
|
||||
Dimensions,
|
||||
StyleSheet,
|
||||
I18nManager,
|
||||
Platform,
|
||||
ScaledSize,
|
||||
} from 'react-native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
import {
|
||||
DrawerNavigationState,
|
||||
DrawerActions,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
import DrawerSidebar from './DrawerSidebar';
|
||||
import DrawerGestureContext from '../utils/DrawerGestureContext';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import DrawerNavigatorItems from './DrawerNavigatorItems';
|
||||
import DrawerContent from './DrawerContent';
|
||||
import Drawer from './Drawer';
|
||||
import {
|
||||
DrawerDescriptorMap,
|
||||
DrawerNavigationConfig,
|
||||
ContentComponentProps,
|
||||
DrawerNavigationHelpers,
|
||||
DrawerContentComponentProps,
|
||||
} from '../types';
|
||||
|
||||
type Props = DrawerNavigationConfig & {
|
||||
type Props = Omit<DrawerNavigationConfig, 'overlayColor'> & {
|
||||
state: DrawerNavigationState;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
overlayColor: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -32,13 +38,26 @@ type State = {
|
||||
drawerWidth: number;
|
||||
};
|
||||
|
||||
const DefaultContentComponent = (props: ContentComponentProps) => (
|
||||
<ScrollView alwaysBounceVertical={false}>
|
||||
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
|
||||
<DrawerNavigatorItems {...props} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
const getDefaultDrawerWidth = ({
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
height: number;
|
||||
width: number;
|
||||
}) => {
|
||||
/*
|
||||
* Default drawer width is screen width - header height
|
||||
* with a max width of 280 on mobile and 320 on tablet
|
||||
* https://material.io/guidelines/patterns/navigation-drawer.html
|
||||
*/
|
||||
const smallerAxisSize = Math.min(height, width);
|
||||
const isLandscape = width > height;
|
||||
const isTablet = smallerAxisSize >= 600;
|
||||
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
|
||||
const maxWidth = isTablet ? 320 : 280;
|
||||
|
||||
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders the drawer.
|
||||
@@ -46,25 +65,12 @@ const DefaultContentComponent = (props: ContentComponentProps) => (
|
||||
export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
lazy: true,
|
||||
drawerWidth: () => {
|
||||
/*
|
||||
* Default drawer width is screen width - header height
|
||||
* with a max width of 280 on mobile and 320 on tablet
|
||||
* https://material.io/guidelines/patterns/navigation-drawer.html
|
||||
*/
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const smallerAxisSize = Math.min(height, width);
|
||||
const isLandscape = width > height;
|
||||
const isTablet = smallerAxisSize >= 600;
|
||||
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
|
||||
const maxWidth = isTablet ? 320 : 280;
|
||||
|
||||
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
|
||||
},
|
||||
contentComponent: DefaultContentComponent,
|
||||
drawerContent: (props: DrawerContentComponentProps) => (
|
||||
<DrawerContent {...props} />
|
||||
),
|
||||
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
|
||||
keyboardDismissMode: 'on-drag',
|
||||
drawerBackgroundColor: 'white',
|
||||
overlayColor: 'rgba(0, 0, 0, 0.5)',
|
||||
drawerType: 'front',
|
||||
hideStatusBar: false,
|
||||
statusBarAnimation: 'slide',
|
||||
@@ -83,10 +89,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
|
||||
state: State = {
|
||||
loaded: [this.props.state.index],
|
||||
drawerWidth:
|
||||
typeof this.props.drawerWidth === 'function'
|
||||
? this.props.drawerWidth()
|
||||
: this.props.drawerWidth,
|
||||
drawerWidth: getDefaultDrawerWidth(Dimensions.get('window')),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -121,11 +124,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
navigation.emit({ type: 'drawerClose' });
|
||||
};
|
||||
|
||||
private updateWidth = () => {
|
||||
const drawerWidth =
|
||||
typeof this.props.drawerWidth === 'function'
|
||||
? this.props.drawerWidth()
|
||||
: this.props.drawerWidth;
|
||||
private updateWidth = ({ window }: { window: ScaledSize }) => {
|
||||
const drawerWidth = getDefaultDrawerWidth(window);
|
||||
|
||||
if (this.state.drawerWidth !== drawerWidth) {
|
||||
this.setState({ drawerWidth });
|
||||
@@ -133,48 +133,57 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
private renderNavigationView = ({ progress }: any) => {
|
||||
return <DrawerSidebar drawerOpenProgress={progress} {...this.props} />;
|
||||
const {
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
drawerPosition,
|
||||
drawerContent,
|
||||
drawerContentOptions,
|
||||
} = this.props;
|
||||
|
||||
return drawerContent({
|
||||
...drawerContentOptions,
|
||||
progress: progress,
|
||||
state: state,
|
||||
navigation: navigation,
|
||||
descriptors: descriptors,
|
||||
drawerPosition: drawerPosition,
|
||||
});
|
||||
};
|
||||
|
||||
private renderContent = () => {
|
||||
let { lazy, state, descriptors, unmountInactiveRoutes } = this.props;
|
||||
let { lazy, state, descriptors, unmountInactiveScreens } = this.props;
|
||||
|
||||
const { loaded } = this.state;
|
||||
|
||||
if (unmountInactiveRoutes) {
|
||||
const activeKey = state.routes[state.index].key;
|
||||
const descriptor = descriptors[activeKey];
|
||||
return (
|
||||
<ScreenContainer style={styles.content}>
|
||||
{state.routes.map((route, index) => {
|
||||
if (unmountInactiveScreens && index !== state.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return descriptor.render();
|
||||
} else {
|
||||
return (
|
||||
<ScreenContainer style={styles.content}>
|
||||
{state.routes.map((route, index) => {
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFocused = state.index === index;
|
||||
const descriptor = descriptors[route.key];
|
||||
const isFocused = state.index === index;
|
||||
const descriptor = descriptors[route.key];
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
{ opacity: isFocused ? 1 : 0 },
|
||||
]}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
private setDrawerGestureRef = (ref: PanGestureHandler | null) => {
|
||||
@@ -188,9 +197,9 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
descriptors,
|
||||
drawerType,
|
||||
drawerPosition,
|
||||
drawerBackgroundColor,
|
||||
overlayColor,
|
||||
sceneContainerStyle,
|
||||
drawerStyle,
|
||||
edgeWidth,
|
||||
minSwipeDistance,
|
||||
hideStatusBar,
|
||||
@@ -198,6 +207,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
gestureHandlerProps,
|
||||
} = this.props;
|
||||
|
||||
const { drawerWidth } = this.state;
|
||||
|
||||
const activeKey = state.routes[state.index].key;
|
||||
const { drawerLockMode } = descriptors[activeKey].options;
|
||||
|
||||
@@ -209,35 +220,32 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
: state.isDrawerOpen;
|
||||
|
||||
return (
|
||||
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
|
||||
<Drawer
|
||||
open={isOpen}
|
||||
locked={
|
||||
drawerLockMode === 'locked-open' ||
|
||||
drawerLockMode === 'locked-closed'
|
||||
}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onClose={this.handleDrawerClose}
|
||||
onGestureRef={this.setDrawerGestureRef}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
drawerStyle={{
|
||||
backgroundColor: drawerBackgroundColor || 'white',
|
||||
width: this.state.drawerWidth,
|
||||
}}
|
||||
overlayStyle={{
|
||||
backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)',
|
||||
}}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={this.renderNavigationView}
|
||||
renderSceneContent={this.renderContent}
|
||||
/>
|
||||
</DrawerGestureContext.Provider>
|
||||
<SafeAreaProvider>
|
||||
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
|
||||
<Drawer
|
||||
open={isOpen}
|
||||
locked={
|
||||
drawerLockMode === 'locked-open' ||
|
||||
drawerLockMode === 'locked-closed'
|
||||
}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onClose={this.handleDrawerClose}
|
||||
onGestureRef={this.setDrawerGestureRef}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
drawerStyle={[{ width: drawerWidth }, drawerStyle]}
|
||||
overlayStyle={{ backgroundColor: overlayColor }}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={this.renderNavigationView}
|
||||
renderSceneContent={this.renderContent}
|
||||
/>
|
||||
</DrawerGestureContext.Provider>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/example/.gitignore
vendored
Normal file
1
packages/example/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ios/Pods
|
||||
@@ -3,6 +3,162 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.18...@react-navigation/example@5.0.0-alpha.19) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.17...@react-navigation/example@5.0.0-alpha.18) (2019-11-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* minor tweaks for web and fix example ([67fd69a](https://github.com/satya164/navigation-ex/commit/67fd69a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.16...@react-navigation/example@5.0.0-alpha.17) (2019-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve type annotation for screens ([8f16085](https://github.com/satya164/navigation-ex/commit/8f16085))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.15...@react-navigation/example@5.0.0-alpha.16) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.14...@react-navigation/example@5.0.0-alpha.15) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.13...@react-navigation/example@5.0.0-alpha.14) (2019-10-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.12...@react-navigation/example@5.0.0-alpha.13) (2019-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* block GH interactions in Native Stack example ([#126](https://github.com/react-navigation/navigation-ex/issues/126)) ([386d1c0](https://github.com/react-navigation/navigation-ex/commit/386d1c0))
|
||||
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.11...@react-navigation/example@5.0.0-alpha.12) (2019-10-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* drop header: null in favor of more explitit headerShown option ([ba6b6ae](https://github.com/satya164/navigation-ex/commit/ba6b6ae))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.10...@react-navigation/example@5.0.0-alpha.11) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.9...@react-navigation/example@5.0.0-alpha.10) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.8...@react-navigation/example@5.0.0-alpha.9) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.7...@react-navigation/example@5.0.0-alpha.8) (2019-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close drawer on navigate ([655a220](https://github.com/react-navigation/navigation-ex/commit/655a220))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.6...@react-navigation/example@5.0.0-alpha.7) (2019-09-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* compatibility layer ([e0f28a4](https://github.com/satya164/navigation-ex/commit/e0f28a4))
|
||||
* make example run as bare react-native project as well ([#85](https://github.com/satya164/navigation-ex/issues/85)) ([d16c20c](https://github.com/satya164/navigation-ex/commit/d16c20c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.6) (2019-08-31)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.5](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.5) (2019-08-31)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.4](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.3...@react-navigation/example@5.0.0-alpha.4) (2019-08-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle route names change when all routes are removed ([#86](https://github.com/satya164/navigation-ex/issues/86)) ([1b2983e](https://github.com/satya164/navigation-ex/commit/1b2983e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.2...@react-navigation/example@5.0.0-alpha.3) (2019-08-29)
|
||||
|
||||
|
||||
|
||||
19
packages/example/android/ReactNavigationExample.iml
Normal file
19
packages/example/android/ReactNavigationExample.iml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="ReactNavigationExample" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
65
packages/example/android/app/BUCK
Normal file
65
packages/example/android/app/BUCK
Normal file
@@ -0,0 +1,65 @@
|
||||
# To learn about Buck see [Docs](https://buckbuild.com/).
|
||||
# To run your application with Buck:
|
||||
# - install Buck
|
||||
# - `npm start` - to start the packager
|
||||
# - `cd android`
|
||||
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
|
||||
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
||||
# - `buck install -r android/app` - compile, install and run application
|
||||
#
|
||||
|
||||
lib_deps = []
|
||||
|
||||
for jarfile in glob(['libs/*.jar']):
|
||||
name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
|
||||
lib_deps.append(':' + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
||||
|
||||
for aarfile in glob(['libs/*.aar']):
|
||||
name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
|
||||
lib_deps.append(':' + name)
|
||||
android_prebuilt_aar(
|
||||
name = name,
|
||||
aar = aarfile,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = "all-libs",
|
||||
exported_deps = lib_deps,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = "app-code",
|
||||
srcs = glob([
|
||||
"src/main/java/**/*.java",
|
||||
]),
|
||||
deps = [
|
||||
":all-libs",
|
||||
":build_config",
|
||||
":res",
|
||||
],
|
||||
)
|
||||
|
||||
android_build_config(
|
||||
name = "build_config",
|
||||
package = "com.reactnavigationexample",
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = "res",
|
||||
package = "com.reactnavigationexample",
|
||||
res = "src/main/res",
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = "app",
|
||||
keystore = "//android/keystores:debug",
|
||||
manifest = "src/main/AndroidManifest.xml",
|
||||
package_type = "debug",
|
||||
deps = [
|
||||
":app-code",
|
||||
],
|
||||
)
|
||||
180
packages/example/android/app/app.iml
Normal file
180
packages/example/android/app/app.iml
Normal file
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.3.0" />
|
||||
<option name="LAST_KNOWN_AGP_VERSION" value="3.3.0" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/rs/debug;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
|
||||
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/react/debug" type="java-resource" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debugAndroidTest/compileDebugAndroidTestRenderscript/out" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:collections:28.0.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.1@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.1@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.device.yearclass:yearclass:2.1.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: commons-codec:commons-codec:1.10@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: commons-io:commons-io:1.4@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.59.10@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:glide:4.9.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-location:16.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base:16.0.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-places-placereport:16.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks:16.0.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement:16.0.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-v4:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-fragment:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:customtabs:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:loader:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-media-compat:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:viewpager:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:coordinatorlayout:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:drawerlayout:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:slidingpanelayout:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:customview:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:swiperefreshlayout:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:asynclayoutinflater:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:support-compat:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:versionedparcelable:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:cursoradapter:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:documentfile:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:localbroadcastmanager:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:print:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.android.support:interpolator:28.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata:1.1.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.1@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
|
||||
<orderEntry type="module" module-name="react-native-screens" />
|
||||
<orderEntry type="module" module-name="react-native-reanimated" />
|
||||
<orderEntry type="module" module-name="react-native-gesture-handler" />
|
||||
<orderEntry type="module" module-name="expo-permissions" />
|
||||
<orderEntry type="module" module-name="unimodules-core" />
|
||||
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
|
||||
<orderEntry type="module" module-name="expo-app-loader-provider" />
|
||||
<orderEntry type="module" module-name="expo-constants" />
|
||||
<orderEntry type="module" module-name="expo-file-system" />
|
||||
<orderEntry type="module" module-name="expo-font" />
|
||||
<orderEntry type="module" module-name="expo-keep-awake" />
|
||||
<orderEntry type="module" module-name="expo-linear-gradient" />
|
||||
<orderEntry type="module" module-name="expo-location" />
|
||||
<orderEntry type="module" module-name="expo-sqlite" />
|
||||
<orderEntry type="module" module-name="expo-web-browser" />
|
||||
<orderEntry type="module" module-name="unimodules-barcode-scanner-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-camera-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-constants-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-face-detector-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-file-system-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-font-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-image-loader-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-permissions-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-sensors-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
|
||||
</component>
|
||||
</module>
|
||||
157
packages/example/android/app/build.gradle
Normal file
157
packages/example/android/app/build.gradle
Normal file
@@ -0,0 +1,157 @@
|
||||
apply plugin: "com.android.application"
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
* and bundleReleaseJsAndAssets).
|
||||
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||
* bundle directly from the development server. Below you can see all the possible configurations
|
||||
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||
*
|
||||
* project.ext.react = [
|
||||
* // the name of the generated asset file containing your JS bundle
|
||||
* bundleAssetName: "index.android.bundle",
|
||||
*
|
||||
* // the entry file for bundle generation
|
||||
* entryFile: "index.android.js",
|
||||
*
|
||||
* // whether to bundle JS and assets in debug mode
|
||||
* bundleInDebug: false,
|
||||
*
|
||||
* // whether to bundle JS and assets in release mode
|
||||
* bundleInRelease: true,
|
||||
*
|
||||
* // whether to bundle JS and assets in another build variant (if configured).
|
||||
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'bundleIn${productFlavor}${buildType}'
|
||||
* // 'bundleIn${buildType}'
|
||||
* // bundleInFreeDebug: true,
|
||||
* // bundleInPaidRelease: true,
|
||||
* // bundleInBeta: true,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // where to put the JS bundle asset in debug mode
|
||||
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||
*
|
||||
* // where to put the JS bundle asset in release mode
|
||||
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in debug mode
|
||||
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in release mode
|
||||
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||
*
|
||||
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||
* // for example, you might want to remove it from here.
|
||||
* inputExcludes: ["android/**", "ios/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
entryFile: "index.js"
|
||||
]
|
||||
|
||||
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
* - An APK that only works on ARM devices
|
||||
* - An APK that only works on x86 devices
|
||||
* The advantage is the size of the APK is reduced by about 4MB.
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.reactnavigationexample"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
// For each separate APK per architecture, set a unique version code as described here:
|
||||
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
|
||||
def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':react-native-safe-area-context')
|
||||
implementation project(':react-native-screens')
|
||||
implementation project(':react-native-reanimated')
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
addUnimodulesDependencies()
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
19
packages/example/android/app/build_defs.bzl
Normal file
19
packages/example/android/app/build_defs.bzl
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Helper definitions to glob .aar and .jar targets"""
|
||||
|
||||
def create_aar_targets(aarfiles):
|
||||
for aarfile in aarfiles:
|
||||
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
|
||||
lib_deps.append(":" + name)
|
||||
android_prebuilt_aar(
|
||||
name = name,
|
||||
aar = aarfile,
|
||||
)
|
||||
|
||||
def create_jar_targets(jarfiles):
|
||||
for jarfile in jarfiles:
|
||||
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
|
||||
lib_deps.append(":" + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
||||
17
packages/example/android/app/proguard-rules.pro
vendored
Normal file
17
packages/example/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
48
packages/example/android/app/src/main/AndroidManifest.xml
Normal file
48
packages/example/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.reactnavigationexample">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<!-- OPTIONAL PERMISSIONS, REMOVE WHATEVER YOU DO NOT NEED -->
|
||||
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
|
||||
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<!-- These require runtime permissions on M -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
<!-- END OPTIONAL PERMISSIONS -->
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.reactnavigationexample;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "ReactNavigationExample";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.reactnavigationexample;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.th3rdwave.safeareacontext.SafeAreaContextPackage;
|
||||
import com.swmansion.rnscreens.RNScreensPackage;
|
||||
import com.swmansion.reanimated.ReanimatedPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.reactnavigationexample.generated.BasePackageList;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||
|
||||
import org.unimodules.adapters.react.ReactAdapterPackage;
|
||||
import org.unimodules.adapters.react.ModuleRegistryAdapter;
|
||||
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
|
||||
import org.unimodules.core.interfaces.Package;
|
||||
import org.unimodules.core.interfaces.SingletonModule;
|
||||
import expo.modules.constants.ConstantsPackage;
|
||||
import expo.modules.permissions.PermissionsPackage;
|
||||
import expo.modules.filesystem.FileSystemPackage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
|
||||
new BasePackageList().getPackageList(),
|
||||
Arrays.<SingletonModule>asList()
|
||||
);
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new SafeAreaContextPackage(),
|
||||
new RNScreensPackage(),
|
||||
new ReanimatedPackage(),
|
||||
new RNGestureHandlerPackage(),
|
||||
new ModuleRegistryAdapter(mModuleRegistryProvider)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user