mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 17:32:55 +08:00
Compare commits
100 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9b044fb3 | ||
|
|
f24d3a3461 | ||
|
|
3df65e2819 | ||
|
|
5c4afc5cb4 | ||
|
|
d5bb357053 | ||
|
|
b1fe73097f | ||
|
|
49f6fed6d3 | ||
|
|
b1a65fc73e | ||
|
|
3ea8eec432 | ||
|
|
00e0f05190 | ||
|
|
193c344ba5 | ||
|
|
358d9e9feb | ||
|
|
6a5d0a035a | ||
|
|
b75744abd5 | ||
|
|
6dbda1a0c2 | ||
|
|
70029d6c13 | ||
|
|
469d0542c7 | ||
|
|
0dcaea3242 | ||
|
|
646cbfb28e | ||
|
|
660cac3557 | ||
|
|
e637250a7e | ||
|
|
82af7bed71 | ||
|
|
cb46d0bca4 | ||
|
|
b3665a325d | ||
|
|
0cc7a12b9c | ||
|
|
90e417248d | ||
|
|
e071a978e6 | ||
|
|
296c836064 | ||
|
|
09f6808d7d | ||
|
|
5bb0f405ce | ||
|
|
2dfa4f3629 | ||
|
|
cf41288760 | ||
|
|
3677818f63 | ||
|
|
162410843c | ||
|
|
028c2887c6 | ||
|
|
7a44cda136 | ||
|
|
a046db536f | ||
|
|
d115787b1c | ||
|
|
80a337024a | ||
|
|
c19da31240 | ||
|
|
85e9376302 | ||
|
|
a67b49477e | ||
|
|
225cb298b6 | ||
|
|
c8ea4199f4 | ||
|
|
f16700812f | ||
|
|
240ce01822 | ||
|
|
c7dd3a58b1 | ||
|
|
125bd70e49 | ||
|
|
4578849ebf | ||
|
|
c084517d7b | ||
|
|
22c85ff6a9 | ||
|
|
bf76075e0f | ||
|
|
d69b0db604 | ||
|
|
cdb2fed43d | ||
|
|
bb0226e26d | ||
|
|
1a28c299b5 | ||
|
|
e0c3298e64 | ||
|
|
040f5dbb9d | ||
|
|
5b7bbbdfd9 | ||
|
|
c5fefc6ee9 | ||
|
|
aaf01e01e7 | ||
|
|
ac242fd281 | ||
|
|
c5fcfbd427 | ||
|
|
424c9469e4 | ||
|
|
8f40a98086 | ||
|
|
f964200b0d | ||
|
|
bd2f008a83 | ||
|
|
e37d6598ca | ||
|
|
c8ac5fab61 | ||
|
|
b6accd03f6 | ||
|
|
0cca1309ec | ||
|
|
6c9447a38c | ||
|
|
030c63c89f | ||
|
|
2bf0958502 | ||
|
|
94cff2380a | ||
|
|
359ae1bfac | ||
|
|
031136f7c8 | ||
|
|
b6e7e08b9a | ||
|
|
6c6102b459 | ||
|
|
0c59ef7328 | ||
|
|
297eabb90e | ||
|
|
b234b035c3 | ||
|
|
80629bf30b | ||
|
|
688d16de5d | ||
|
|
13b4e07348 | ||
|
|
86c39d2e0e | ||
|
|
7160a511e6 | ||
|
|
ae680a1e3c | ||
|
|
7c72337c33 | ||
|
|
6c188addc6 | ||
|
|
cff2d06adc | ||
|
|
4c9f87df6d | ||
|
|
fa48c9d42a | ||
|
|
abb595830e | ||
|
|
8ad2922f35 | ||
|
|
c715fef2bd | ||
|
|
cea2fc29ba | ||
|
|
79ab56fe41 | ||
|
|
a121844148 | ||
|
|
61b1134f90 |
@@ -31,7 +31,7 @@ jobs:
|
||||
- run: |
|
||||
yarn lint
|
||||
yarn typescript
|
||||
unit-test:
|
||||
unit-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
@@ -59,7 +59,7 @@ workflows:
|
||||
- lint-and-typecheck:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- unit-test:
|
||||
- unit-tests:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- build-packages:
|
||||
|
||||
@@ -4,3 +4,7 @@ dist/
|
||||
lib/
|
||||
web-build/
|
||||
web-report/
|
||||
|
||||
.expo/
|
||||
.yarn/
|
||||
.vscode/
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"@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",
|
||||
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
yarn-*.js binary
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: Native Stack Navigator
|
||||
about: Report an issue with Native Stack Navigator (@react-navigation/native-stack)
|
||||
name: React Navigation 4
|
||||
about: Report an issue with React Navigation 4
|
||||
title: ''
|
||||
labels: bug, package:native-stack
|
||||
labels: bug, version-4
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
@@ -29,8 +29,13 @@ assignees: ''
|
||||
| software | version |
|
||||
| ------------------------------ | ------- |
|
||||
| iOS or Android |
|
||||
| @react-navigation/native |
|
||||
| @react-navigation/native-stack |
|
||||
| react-navigation |
|
||||
| react-navigation-stack |
|
||||
| react-navigation-tabs |
|
||||
| react-navigation-drawer |
|
||||
| react-native-reanimated |
|
||||
| react-native-gesture-handler |
|
||||
| react-native-safe-area-context |
|
||||
| react-native-screens |
|
||||
| react-native |
|
||||
| expo |
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,10 +1,10 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Troubleshooting
|
||||
url: https://reactnavigation.org/docs/en/next/troubleshooting.html
|
||||
url: https://reactnavigation.org/docs/troubleshooting.html
|
||||
about: Read how to troubleshoot and fix common issues and mistakes.
|
||||
- name: Documentation
|
||||
url: https://next.reactnavigation.org
|
||||
url: https://reactnavigation.org
|
||||
about: Read the official documentation.
|
||||
- name: Feature requests
|
||||
url: https://react-navigation.canny.io/feature-requests
|
||||
|
||||
49
.github/workflows/triage.yml
vendored
Normal file
49
.github/workflows/triage.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Triage
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
needs-more-info:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'needs more info'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/github@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to reproduce the issue with minimal code (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
|
||||
|
||||
needs-repro:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'needs repro'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/github@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. Can you provide a minimal repro which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
|
||||
|
||||
question:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'question'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/github@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. This helps us prioritize fixing bugs in the library. Seems you have a usage question. Please ask the question on [StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation) instead using the `react-navigation` label. You can also chat with other community members on [Reactiflux Discord server](https://www.reactiflux.com/) in the `#react-navigation` channel."
|
||||
|
||||
feature-request:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'feature-request'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/github@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. Seems you have a feature request. Please post the feature request on [Canny](https://react-navigation.canny.io/feature-requests). This lets other users upvote your feature request and helps us prioritize the most requested features."
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,9 @@
|
||||
.idea
|
||||
.expo
|
||||
.gradle
|
||||
.project
|
||||
.settings
|
||||
.history
|
||||
|
||||
local.properties
|
||||
|
||||
|
||||
147155
.yarn/releases/yarn-1.18.0.js
vendored
Executable file
147155
.yarn/releases/yarn-1.18.0.js
vendored
Executable file
File diff suppressed because one or more lines are too long
5
.yarnrc
Normal file
5
.yarnrc
Normal file
@@ -0,0 +1,5 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
yarn-path ".yarn/releases/yarn-1.18.0.js"
|
||||
204
CONTRIBUTING.md
Normal file
204
CONTRIBUTING.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Contributing
|
||||
|
||||
This library is a community effort: it can only be great if we all help out in one way or another! If you feel like you aren't experienced enough using React Navigation to contribute, you can still make an impact by:
|
||||
|
||||
- Responding to one of the open [issues](https://github.com/react-navigation/react-navigation/issues). Even if you can't resolve or fully answer a question, asking for more information or clarity on an issue is extremely beneficial for someone to come after you to resolve the issue.
|
||||
- Creating public example repositories or [Snacks](https://snack.expo.io/) of navigation problems you have solved and sharing the links.
|
||||
- Answering questions on [Stack Overflow](https://stackoverflow.com/search?q=react-navigation).
|
||||
- Answering questions in our [Reactiflux](https://www.reactiflux.com/) channel.
|
||||
- Providing feedback on the open [PRs](https://github.com/react-navigation/react-navigation/pulls).
|
||||
- Providing feedback on the open [RFCs](https://github.com/react-navigation/rfcs).
|
||||
- Improving the [website](https://github.com/react-navigation/react-navigation.github.io).
|
||||
|
||||
If you don't know where to start, check the ones with the label [`good first issue`](https://github.com/react-navigation/react-navigation/labels/good%20first%20issue) - even fixing a typo in the documentation is a worthy contribution!
|
||||
|
||||
## Development workflow
|
||||
|
||||
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
|
||||
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
While developing, you can run the [example app](/example/) with [Expo](https://expo.io/) to test your changes:
|
||||
|
||||
```sh
|
||||
yarn example start
|
||||
```
|
||||
|
||||
Make sure your code passes TypeScript and ESLint. Run the following to verify:
|
||||
|
||||
```sh
|
||||
yarn typescript
|
||||
yarn lint
|
||||
```
|
||||
|
||||
To fix formatting errors, run the following:
|
||||
|
||||
```sh
|
||||
yarn lint --fix
|
||||
```
|
||||
|
||||
Remember to add tests for your change if possible. Run the unit tests by:
|
||||
|
||||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
Running the e2e tests with Detox (on iOS) requires the following:
|
||||
|
||||
- Mac with macOS (at least macOS High Sierra 10.13.6)
|
||||
- Xcode 10.1+ with Xcode command line tools
|
||||
|
||||
First you need to install `applesimutils` and `detox-cli`:
|
||||
|
||||
```sh
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
yarn global add detox-cli
|
||||
```
|
||||
|
||||
Then you can build and run the tests:
|
||||
|
||||
```sh
|
||||
detox build -c ios.sim.debug
|
||||
detox test -c ios.sim.debug
|
||||
```
|
||||
|
||||
### Commit message convention
|
||||
|
||||
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
|
||||
|
||||
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
|
||||
- `feat`: new features, e.g. add new method to the module.
|
||||
- `refactor`: code refactor, e.g. migrate from class components to hooks.
|
||||
- `docs`: changes into documentation, e.g. add usage example for the module..
|
||||
- `test`: adding or updating tests, eg add integration tests using detox.
|
||||
- `chore`: tooling changes, e.g. change CI config.
|
||||
|
||||
Our pre-commit hooks verify that your commit message matches this format when committing.
|
||||
|
||||
### Linting and tests
|
||||
|
||||
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
|
||||
|
||||
Our pre-commit hooks verify that the linter and tests pass when committing.
|
||||
|
||||
### Scripts
|
||||
|
||||
The `package.json` file contains various scripts for common tasks:
|
||||
|
||||
- `yarn install`: setup project by installing all dependencies and pods.
|
||||
- `yarn typescript`: type-check files with TypeScript.
|
||||
- `yarn lint`: lint files with ESLint.
|
||||
- `yarn test`: run unit tests with Jest.
|
||||
- `yarn example start`: run the example app with Expo.
|
||||
|
||||
### Sending a pull request
|
||||
|
||||
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
|
||||
|
||||
When you're sending a pull request:
|
||||
|
||||
- Prefer small pull requests focused on one change.
|
||||
- Verify that linters and tests are passing.
|
||||
- Review the documentation to make sure it looks good.
|
||||
- Follow the pull request template when opening a pull request.
|
||||
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
|
||||
|
||||
## Publishing
|
||||
|
||||
Maintainers with write access to the GitHub repo and the npm organization can publish new versions. To publish a new version, first, you need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
|
||||
|
||||
```sh
|
||||
yarn release
|
||||
```
|
||||
|
||||
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to Brent Vatne ([brentvatne@gmail.com](mailto:brentvatne@gmail.com)), Satyajit Sahoo ([satyajit.happy@gmail.com](mailto:satyajit.happy@gmail.com)) or Michał Osadnik ([micosa97@gmail.com](mailto:micosa97@gmail.com)). All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
### Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
#### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
#### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
#### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
#### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
||||
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
||||
75
README.md
75
README.md
@@ -4,72 +4,15 @@
|
||||
[![Code Coverage][coverage-badge]][coverage]
|
||||
[![MIT License][license-badge]][license]
|
||||
|
||||
Routing and navigation for your React Native apps with a component-first API.
|
||||
Routing and navigation for your React Native apps.
|
||||
|
||||
Documentation can be found at [next.reactnavigation.org](https://next.reactnavigation.org/).
|
||||
Documentation can be found at [reactnavigation.org](https://reactnavigation.org/).
|
||||
|
||||
If you are looking for version 4, the code can be found in the [4.x branch](https://github.com/react-navigation/react-navigation/tree/4.x).
|
||||
|
||||
## Contributing
|
||||
|
||||
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
|
||||
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
While developing, you can run the [example app](/example/) with [Expo](https://expo.io/) to test your changes:
|
||||
|
||||
```sh
|
||||
yarn example start
|
||||
```
|
||||
|
||||
Make sure your code passes TypeScript and ESLint. Run the following to verify:
|
||||
|
||||
```sh
|
||||
yarn typescript
|
||||
yarn lint
|
||||
```
|
||||
|
||||
To fix formatting errors, run the following:
|
||||
|
||||
```sh
|
||||
yarn lint --fix
|
||||
```
|
||||
|
||||
Remember to add tests for your change if possible. Run the unit tests by:
|
||||
|
||||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
Running the e2e tests with Detox (on iOS) requires the following:
|
||||
|
||||
- Mac with macOS (at least macOS High Sierra 10.13.6)
|
||||
- Xcode 10.1+ with Xcode command line tools
|
||||
|
||||
First you need to install `applesimutils` and `detox-cli`:
|
||||
|
||||
```sh
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
yarn global add detox-cli
|
||||
```
|
||||
|
||||
Then you can build and run the tests:
|
||||
|
||||
```sh
|
||||
detox build -c ios.sim.debug
|
||||
detox test -c ios.sim.debug
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
To publish a new version, first we need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
|
||||
|
||||
```sh
|
||||
yarn lerna publish
|
||||
```
|
||||
|
||||
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
|
||||
Please read through our [contribution guide](CONTRIBUTING.md) a to get started!
|
||||
|
||||
## Installing from a fork on GitHub
|
||||
|
||||
@@ -106,9 +49,9 @@ Remember to replace `<user>`, `<repo>` and `<name>` with right values.
|
||||
|
||||
<!-- badges -->
|
||||
|
||||
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/navigation-ex/master.svg?style=flat-square
|
||||
[build]: https://circleci.com/gh/react-navigation/navigation-ex
|
||||
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/navigation-ex.svg?style=flat-square
|
||||
[coverage]: https://codecov.io/github/react-navigation/navigation-ex
|
||||
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/react-navigation/master.svg?style=flat-square
|
||||
[build]: https://circleci.com/gh/react-navigation/react-navigation
|
||||
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/react-navigation.svg?style=flat-square
|
||||
[coverage]: https://codecov.io/github/react-navigation/react-navigation
|
||||
[license-badge]: https://img.shields.io/npm/l/@react-navigation/core.svg?style=flat-square
|
||||
[license]: https://opensource.org/licenses/MIT
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
# 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.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.22...@react-navigation/example@5.0.0-alpha.23) (2019-11-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.22](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.21...@react-navigation/example@5.0.0-alpha.22) (2019-11-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.20...@react-navigation/example@5.0.0-alpha.21) (2019-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.19...@react-navigation/example@5.0.0-alpha.20) (2019-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.1...@react-navigation/example@5.0.0-alpha.2) (2019-08-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add native container ([d26b77f](https://github.com/react-navigation/navigation-ex/commit/d26b77f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 5.0.0-alpha.1 (2019-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add margin on left when left button is specified in header ([f1f1541](https://github.com/satya164/navigation-ex/commit/f1f1541))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a simple stack and material tabs integration ([#39](https://github.com/satya164/navigation-ex/issues/39)) ([e0bee10](https://github.com/satya164/navigation-ex/commit/e0bee10))
|
||||
* add hook for deep link support ([35987ae](https://github.com/satya164/navigation-ex/commit/35987ae))
|
||||
* add integration for paper's bottom navigation ([f3b6d1f](https://github.com/satya164/navigation-ex/commit/f3b6d1f))
|
||||
* add native container with back button integration ([#48](https://github.com/satya164/navigation-ex/issues/48)) ([b7735af](https://github.com/satya164/navigation-ex/commit/b7735af))
|
||||
* integrate reanimated based stack ([#42](https://github.com/satya164/navigation-ex/issues/42)) ([dcf57c0](https://github.com/satya164/navigation-ex/commit/dcf57c0))
|
||||
@@ -13,7 +13,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -161,32 +161,33 @@
|
||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:2.0.0@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
|
||||
<orderEntry type="library" name="Gradle: org.webkit:android-jsc:r245459@aar" level="project" />
|
||||
<orderEntry type="module" module-name="android-expo-permissions" />
|
||||
<orderEntry type="module" module-name="android-expo-constants" />
|
||||
<orderEntry type="module" module-name="android-unimodules-image-loader-interface" />
|
||||
<orderEntry type="module" module-name="android-expo-web-browser" />
|
||||
<orderEntry type="module" module-name="android-unimodules-react-native-adapter" />
|
||||
<orderEntry type="module" module-name="android-expo-file-system" />
|
||||
<orderEntry type="module" module-name="android-expo-location" />
|
||||
<orderEntry type="module" module-name="android-expo-error-recovery" />
|
||||
<orderEntry type="module" module-name="android-unimodules-permissions-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-core" />
|
||||
<orderEntry type="module" module-name="android-expo-app-loader-provider" />
|
||||
<orderEntry type="module" module-name="android-expo-font" />
|
||||
<orderEntry type="module" module-name="android-expo-keep-awake" />
|
||||
<orderEntry type="module" module-name="android-expo-linear-gradient" />
|
||||
<orderEntry type="module" module-name="android-expo-sqlite" />
|
||||
<orderEntry type="module" module-name="android-unimodules-barcode-scanner-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-camera-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-constants-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-face-detector-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-file-system-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-font-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-sensors-interface" />
|
||||
<orderEntry type="module" module-name="android-unimodules-task-manager-interface" />
|
||||
<orderEntry type="module" module-name="android-@react-native-community_masked-view" />
|
||||
<orderEntry type="module" module-name="android-react-native-gesture-handler" />
|
||||
<orderEntry type="module" module-name="android-react-native-reanimated" />
|
||||
<orderEntry type="module" module-name="expo-permissions" />
|
||||
<orderEntry type="module" module-name="expo-constants" />
|
||||
<orderEntry type="module" module-name="unimodules-image-loader-interface" />
|
||||
<orderEntry type="module" module-name="expo-web-browser" />
|
||||
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
|
||||
<orderEntry type="module" module-name="expo-file-system" />
|
||||
<orderEntry type="module" module-name="expo-location" />
|
||||
<orderEntry type="module" module-name="expo-error-recovery" />
|
||||
<orderEntry type="module" module-name="unimodules-permissions-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-core" />
|
||||
<orderEntry type="module" module-name="expo-app-loader-provider" />
|
||||
<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-sqlite" />
|
||||
<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-sensors-interface" />
|
||||
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
|
||||
<orderEntry type="module" module-name="@react-native-community_masked-view" />
|
||||
<orderEntry type="module" module-name="react-native-gesture-handler" />
|
||||
<orderEntry type="module" module-name="react-native-reanimated" />
|
||||
<orderEntry type="module" module-name="react-native-restart" />
|
||||
<orderEntry type="module" module-name="react-native-safe-area-context" />
|
||||
<orderEntry type="module" module-name="react-native-screens" />
|
||||
</component>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/example",
|
||||
"description": "Demo app to showcase various functionality of React Navigation",
|
||||
"version": "5.0.0-alpha.23",
|
||||
"version": "5.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-community/masked-view": "0.1.5",
|
||||
"@react-native-community/masked-view": "0.1.7",
|
||||
"@types/react-native-restart": "^0.0.0",
|
||||
"color": "^3.1.2",
|
||||
"expo": "^36.0.2",
|
||||
@@ -21,23 +21,22 @@
|
||||
"react": "~16.9.0",
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.5",
|
||||
"react-native-paper": "^3.5.0",
|
||||
"react-native-reanimated": "^1.4.0",
|
||||
"react-native-restart": "^0.0.13",
|
||||
"react-native-safe-area-context": "^0.6.2",
|
||||
"react-native-screens": "^2.0.0-alpha.33",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-paper": "^3.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-restart": "^0.0.14",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-tab-view": "2.13.0",
|
||||
"react-native-unimodules": "^0.7.0",
|
||||
"react-native-web": "^0.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@expo/webpack-config": "^0.10.9",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@expo/webpack-config": "^0.11.7",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"babel-preset-expo": "^8.0.0",
|
||||
"expo-cli": "^3.11.5",
|
||||
"typescript": "^3.7.4"
|
||||
"expo-cli": "^3.13.8",
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import {
|
||||
RouteProp,
|
||||
ParamListBase,
|
||||
useFocusEffect,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
import { DrawerNavigationProp } from '@react-navigation/drawer';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import {
|
||||
createNativeStackNavigator,
|
||||
NativeStackNavigationProp,
|
||||
} from '@react-navigation/native-stack';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type NativeStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
};
|
||||
|
||||
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
|
||||
|
||||
const Title = ({ children }: { children: React.ReactNode }) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return <Text style={[styles.title, { color: colors.text }]}>{children}</Text>;
|
||||
};
|
||||
|
||||
const Paragraph = ({ children }: { children: React.ReactNode }) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<Text style={[styles.paragraph, { color: colors.text }]}>{children}</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigation;
|
||||
route: RouteProp<NativeStackParams, 'Article'>;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={{ backgroundColor: colors.card }}
|
||||
contentContainerStyle={styles.content}
|
||||
>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Album')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Title>What is Lorem Ipsum?</Title>
|
||||
<Paragraph>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting
|
||||
industry. Lorem Ipsum has been the industry's standard dummy text
|
||||
ever since the 1500s, when an unknown printer took a galley of type and
|
||||
scrambled it to make a type specimen book. It has survived not only five
|
||||
centuries, but also the leap into electronic typesetting, remaining
|
||||
essentially unchanged. It was popularised in the 1960s with the release
|
||||
of Letraset sheets containing Lorem Ipsum passages, and more recently
|
||||
with desktop publishing software like Aldus PageMaker including versions
|
||||
of Lorem Ipsum.
|
||||
</Paragraph>
|
||||
<Title>Where does it come from?</Title>
|
||||
<Paragraph>
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It
|
||||
has roots in a piece of classical Latin literature from 45 BC, making it
|
||||
over 2000 years old. Richard McClintock, a Latin professor at
|
||||
Hampden-Sydney College in Virginia, looked up one of the more obscure
|
||||
Latin words, consectetur, from a Lorem Ipsum passage, and going through
|
||||
the cites of the word in classical literature, discovered the
|
||||
undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33
|
||||
of "de Finibus Bonorum et Malorum" (The Extremes of Good and
|
||||
Evil) by Cicero, written in 45 BC. This book is a treatise on the theory
|
||||
of ethics, very popular during the Renaissance. The first line of Lorem
|
||||
Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in
|
||||
section 1.10.32.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
The standard chunk of Lorem Ipsum used since the 1500s is reproduced
|
||||
below for those interested. Sections 1.10.32 and 1.10.33 from "de
|
||||
Finibus Bonorum et Malorum" by Cicero are also reproduced in their
|
||||
exact original form, accompanied by English versions from the 1914
|
||||
translation by H. Rackham.
|
||||
</Paragraph>
|
||||
<Title>Why do we use it?</Title>
|
||||
<Paragraph>
|
||||
It is a long established fact that a reader will be distracted by the
|
||||
readable content of a page when looking at its layout. The point of
|
||||
using Lorem Ipsum is that it has a more-or-less normal distribution of
|
||||
letters, as opposed to using "Content here, content here",
|
||||
making it look like readable English. Many desktop publishing packages
|
||||
and web page editors now use Lorem Ipsum as their default model text,
|
||||
and a search for "lorem ipsum" will uncover many web sites
|
||||
still in their infancy. Various versions have evolved over the years,
|
||||
sometimes by accident, sometimes on purpose (injected humour and the
|
||||
like).
|
||||
</Paragraph>
|
||||
<Title>Where can I get some?</Title>
|
||||
<Paragraph>
|
||||
There are many variations of passages of Lorem Ipsum available, but the
|
||||
majority have suffered alteration in some form, by injected humour, or
|
||||
randomised words which don't look even slightly believable. If you
|
||||
are going to use a passage of Lorem Ipsum, you need to be sure there
|
||||
isn't anything embarrassing hidden in the middle of text. All the
|
||||
Lorem Ipsum generators on the Internet tend to repeat predefined chunks
|
||||
as necessary, making this the first true generator on the Internet. It
|
||||
uses a dictionary of over 200 Latin words, combined with a handful of
|
||||
model sentence structures, to generate Lorem Ipsum which looks
|
||||
reasonable. The generated Lorem Ipsum is therefore always free from
|
||||
repetition, injected humour, or non-characteristic words etc.
|
||||
</Paragraph>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigation;
|
||||
}) => (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
const NativeStack = createNativeStackNavigator<NativeStackParams>();
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp<ParamListBase>;
|
||||
};
|
||||
|
||||
export default function NativeStackScreen({ navigation }: Props) {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
const drawer = navigation.dangerouslyGetParent() as DrawerNavigationProp<
|
||||
ParamListBase
|
||||
>;
|
||||
|
||||
navigation.setOptions({ gestureEnabled: false });
|
||||
drawer.setOptions({ gestureEnabled: false });
|
||||
|
||||
return () => {
|
||||
navigation.setOptions({ gestureEnabled: true });
|
||||
drawer.setOptions({ gestureEnabled: true });
|
||||
};
|
||||
}, [navigation])
|
||||
);
|
||||
|
||||
return (
|
||||
<NativeStack.Navigator>
|
||||
<NativeStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={{
|
||||
title: 'Lorem Ipsum',
|
||||
headerLargeTitle: true,
|
||||
headerHideShadow: true,
|
||||
}}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
name="Album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
</NativeStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
enableScreens(true);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
content: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
title: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 24,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
paragraph: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
|
||||
export default function NativeStack() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>Not supported on Web :(</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#eceff1',
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
color: '#999',
|
||||
},
|
||||
});
|
||||
@@ -6,8 +6,14 @@ import {
|
||||
Platform,
|
||||
StatusBar,
|
||||
I18nManager,
|
||||
Dimensions,
|
||||
ScaledSize,
|
||||
} from 'react-native';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import RNRestart from 'react-native-restart';
|
||||
import { Updates } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import {
|
||||
Provider as PaperProvider,
|
||||
@@ -17,7 +23,6 @@ import {
|
||||
List,
|
||||
Divider,
|
||||
} from 'react-native-paper';
|
||||
import { Asset } from 'expo-asset';
|
||||
import {
|
||||
InitialState,
|
||||
useLinking,
|
||||
@@ -39,7 +44,6 @@ import {
|
||||
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
import NativeStack from './Screens/NativeStack';
|
||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||
import StackTransparent from './Screens/StackTransparent';
|
||||
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||
@@ -50,10 +54,11 @@ import DynamicTabs from './Screens/DynamicTabs';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import { Updates } from 'expo';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
|
||||
enableScreens();
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
Another: undefined;
|
||||
@@ -67,7 +72,6 @@ type RootStackParamList = {
|
||||
|
||||
const SCREENS = {
|
||||
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
|
||||
NativeStack: { title: 'Native Stack', component: NativeStack },
|
||||
ModalPresentationStack: {
|
||||
title: 'Modal Presentation Stack',
|
||||
component: ModalPresentationStack,
|
||||
@@ -124,7 +128,8 @@ export default function App() {
|
||||
prefixes: LinkingPrefixes,
|
||||
config: {
|
||||
Root: {
|
||||
path: 'root',
|
||||
path: '',
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
@@ -135,7 +140,7 @@ export default function App() {
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
{ Home: '' }
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -193,10 +198,24 @@ export default function App() {
|
||||
};
|
||||
}, [theme.colors, theme.dark]);
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||
|
||||
React.useEffect(() => {
|
||||
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
|
||||
setDimensions(window);
|
||||
};
|
||||
|
||||
Dimensions.addEventListener('change', onDimensionsChange);
|
||||
|
||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||
}, []);
|
||||
|
||||
if (!isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isLargeScreen = dimensions.width > 900;
|
||||
|
||||
return (
|
||||
<PaperProvider theme={paperTheme}>
|
||||
{Platform.OS === 'ios' && (
|
||||
@@ -213,7 +232,7 @@ export default function App() {
|
||||
}
|
||||
theme={theme}
|
||||
>
|
||||
<Drawer.Navigator>
|
||||
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||
<Drawer.Screen
|
||||
name="Root"
|
||||
options={{
|
||||
@@ -237,13 +256,15 @@ export default function App() {
|
||||
name="Home"
|
||||
options={{
|
||||
title: 'Examples',
|
||||
headerLeft: () => (
|
||||
<Appbar.Action
|
||||
color={theme.colors.text}
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
headerLeft: isLargeScreen
|
||||
? undefined
|
||||
: () => (
|
||||
<Appbar.Action
|
||||
color={theme.colors.text}
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const error = console.error;
|
||||
|
||||
console.error = (...args) =>
|
||||
// Supress error messages regarding error boundary in tests
|
||||
// Suppress error messages regarding error boundary in tests
|
||||
/(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
|
||||
args[0]
|
||||
)
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"allowBranch": "master",
|
||||
"conventionalCommits": true,
|
||||
"createRelease": "github",
|
||||
"preId": "alpha",
|
||||
"preDistTag": "next",
|
||||
"message": "chore: publish",
|
||||
"ignoreChanges": [
|
||||
"**/__fixtures__/**",
|
||||
|
||||
31
package.json
31
package.json
@@ -20,28 +20,31 @@
|
||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||
"typescript": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"prerelease": "lerna run clean",
|
||||
"release": "lerna publish",
|
||||
"example": "yarn --cwd example"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/preset-react": "^7.7.0",
|
||||
"@babel/preset-typescript": "^7.7.7",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-flow": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^24.0.25",
|
||||
"codecov": "^3.6.1",
|
||||
"commitlint": "^8.3.4",
|
||||
"core-js": "^3.6.2",
|
||||
"detox": "^15.0.0",
|
||||
"@types/jest": "^25.1.4",
|
||||
"codecov": "^3.6.5",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"detox": "^16.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-satya164": "^3.1.5",
|
||||
"husky": "^4.0.1",
|
||||
"jest": "^24.9.0",
|
||||
"husky": "^4.2.3",
|
||||
"jest": "^25.1.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.7.4"
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "~16.9.0",
|
||||
|
||||
@@ -3,6 +3,116 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.1...@react-navigation/bottom-tabs@5.2.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7485)
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.0...@react-navigation/bottom-tabs@5.2.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.1...@react-navigation/bottom-tabs@5.2.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add safeAreaInsets to bottom tabs ([82af7be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/82af7bed7135e42e24693b48cf7f1c6f9f5a6981))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.0...@react-navigation/bottom-tabs@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.7...@react-navigation/bottom-tabs@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.6...@react-navigation/bottom-tabs@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.5...@react-navigation/bottom-tabs@5.0.6) (2020-02-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.4...@react-navigation/bottom-tabs@5.0.5) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.3...@react-navigation/bottom-tabs@5.0.4) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.2...@react-navigation/bottom-tabs@5.0.3) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.1...@react-navigation/bottom-tabs@5.0.2) (2020-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* initialize keyboard-hiding tabBar to visible=true ([#6740](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6740), [#6799](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6799)) ([0c59ef7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/0c59ef7328c63108a2a2c04e927794d73cead63a))
|
||||
* provide route context to header and bottom tabs ([b6e7e08](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b6e7e08b9a05be6c04ed21e938b9580876239116))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.45...@react-navigation/bottom-tabs@5.0.1) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.45](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.44...@react-navigation/bottom-tabs@5.0.0-alpha.45) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -2,80 +2,4 @@
|
||||
|
||||
Bottom tab navigator for React Navigation following iOS design guidelines.
|
||||
|
||||
Documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/en/next/bottom-tab-navigator.html).
|
||||
|
||||
## Installation
|
||||
|
||||
Open a Terminal in your project's folder and run,
|
||||
|
||||
```sh
|
||||
yarn add @react-navigation/native @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() {
|
||||
return (
|
||||
<BottomTabs.Navigator>
|
||||
<BottomTabs.Screen
|
||||
name="article"
|
||||
component={Article}
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: getTabBarIcon('message-reply'),
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
tabBarLabel: 'Contacts',
|
||||
tabBarIcon: getTabBarIcon('contacts'),
|
||||
}}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
}
|
||||
```
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/bottom-tab-navigator.html).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.2.2",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -10,9 +11,8 @@
|
||||
"android",
|
||||
"tab"
|
||||
],
|
||||
"version": "5.0.0-alpha.45",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -30,26 +30,28 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.33",
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.8.0",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.1",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-safe-area-context": "^0.6.2",
|
||||
"typescript": "^3.7.4"
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.0-alpha.0",
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": "^0.6.0"
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
} from '@react-navigation/native';
|
||||
import BottomTabView from '../views/BottomTabView';
|
||||
import {
|
||||
BottomTabNavigationConfig,
|
||||
@@ -50,6 +48,8 @@ function BottomTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap,
|
||||
typeof BottomTabNavigator
|
||||
>(BottomTabNavigator);
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
Descriptor,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
import { TabNavigationState } from '@react-navigation/routers';
|
||||
|
||||
export type BottomTabNavigationEventMap = {
|
||||
/**
|
||||
@@ -40,19 +41,8 @@ export type BottomTabNavigationProp<
|
||||
TabNavigationState,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Jump to an existing tab.
|
||||
*
|
||||
* @param name Name of the route for the tab.
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
|
||||
...args: ParamList[RouteName] extends undefined | any
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
};
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type BottomTabNavigationOptions = {
|
||||
/**
|
||||
@@ -176,14 +166,24 @@ export type BottomTabBarOptions = {
|
||||
*/
|
||||
tabStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Whether the label is renderd below the icon or beside the icon.
|
||||
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
|
||||
* Whether the label is rendered below the icon or beside the icon.
|
||||
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's rendered beside.
|
||||
*/
|
||||
labelPosition?: LabelPosition;
|
||||
/**
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
adaptive?: boolean;
|
||||
/**
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
} from 'react-native';
|
||||
import {
|
||||
NavigationContext,
|
||||
NavigationRouteContext,
|
||||
CommonActions,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
import { SafeAreaConsumer } from 'react-native-safe-area-context';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
|
||||
import BottomTabItem from './BottomTabItem';
|
||||
import { BottomTabBarProps } from '../types';
|
||||
@@ -42,6 +43,7 @@ export default function BottomTabBar({
|
||||
keyboardHidesTabBar = false,
|
||||
labelPosition,
|
||||
labelStyle,
|
||||
safeAreaInsets,
|
||||
showIcon,
|
||||
showLabel,
|
||||
style,
|
||||
@@ -49,14 +51,19 @@ export default function BottomTabBar({
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
const [layout, setLayout] = React.useState({
|
||||
height: 0,
|
||||
width: dimensions.width,
|
||||
});
|
||||
const [keyboardShown, setKeyboardShown] = React.useState(false);
|
||||
|
||||
const [visible] = React.useState(() => new Animated.Value(0));
|
||||
const [visible] = React.useState(() => new Animated.Value(1));
|
||||
|
||||
const { routes } = state;
|
||||
|
||||
@@ -157,114 +164,122 @@ export default function BottomTabBar({
|
||||
}
|
||||
};
|
||||
|
||||
const defaultInsets = useSafeArea();
|
||||
|
||||
const insets = {
|
||||
top: safeAreaInsets?.top ?? defaultInsets.top,
|
||||
right: safeAreaInsets?.right ?? defaultInsets.right,
|
||||
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [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: keyboardShown ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
|
||||
paddingBottom: insets ? insets.bottom : 0,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [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: keyboardShown ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
|
||||
paddingBottom: insets.bottom,
|
||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
const onPress = () => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
const onPress = () => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
|
||||
if (!focused && !event.defaultPrevented) {
|
||||
navigation.dispatch({
|
||||
...CommonActions.navigate(route.name),
|
||||
target: state.key,
|
||||
});
|
||||
}
|
||||
};
|
||||
if (!focused && !event.defaultPrevented) {
|
||||
navigation.dispatch({
|
||||
...CommonActions.navigate(route.name),
|
||||
target: state.key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onLongPress = () => {
|
||||
navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
const onLongPress = () => {
|
||||
navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={route.key}
|
||||
value={descriptors[route.key].navigation}
|
||||
>
|
||||
<BottomTabItem
|
||||
route={route}
|
||||
focused={focused}
|
||||
horizontal={shouldUseHorizontalLabels()}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
label={label}
|
||||
showIcon={showIcon}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
style={tabStyle}
|
||||
/>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</SafeAreaConsumer>
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={route.key}
|
||||
value={descriptors[route.key].navigation}
|
||||
>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<BottomTabItem
|
||||
route={route}
|
||||
focused={focused}
|
||||
horizontal={shouldUseHorizontalLabels()}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
label={label}
|
||||
showIcon={showIcon}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
style={tabStyle}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
|
||||
import { TabNavigationState } from '@react-navigation/routers';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import { TabNavigationState, useTheme } from '@react-navigation/native';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
|
||||
@@ -10,12 +9,14 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -2,17 +2,25 @@ import * as React from 'react';
|
||||
import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaConsumer,
|
||||
initialWindowSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context';
|
||||
import {
|
||||
getStatusBarHeight,
|
||||
getBottomSpace,
|
||||
} from 'react-native-iphone-x-helper';
|
||||
|
||||
// The provider component for safe area initializes asynchornously
|
||||
// Until the insets are available, there'll be blank screen
|
||||
// To avoid the blank screen, we specify some initial values
|
||||
const initialSafeAreaInsets = {
|
||||
// Approximate values which are good enough for most cases
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
right: 0,
|
||||
left: 0,
|
||||
// If we are on a newer version of the library, we can get the correct window insets
|
||||
// The component might not be filling the window, but this is good enough for most cases
|
||||
...initialWindowSafeAreaInsets,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -26,7 +34,7 @@ export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
// https://github.com/react-navigation/navigation-ex/issues/174
|
||||
// https://github.com/react-navigation/react-navigation/issues/174
|
||||
return children;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"extends": "../../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../native" },
|
||||
{ "path": "../routers" }
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
|
||||
@@ -3,6 +3,108 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.5...@react-navigation/compat@5.0.6) (2020-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add NavigationEvents ([d69b0db](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/d69b0db60455b8789276822ba73f5349db8842d7)), closes [/github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/issuecomment-588268512)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.4...@react-navigation/compat@5.0.5) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.3...@react-navigation/compat@5.0.4) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.2...@react-navigation/compat@5.0.3) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.1...@react-navigation/compat@5.0.2) (2020-02-11)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.34...@react-navigation/compat@5.0.1) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.34](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.33...@react-navigation/compat@5.0.0-alpha.34) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -2,29 +2,4 @@
|
||||
|
||||
Compatibility layer to write navigator definitions in static configuration format.
|
||||
|
||||
Documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/en/next/compatibility.html).
|
||||
|
||||
## Installation
|
||||
|
||||
Open a Terminal in your project's folder and run,
|
||||
|
||||
```sh
|
||||
yarn add @react-navigation/native @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',
|
||||
}
|
||||
);
|
||||
```
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/compatibility.html).
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.0.0-alpha.34",
|
||||
"version": "5.1.4",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/compat",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-navigation/react-navigation/issues"
|
||||
},
|
||||
"homepage": "https://reactnavigation.org/docs/compatibility.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -20,17 +24,16 @@
|
||||
"prepare": "bob build",
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.17",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"react": "~16.9.0",
|
||||
"typescript": "^3.7.4"
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.0-alpha.0",
|
||||
"react": "~16.9.0"
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DrawerActions, DrawerActionType } from '@react-navigation/routers';
|
||||
import { DrawerActions, DrawerActionType } from '@react-navigation/native';
|
||||
|
||||
export function openDrawer(): DrawerActionType {
|
||||
return DrawerActions.openDrawer();
|
||||
|
||||
45
packages/compat/src/NavigationEvents.tsx
Normal file
45
packages/compat/src/NavigationEvents.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
type Props = {
|
||||
onWillFocus?: () => void;
|
||||
onDidFocus?: () => void;
|
||||
onWillBlur?: () => void;
|
||||
onDidBlur?: () => void;
|
||||
};
|
||||
|
||||
export default function NavigationEvents(props: Props) {
|
||||
const navigation = useNavigation();
|
||||
const propsRef = React.useRef(props);
|
||||
|
||||
React.useEffect(() => {
|
||||
propsRef.current = props;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubFocus = navigation.addListener('focus', () => {
|
||||
propsRef.current.onWillFocus?.();
|
||||
});
|
||||
|
||||
const unsubBlur = navigation.addListener('blur', () => {
|
||||
propsRef.current.onWillBlur?.();
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const unsubTransitionEnd = navigation.addListener('transitionEnd', () => {
|
||||
if (navigation.isFocused()) {
|
||||
propsRef.current.onDidFocus?.();
|
||||
} else {
|
||||
propsRef.current.onDidBlur?.();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubFocus();
|
||||
unsubBlur();
|
||||
unsubTransitionEnd();
|
||||
};
|
||||
}, [navigation]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import { StackActions, StackActionType } from '@react-navigation/routers';
|
||||
import {
|
||||
CommonActions,
|
||||
StackActions,
|
||||
StackActionType,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export function reset(): CommonActions.Action {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TabActions, TabActionType } from '@react-navigation/routers';
|
||||
import { TabActions, TabActionType } from '@react-navigation/native';
|
||||
|
||||
export function jumpTo({
|
||||
routeName,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TypedNavigator,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
@@ -15,7 +16,9 @@ import { CompatScreenType, CompatRouteConfig } from './types';
|
||||
export default function createCompatNavigatorFactory<
|
||||
CreateNavigator extends () => TypedNavigator<
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
{},
|
||||
EventMapBase,
|
||||
React.ComponentType<any>
|
||||
>
|
||||
>(createNavigator: CreateNavigator) {
|
||||
|
||||
@@ -2,12 +2,10 @@ import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
|
||||
|
||||
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
|
||||
@@ -24,5 +22,7 @@ function SwitchNavigator(props: Props) {
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigatorFactory<{}, typeof SwitchNavigator>(SwitchNavigator)
|
||||
createNavigatorFactory<TabNavigationState, {}, {}, typeof SwitchNavigator>(
|
||||
SwitchNavigator
|
||||
)
|
||||
);
|
||||
|
||||
@@ -14,4 +14,6 @@ export { default as createSwitchNavigator } from './createSwitchNavigator';
|
||||
export { default as withNavigation } from './withNavigation';
|
||||
export { default as withNavigationFocus } from './withNavigationFocus';
|
||||
|
||||
export { default as NavigationEvents } from './NavigationEvents';
|
||||
|
||||
export * from './types';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"extends": "../../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../native" },
|
||||
{ "path": "../routers" }
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
|
||||
@@ -3,6 +3,132 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.2...@react-navigation/core@5.2.3) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.1...@react-navigation/core@5.2.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.0...@react-navigation/core@5.2.1) (2020-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix links for documentation ([5bb0f40](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5bb0f405ceb5755d39a0b5b1f2e4ecee0da051bc))
|
||||
* move updating state to useEffect ([2dfa4f3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2dfa4f36293a2acb718814f6b2fa79d7c7ddf09c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.6...@react-navigation/core@5.2.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.5...@react-navigation/core@5.1.6) (2020-02-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid emitting focus events twice ([f167008](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f16700812f3757713b04ca3a860209795b4a6c44)), closes [#6749](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6749)
|
||||
* preserve screen order with numeric names ([125bd70](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/125bd70e49b708d936a2eee72ba5cb92eacf26a9)), closes [#6900](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6900)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.4...@react-navigation/core@5.1.5) (2020-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* show descriptive error for invalid return for useFocusEffect ([1a28c29](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1a28c299b5e3f0805eb6e9ea3cf5e9cc90c7a280))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.3...@react-navigation/core@5.1.4) (2020-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* link to migration guide on invalid usage ([c5fcfbd](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c5fcfbd4277541e131acbaa7602a5d7e636afebb))
|
||||
* return '/' for empty paths ([aaf01e0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/aaf01e01e7b47b375f68aebe6d0effe82878d060))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.2...@react-navigation/core@5.1.3) (2020-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return false for canGoBack if navigator hasn't finished mounting ([c8ac5fa](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c8ac5fab61cf127985431075a3c59c1f3dfa42da))
|
||||
* throw a descriptive error if navigation object hasn't initialized ([b6accd0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/b6accd03f69dd438e595094d8bf8599cc12e71ac))
|
||||
* update links in error messages ([f964200](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f964200b0dcbc19d5f88ad2dd1eb8e5576973497))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.1...@react-navigation/core@5.1.2) (2020-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix false positives for circular object check ([030c63c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/030c63c89fe447aa484b767831c8f8e26e90431c)), closes [#6827](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6827)
|
||||
* static container memo check ([#6825](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6825)) ([2bf0958](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2bf09585021470f500d967e9242836840efe970f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.0...@react-navigation/core@5.1.1) (2020-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't cleanup state on switching navigator ([359ae1b](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/359ae1bfacec5ef880b3944f465c881aedb16767))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.43...@react-navigation/core@5.1.0) (2020-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add some links in the error messages ([13b4e07](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/13b4e07348496f7cb516d625b44a6a7d310ef9af))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support ignoring empty path strings ([#349](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/349)) ([61b1134](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/61b1134f90310390fe819622c1f33273fca0bd42))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.43](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.42...@react-navigation/core@5.0.0-alpha.43) (2020-02-04)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# `@react-navigation/core`
|
||||
|
||||
Core utilities for building navigators.
|
||||
Core utilities for building navigators independent of the platform.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.2.3",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
"react-navigation"
|
||||
],
|
||||
"version": "5.0.0-alpha.43",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/core",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/core",
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-navigation/react-navigation/issues"
|
||||
},
|
||||
"homepage": "https://reactnavigation.org",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -25,27 +29,27 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.1.1",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"query-string": "^6.9.0",
|
||||
"react-is": "^16.12.0",
|
||||
"query-string": "^6.11.1",
|
||||
"react-is": "^16.13.0",
|
||||
"shortid": "^2.2.15",
|
||||
"use-subscription": "^1.3.0"
|
||||
"use-subscription": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@react-native-community/bob": "^0.8.0",
|
||||
"@types/react": "^16.9.17",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/use-subscription": "^1.0.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-test-renderer": "~16.12.0",
|
||||
"typescript": "^3.7.4"
|
||||
"react-test-renderer": "~16.9.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "~16.9.0"
|
||||
"react": "*"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import * as CommonActions from './CommonActions';
|
||||
import {
|
||||
CommonActions,
|
||||
Route,
|
||||
NavigationState,
|
||||
InitialState,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
} from '@react-navigation/routers';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import useFocusedListeners from './useFocusedListeners';
|
||||
@@ -7,43 +14,42 @@ import useDevTools from './useDevTools';
|
||||
import useStateGetters from './useStateGetters';
|
||||
import isSerializable from './isSerializable';
|
||||
|
||||
import {
|
||||
Route,
|
||||
NavigationState,
|
||||
InitialState,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
NavigationContainerRef,
|
||||
NavigationContainerProps,
|
||||
} from './types';
|
||||
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useSyncState from './useSyncState';
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
const MISSING_CONTEXT_ERROR =
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
|
||||
|
||||
const NOT_INITIALIZED_ERROR =
|
||||
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
|
||||
|
||||
export const NavigationStateContext = React.createContext<{
|
||||
isDefault?: true;
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
getKey: () => string | undefined;
|
||||
setKey: (key: string) => void;
|
||||
getState: () => NavigationState | PartialState<NavigationState> | undefined;
|
||||
setState: (
|
||||
state: NavigationState | PartialState<NavigationState> | undefined
|
||||
) => void;
|
||||
key?: string;
|
||||
performTransaction: (action: () => void) => void;
|
||||
}>({
|
||||
isDefault: true,
|
||||
|
||||
get getKey(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
},
|
||||
get setKey(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
},
|
||||
get getState(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
},
|
||||
get setState(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
},
|
||||
get performTransaction(): any {
|
||||
throw new Error(MISSING_CONTEXT_ERROR);
|
||||
},
|
||||
});
|
||||
|
||||
let hasWarnedForSerialization = false;
|
||||
@@ -102,70 +108,35 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
|
||||
if (!parent.isDefault && !independent) {
|
||||
throw new Error(
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely."
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
|
||||
);
|
||||
}
|
||||
|
||||
const [state, setNavigationState] = React.useState<State>(() =>
|
||||
const [state, getState, setState] = useSyncState<State>(() =>
|
||||
getPartialState(initialState == null ? undefined : initialState)
|
||||
);
|
||||
|
||||
const navigationStateRef = React.useRef<State>();
|
||||
const transactionStateRef = React.useRef<State | null>(null);
|
||||
const isTransactionActiveRef = React.useRef<boolean>(false);
|
||||
const isFirstMountRef = React.useRef<boolean>(true);
|
||||
const skipTrackingRef = React.useRef<boolean>(false);
|
||||
|
||||
const performTransaction = React.useCallback((callback: () => void) => {
|
||||
if (isTransactionActiveRef.current) {
|
||||
throw new Error(
|
||||
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
|
||||
);
|
||||
}
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
|
||||
setNavigationState((navigationState: State) => {
|
||||
isTransactionActiveRef.current = true;
|
||||
transactionStateRef.current = navigationState;
|
||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
isTransactionActiveRef.current = false;
|
||||
}
|
||||
|
||||
return transactionStateRef.current;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getState = React.useCallback(
|
||||
() =>
|
||||
transactionStateRef.current !== null
|
||||
? transactionStateRef.current
|
||||
: navigationStateRef.current,
|
||||
[]
|
||||
);
|
||||
|
||||
const setState = React.useCallback((navigationState: State) => {
|
||||
if (transactionStateRef.current === null) {
|
||||
throw new Error(
|
||||
"Any 'setState' calls need to be done inside 'performTransaction'"
|
||||
);
|
||||
}
|
||||
|
||||
transactionStateRef.current = navigationState;
|
||||
const setKey = React.useCallback((key: string) => {
|
||||
navigatorKeyRef.current = key;
|
||||
}, []);
|
||||
|
||||
const reset = React.useCallback(
|
||||
(state: NavigationState) => {
|
||||
performTransaction(() => {
|
||||
skipTrackingRef.current = true;
|
||||
setState(state);
|
||||
});
|
||||
skipTrackingRef.current = true;
|
||||
setState(state);
|
||||
},
|
||||
[performTransaction, setState]
|
||||
[setState]
|
||||
);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
enabled: false,
|
||||
name: '@react-navigation',
|
||||
reset,
|
||||
state,
|
||||
@@ -181,10 +152,18 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationAction)
|
||||
) => {
|
||||
if (listeners[0] == null) {
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
|
||||
listeners[0](navigation => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
if (listeners[0] == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result, handled } = listeners[0](navigation =>
|
||||
navigation.canGoBack()
|
||||
);
|
||||
@@ -198,12 +177,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
|
||||
const resetRoot = React.useCallback(
|
||||
(state?: PartialState<NavigationState> | NavigationState) => {
|
||||
performTransaction(() => {
|
||||
trackAction('@@RESET_ROOT');
|
||||
setState(state);
|
||||
});
|
||||
trackAction('@@RESET_ROOT');
|
||||
setState(state);
|
||||
},
|
||||
[performTransaction, setState, trackAction]
|
||||
[setState, trackAction]
|
||||
);
|
||||
|
||||
const getRootState = React.useCallback(() => {
|
||||
@@ -244,11 +221,12 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
state,
|
||||
performTransaction,
|
||||
getState,
|
||||
setState,
|
||||
getKey,
|
||||
setKey,
|
||||
}),
|
||||
[getState, performTransaction, setState, state]
|
||||
[getKey, getState, setKey, setState, state]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -261,7 +239,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
hasWarnedForSerialization = true;
|
||||
|
||||
console.warn(
|
||||
"We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use functions in your options, you can use 'navigation.setOptions' instead."
|
||||
"Non-serializable values were found in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -277,15 +255,12 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
trackState(getRootState);
|
||||
}
|
||||
|
||||
navigationStateRef.current = state;
|
||||
transactionStateRef.current = null;
|
||||
|
||||
if (!isFirstMountRef.current && onStateChange) {
|
||||
onStateChange(getRootState());
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
}, [state, onStateChange, trackState, getRootState, emitter]);
|
||||
}, [onStateChange, trackState, getRootState, emitter, state]);
|
||||
|
||||
return (
|
||||
<NavigationBuilderContext.Provider value={builderContext}>
|
||||
|
||||
@@ -4,7 +4,7 @@ type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container.`;
|
||||
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container. See https://reactnavigation.org/docs/nesting-navigators for a guide on nesting.`;
|
||||
|
||||
export const SingleNavigatorContext = React.createContext<
|
||||
| {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
} from './types';
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationHelpers } from './types';
|
||||
|
||||
export type ChildActionListener = (
|
||||
action: NavigationAction,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationProp, ParamListBase } from './types';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import { NavigationProp } from './types';
|
||||
|
||||
/**
|
||||
* Context which holds the navigation prop for a screen.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Route } from './types';
|
||||
import { Route } from '@react-navigation/routers';
|
||||
|
||||
/**
|
||||
* Context which holds the route prop for a screen.
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Route,
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
PartialState,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import {
|
||||
Route,
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
NavigationProp,
|
||||
RouteConfig,
|
||||
PartialState,
|
||||
} from './types';
|
||||
import { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||
|
||||
type Props<State extends NavigationState, ScreenOptions extends object> = {
|
||||
screen: RouteConfig<ParamListBase, string, ScreenOptions>;
|
||||
type Props<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
|
||||
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||
route: Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
@@ -29,15 +32,22 @@ type Props<State extends NavigationState, ScreenOptions extends object> = {
|
||||
*/
|
||||
export default function SceneView<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
screen,
|
||||
route,
|
||||
navigation,
|
||||
getState,
|
||||
setState,
|
||||
}: Props<State, ScreenOptions>) {
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
}: Props<State, ScreenOptions, EventMap>) {
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
|
||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||
|
||||
const setKey = React.useCallback((key: string) => {
|
||||
navigatorKeyRef.current = key;
|
||||
}, []);
|
||||
|
||||
const getCurrentState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
@@ -65,16 +75,10 @@ export default function SceneView<
|
||||
state: route.state,
|
||||
getState: getCurrentState,
|
||||
setState: setCurrentState,
|
||||
performTransaction,
|
||||
key: route.key,
|
||||
getKey,
|
||||
setKey,
|
||||
}),
|
||||
[
|
||||
getCurrentState,
|
||||
performTransaction,
|
||||
route.key,
|
||||
route.state,
|
||||
setCurrentState,
|
||||
]
|
||||
[getCurrentState, getKey, route.state, setCurrentState, setKey]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RouteConfig, ParamListBase } from './types';
|
||||
import { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import { RouteConfig, EventMapBase } from './types';
|
||||
|
||||
/**
|
||||
* Empty component used for specifying route configuration.
|
||||
@@ -6,8 +7,10 @@ import { RouteConfig, ParamListBase } from './types';
|
||||
export default function Screen<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
ScreenOptions extends object
|
||||
>(_: RouteConfig<ParamList, RouteName, ScreenOptions>) {
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>(_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>) {
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,19 @@ function StaticContainer(props: any) {
|
||||
}
|
||||
|
||||
export default React.memo(StaticContainer, (prevProps: any, nextProps: any) => {
|
||||
for (const prop in prevProps) {
|
||||
if (prop === 'children') {
|
||||
const prevPropKeys = Object.keys(prevProps);
|
||||
const nextPropKeys = Object.keys(nextProps);
|
||||
|
||||
if (prevPropKeys.length !== nextPropKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of prevPropKeys) {
|
||||
if (key === 'children') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevProps[prop] !== nextProps[prop]) {
|
||||
if (prevProps[key] !== nextProps[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { act, render } from 'react-native-testing-library';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import BaseNavigationContainer, {
|
||||
NavigationStateContext,
|
||||
} from '../BaseNavigationContainer';
|
||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import Screen from '../Screen';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
Router,
|
||||
NavigationContainerRef,
|
||||
} from '../types';
|
||||
import { NavigationContainerRef } from '../types';
|
||||
|
||||
it('throws when getState is accessed without a container', () => {
|
||||
expect.assertions(1);
|
||||
@@ -28,7 +28,7 @@ it('throws when getState is accessed without a container', () => {
|
||||
const element = <Test />;
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,78 +47,7 @@ it('throws when setState is accessed without a container', () => {
|
||||
const element = <Test />;
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when performTransaction is accessed without a container', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const Test = () => {
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
performTransaction;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const element = <Test />;
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when setState is called outside performTransaction', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const Test = () => {
|
||||
const { setState } = React.useContext(NavigationStateContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
setState(undefined);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<Test />
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"Any 'setState' calls need to be done inside 'performTransaction'"
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when nesting performTransaction', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const Test = () => {
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
performTransaction(() => {
|
||||
performTransaction(() => {});
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<Test />
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -501,3 +430,51 @@ it('emits state events when the state changes', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if there is no navigator rendered', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = <BaseNavigationContainer ref={ref} children={null} />;
|
||||
|
||||
render(element);
|
||||
|
||||
act(() => {
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("throws if the ref hasn't finished initializing", () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestScreen = () => {
|
||||
React.useEffect(() => {
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
});
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import Screen from '../Screen';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
it('throws if NAVIGATE dispatched neither key nor name', () => {
|
||||
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.navigate({});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer onStateChange={onStateChange}>
|
||||
<TestNavigator initialRouteName="foo">
|
||||
<Screen
|
||||
name="foo"
|
||||
component={FooScreen}
|
||||
initialParams={{ count: 10 }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
'While calling navigate with an object as the argument, you need to specify name or key'
|
||||
);
|
||||
});
|
||||
@@ -49,3 +49,27 @@ it('updates element if any props changed', () => {
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`"second"`);
|
||||
});
|
||||
|
||||
it('updates element if any props are added', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const Test = ({ label }: any) => {
|
||||
return label;
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<StaticContainer count={42}>
|
||||
<Test label="first" />
|
||||
</StaticContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`"first"`);
|
||||
|
||||
root.update(
|
||||
<StaticContainer count={42} moreCounts={12}>
|
||||
<Test label="second" />
|
||||
</StaticContainer>
|
||||
);
|
||||
|
||||
expect(root).toMatchInlineSnapshot(`"second"`);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import BaseRouter from '../../BaseRouter';
|
||||
import {
|
||||
BaseRouter,
|
||||
Router,
|
||||
CommonAction,
|
||||
CommonNavigationAction,
|
||||
NavigationState,
|
||||
Route,
|
||||
DefaultRouterOptions,
|
||||
} from '../../types';
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
export type MockActions = CommonAction | { type: 'NOOP' | 'UPDATE' };
|
||||
export type MockActions = CommonNavigationAction | { type: 'NOOP' | 'UPDATE' };
|
||||
|
||||
export const MockRouterKey = { current: 0 };
|
||||
|
||||
|
||||
@@ -41,6 +41,43 @@ it('gets navigate action from state', () => {
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
|
||||
expect(
|
||||
getActionFromState({
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
params: {
|
||||
screen: 'quz',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets reset action from state', () => {
|
||||
@@ -53,13 +90,7 @@ it('gets reset action from state', () => {
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
routes: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -68,8 +99,6 @@ it('gets reset action from state', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: state,
|
||||
type: 'RESET_ROOT',
|
||||
});
|
||||
expect(getActionFromState(state)).toBe(undefined);
|
||||
expect(getActionFromState({ routes: [] })).toBe(undefined);
|
||||
});
|
||||
|
||||
@@ -236,12 +236,14 @@ it('handles state with config with nested screens and unused configs', () => {
|
||||
});
|
||||
|
||||
it('handles nested object with stringify in it', () => {
|
||||
const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true';
|
||||
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
@@ -278,23 +280,16 @@ it('handles nested object with stringify in it', () => {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
name: 'Baz',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -399,3 +394,126 @@ it('handles nested object for second route depth and and path and stringify in r
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('ignores empty string paths', () => {
|
||||
const path = '/bar';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: '',
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
},
|
||||
},
|
||||
Bar: 'bar',
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [{ name: 'Bar' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('cuts nested configs too', () => {
|
||||
const path = '/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Bar: '',
|
||||
},
|
||||
},
|
||||
Baz: { path: 'baz' },
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Baz' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles empty path at the end', () => {
|
||||
const path = '/bar';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Bar: 'bar',
|
||||
},
|
||||
},
|
||||
Baz: { path: '' },
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Baz' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('returns "/" for empty path', () => {
|
||||
const config = {
|
||||
Foo: {
|
||||
path: '',
|
||||
screens: {
|
||||
Bar: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/');
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import Screen from '../Screen';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import useNavigation from '../useNavigation';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import { NavigationState, NavigationContainerRef } from '../types';
|
||||
import { NavigationContainerRef } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -371,7 +372,7 @@ it("doesn't update state if action wasn't handled", () => {
|
||||
expect(onStateChange).toBeCalledTimes(0);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
|
||||
"The action 'INVALID' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
@@ -771,6 +772,60 @@ it('gives access to internal state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves order of screens in state with non-numeric names', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={jest.fn()} />
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
<Screen name="baz" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves order of screens in state with numeric names', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="4" component={jest.fn()} />
|
||||
<Screen name="7" component={jest.fn()} />
|
||||
<Screen name="1" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'4',
|
||||
'7',
|
||||
'1',
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws if navigator doesn't have any screens", () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
useNavigationBuilder(MockRouter, props);
|
||||
@@ -1030,7 +1085,7 @@ it('throws descriptive error for invalid screen component', () => {
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a a valid React Component."
|
||||
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a valid React Component."
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -51,20 +51,55 @@ it('returns false for non-serializable object', () => {
|
||||
});
|
||||
|
||||
it('returns false for circular references', () => {
|
||||
const o = {
|
||||
index: 0,
|
||||
key: '7',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
},
|
||||
],
|
||||
const x = {
|
||||
a: 1,
|
||||
b: { b1: 1 },
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
o.routes[0].state = o;
|
||||
x.b.b2 = x;
|
||||
// @ts-ignore
|
||||
x.c = x.b;
|
||||
|
||||
expect(isSerializable(o)).toBe(false);
|
||||
expect(isSerializable(x)).toBe(false);
|
||||
|
||||
const y = [
|
||||
{
|
||||
label: 'home',
|
||||
children: [{ label: 'product' }],
|
||||
},
|
||||
{ label: 'about', extend: {} },
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
y[0].children[0].parent = y[0];
|
||||
// @ts-ignore
|
||||
y[1].extend.home = y[0].children[0];
|
||||
|
||||
expect(isSerializable(y)).toBe(false);
|
||||
|
||||
const z = {
|
||||
name: 'sun',
|
||||
child: [{ name: 'flower' }],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
z.child[0].parent = z;
|
||||
|
||||
expect(isSerializable(z)).toBe(false);
|
||||
});
|
||||
|
||||
it("doesn't fail if same object used multiple times", () => {
|
||||
const o = { foo: 'bar' };
|
||||
|
||||
expect(
|
||||
isSerializable({
|
||||
baz: 'bax',
|
||||
first: o,
|
||||
second: o,
|
||||
stuff: {
|
||||
b: o,
|
||||
},
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
@@ -7,7 +12,6 @@ import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import { DefaultRouterOptions, NavigationState, Router } from '../types';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import { Router, NavigationState } from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
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 => {
|
||||
@@ -188,10 +188,12 @@ it('fires focus and blur events in nested navigator', () => {
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(0);
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||
|
||||
act(() => parent.current.navigate('nested'));
|
||||
|
||||
expect(firstBlurCallback).toBeCalledTimes(1);
|
||||
expect(secondBlurCallback).toBeCalledTimes(1);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(0);
|
||||
expect(fourthFocusCallback).toBeCalledTimes(1);
|
||||
|
||||
@@ -199,6 +201,35 @@ it('fires focus and blur events in nested navigator', () => {
|
||||
|
||||
expect(fourthBlurCallback).toBeCalledTimes(1);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(1);
|
||||
|
||||
act(() => parent.current.navigate('first'));
|
||||
|
||||
expect(firstFocusCallback).toBeCalledTimes(2);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(1);
|
||||
|
||||
act(() => parent.current.navigate('nested', { screen: 'fourth' }));
|
||||
|
||||
expect(fourthFocusCallback).toBeCalledTimes(2);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(1);
|
||||
expect(firstBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
act(() => parent.current.navigate('nested', { screen: 'third' }));
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(2);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
// Make sure nothing else has changed
|
||||
expect(firstFocusCallback).toBeCalledTimes(2);
|
||||
expect(firstBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
expect(secondBlurCallback).toBeCalledTimes(1);
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(2);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(1);
|
||||
|
||||
expect(fourthFocusCallback).toBeCalledTimes(2);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('fires blur event when a route is removed with a delay', async () => {
|
||||
@@ -331,7 +362,7 @@ it('fires blur event when a route is removed with a delay', async () => {
|
||||
expect(blurCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires custom events', () => {
|
||||
it('fires custom events added with addListener', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
@@ -378,10 +409,13 @@ it('fires custom events', () => {
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(0);
|
||||
|
||||
const target =
|
||||
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({
|
||||
type: eventName,
|
||||
target: ref.current.state.routes[ref.current.state.routes.length - 1].key,
|
||||
target,
|
||||
data: 42,
|
||||
});
|
||||
});
|
||||
@@ -391,6 +425,7 @@ it('fires custom events', () => {
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||
|
||||
@@ -398,11 +433,197 @@ it('fires custom events', () => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(secondCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[1][0].target).toBe(undefined);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(1);
|
||||
expect(secondCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it("doesn't call same listener multiple times with addListener", () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||
MockRouter,
|
||||
props
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = ({ navigation }: any) => {
|
||||
React.useEffect(() => navigation.addListener(eventName, callback), [
|
||||
navigation,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen name="first" component={Test} />
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(callback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires custom events added with listeners prop', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const firstCallback = jest.fn();
|
||||
const secondCallback = jest.fn();
|
||||
const thirdCallback = jest.fn();
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen
|
||||
name="first"
|
||||
listeners={{ someSuperCoolEvent: firstCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="second"
|
||||
listeners={{ someSuperCoolEvent: secondCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="third"
|
||||
listeners={{ someSuperCoolEvent: thirdCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(0);
|
||||
|
||||
const target =
|
||||
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({
|
||||
type: eventName,
|
||||
target,
|
||||
data: 42,
|
||||
});
|
||||
});
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(secondCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[1][0].target).toBe(undefined);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(1);
|
||||
expect(secondCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it("doesn't call same listener multiple times with listeners", () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen
|
||||
name="first"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="second"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="third"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(callback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('has option to prevent default', () => {
|
||||
expect.assertions(5);
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ it('throws if called outside a navigation context', () => {
|
||||
const Test = () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
expect(() => useNavigation()).toThrow(
|
||||
"We couldn't find a navigation object. Is your component inside a navigator?"
|
||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import useNavigationState from '../useNavigationState';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter from './__fixtures__/MockRouter';
|
||||
import { NavigationState } from '../types';
|
||||
|
||||
it('gets the current navigation state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import {
|
||||
Router,
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
@@ -7,7 +12,6 @@ import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import { Router, DefaultRouterOptions, NavigationState } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -370,7 +374,7 @@ it('logs error if no navigator handled the action', () => {
|
||||
render(element).update(element);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
|
||||
"The action 'UNKNOWN' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import Screen from './Screen';
|
||||
import { ParamListBase, TypedNavigator } from './types';
|
||||
import { TypedNavigator, EventMapBase } from './types';
|
||||
|
||||
/**
|
||||
* Higher order component to create a `Navigator` and `Screen` pair.
|
||||
@@ -10,17 +11,21 @@ import { ParamListBase, TypedNavigator } from './types';
|
||||
* @returns Factory method to create a `Navigator` and `Screen` pair.
|
||||
*/
|
||||
export default function createNavigatorFactory<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase,
|
||||
NavigatorComponent extends React.ComponentType<any>
|
||||
>(Navigator: NavigatorComponent) {
|
||||
return function<ParamList extends ParamListBase>(): TypedNavigator<
|
||||
ParamList,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap,
|
||||
typeof Navigator
|
||||
> {
|
||||
if (arguments[0] !== undefined) {
|
||||
throw new Error(
|
||||
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5?"
|
||||
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5? See https://reactnavigation.org/docs/upgrading-from-4.x for migration guide."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,54 @@
|
||||
import { PartialState, NavigationState } from './types';
|
||||
import { PartialState, NavigationState } from '@react-navigation/routers';
|
||||
|
||||
type NavigateParams = {
|
||||
screen?: string;
|
||||
params?: NavigateParams;
|
||||
};
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: 'NAVIGATE';
|
||||
payload: { name: string; params: NavigateParams };
|
||||
}
|
||||
| {
|
||||
type: 'RESET_ROOT';
|
||||
payload: PartialState<NavigationState>;
|
||||
};
|
||||
type NavigateAction = {
|
||||
type: 'NAVIGATE';
|
||||
payload: { name: string; params: NavigateParams };
|
||||
};
|
||||
|
||||
export default function getActionFromState(
|
||||
state: PartialState<NavigationState>
|
||||
): Action {
|
||||
let payload: { name: string; params: NavigateParams } | undefined;
|
||||
|
||||
if (state.routes.length === 1) {
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
let route = state.routes[0];
|
||||
|
||||
payload = {
|
||||
name: route.name,
|
||||
params: { ...route.params },
|
||||
};
|
||||
|
||||
let current = state.routes[0].state;
|
||||
let params = payload.params;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 1) {
|
||||
route = current.routes[0];
|
||||
params.screen = route.name;
|
||||
|
||||
if (route.state) {
|
||||
params.params = { ...route.params };
|
||||
params = params.params;
|
||||
} else {
|
||||
params.params = route.params;
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
} else {
|
||||
payload = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
): NavigateAction | undefined {
|
||||
if (state.routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (payload) {
|
||||
return {
|
||||
type: 'NAVIGATE',
|
||||
payload,
|
||||
};
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
let route = state.routes[state.routes.length - 1];
|
||||
|
||||
let payload: { name: string; params: NavigateParams } = {
|
||||
name: route.name,
|
||||
params: { ...route.params },
|
||||
};
|
||||
|
||||
let current = route.state;
|
||||
let params = payload.params;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
route = current.routes[current.routes.length - 1];
|
||||
params.screen = route.name;
|
||||
|
||||
if (route.state) {
|
||||
params.params = { ...route.params };
|
||||
params = params.params;
|
||||
} else {
|
||||
params.params = route.params;
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'RESET_ROOT',
|
||||
payload: state,
|
||||
type: 'NAVIGATE',
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import queryString from 'query-string';
|
||||
import { NavigationState, PartialState, Route } from './types';
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||
|
||||
@@ -66,73 +70,98 @@ export default function getPathFromState(
|
||||
pattern = currentOptions[route.name] as string;
|
||||
break;
|
||||
} else if (typeof currentOptions[route.name] === 'object') {
|
||||
if (route.state === undefined) {
|
||||
// if there is no `screens` property, we return pattern
|
||||
if (
|
||||
!(currentOptions[route.name] as {
|
||||
screens: Options;
|
||||
}).screens
|
||||
) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
break;
|
||||
} else {
|
||||
if (!(currentOptions[route.name] as { screens?: Options }).screens) {
|
||||
throw Error('Wrong Options object passed');
|
||||
// if it is the end of state, we return pattern
|
||||
if (route.state === undefined) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
break;
|
||||
} else {
|
||||
index =
|
||||
typeof route.state.index === 'number' ? route.state.index : 0;
|
||||
const nextRoute = route.state.routes[index];
|
||||
const deeperConfig = (currentOptions[route.name] as {
|
||||
screens: Options;
|
||||
}).screens;
|
||||
// if there is config for next route name, we go deeper
|
||||
if (nextRoute.name in deeperConfig) {
|
||||
route = nextRoute as Route<string> & { state?: State };
|
||||
currentOptions = deeperConfig;
|
||||
} else {
|
||||
// if not, there is no sense in going deeper in config
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentOptions = (currentOptions[route.name] as { screens: Options })
|
||||
.screens;
|
||||
index = typeof route.state.index === 'number' ? route.state.index : 0;
|
||||
route = route.state.routes[index] as Route<string> & {
|
||||
state?: State;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config =
|
||||
currentOptions[route.name] !== undefined
|
||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
||||
.stringify
|
||||
// we don't add empty path strings to path
|
||||
if (pattern !== '') {
|
||||
const config =
|
||||
currentOptions[route.name] !== undefined
|
||||
? (currentOptions[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?.[key] ? config[key](value) : String(value);
|
||||
return acc;
|
||||
}, {})
|
||||
: 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?.[key] ? config[key](value) : String(value);
|
||||
return acc;
|
||||
}, {})
|
||||
: undefined;
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map(p => {
|
||||
const name = p.replace(/^:/, '');
|
||||
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
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
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
// 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
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
}
|
||||
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
}
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} else if (params) {
|
||||
const query = queryString.stringify(params);
|
||||
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} else if (params) {
|
||||
const query = queryString.stringify(params);
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
}
|
||||
|
||||
path =
|
||||
path !== '/' && path.slice(path.length - 1) === '/'
|
||||
? path.slice(0, -1)
|
||||
: path;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import escape from 'escape-string-regexp';
|
||||
import queryString from 'query-string';
|
||||
import { NavigationState, PartialState, InitialState } from './types';
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
InitialState,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
type ParseConfig = Record<string, (value: string) => any>;
|
||||
|
||||
@@ -211,12 +215,14 @@ function createNormalizedConfigs(
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||
configs.push(createConfigItem(routeNames, value));
|
||||
if (value !== '') {
|
||||
configs.push(createConfigItem(routeNames, value));
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||
// it can have `path` property and
|
||||
// it could have `screens` prop which has nested configs
|
||||
if (value.path) {
|
||||
if (value.path && value.path !== '') {
|
||||
configs.push(createConfigItem(routeNames, value.path, value.parse));
|
||||
}
|
||||
if (value.screens) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import * as CommonActions from './CommonActions';
|
||||
export * from '@react-navigation/routers';
|
||||
|
||||
export { CommonActions };
|
||||
|
||||
export { default as BaseRouter } from './BaseRouter';
|
||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const isSerializableWithoutCircularReference = (
|
||||
o: { [key: string]: any },
|
||||
seen = new Set<any>()
|
||||
seen: Set<any>
|
||||
): boolean => {
|
||||
if (
|
||||
o === undefined ||
|
||||
@@ -27,13 +27,13 @@ const isSerializableWithoutCircularReference = (
|
||||
|
||||
if (Array.isArray(o)) {
|
||||
for (const it of o) {
|
||||
if (!isSerializableWithoutCircularReference(it, seen)) {
|
||||
if (!isSerializableWithoutCircularReference(it, new Set<any>(seen))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const key in o) {
|
||||
if (!isSerializableWithoutCircularReference(o[key], seen)) {
|
||||
if (!isSerializableWithoutCircularReference(o[key], new Set<any>(seen))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,5 +43,5 @@ const isSerializableWithoutCircularReference = (
|
||||
};
|
||||
|
||||
export default function isSerializable(o: { [key: string]: any }) {
|
||||
return isSerializableWithoutCircularReference(o);
|
||||
return isSerializableWithoutCircularReference(o, new Set<any>());
|
||||
}
|
||||
|
||||
@@ -1,105 +1,13 @@
|
||||
import * as CommonActions from './CommonActions';
|
||||
import * as React from 'react';
|
||||
|
||||
export type CommonAction = CommonActions.Action;
|
||||
|
||||
export type NavigationState = {
|
||||
/**
|
||||
* Unique key for the navigation state.
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* Index of the currently focused route.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
* List of valid route names as defined in the screen components.
|
||||
*/
|
||||
routeNames: string[];
|
||||
/**
|
||||
* Alternative entries for history.
|
||||
*/
|
||||
history?: unknown[];
|
||||
/**
|
||||
* List of rendered routes.
|
||||
*/
|
||||
routes: (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.
|
||||
*/
|
||||
stale: false;
|
||||
};
|
||||
|
||||
export type InitialState = Partial<
|
||||
Omit<NavigationState, 'stale' | 'routes'>
|
||||
> & {
|
||||
routes: (Omit<Route<string>, 'key'> & { state?: InitialState })[];
|
||||
};
|
||||
|
||||
export type PartialState<State extends NavigationState> = Partial<
|
||||
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
|
||||
> & {
|
||||
stale?: true;
|
||||
type?: string;
|
||||
routes: (Omit<Route<string>, 'key'> & {
|
||||
key?: string;
|
||||
state?: InitialState;
|
||||
})[];
|
||||
};
|
||||
|
||||
export type Route<RouteName extends string> = {
|
||||
/**
|
||||
* Unique key for the route.
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* User-provided name for the route.
|
||||
*/
|
||||
name: RouteName;
|
||||
/**
|
||||
* Params for the route.
|
||||
*/
|
||||
params?: object;
|
||||
};
|
||||
|
||||
export type NavigationAction = {
|
||||
/**
|
||||
* Type of the action (e.g. `NAVIGATE`)
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Additional data for the action
|
||||
*/
|
||||
payload?: object;
|
||||
/**
|
||||
* Key of the route which dispatched this action.
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* Key of the navigator which should handle this action.
|
||||
*/
|
||||
target?: string;
|
||||
};
|
||||
|
||||
export type ActionCreators<Action extends NavigationAction> = {
|
||||
[key: string]: (...args: any) => Action;
|
||||
};
|
||||
|
||||
export type DefaultRouterOptions = {
|
||||
/**
|
||||
* Name of the route to focus by on initial render.
|
||||
* If not specified, usually the first route is used.
|
||||
*/
|
||||
initialRouteName?: string;
|
||||
};
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
NavigationAction,
|
||||
InitialState,
|
||||
PartialState,
|
||||
Route,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
export type DefaultNavigatorOptions<
|
||||
ScreenOptions extends object
|
||||
@@ -120,97 +28,6 @@ export type DefaultNavigatorOptions<
|
||||
}) => ScreenOptions);
|
||||
};
|
||||
|
||||
export type RouterFactory<
|
||||
State extends NavigationState,
|
||||
Action extends NavigationAction,
|
||||
RouterOptions extends DefaultRouterOptions
|
||||
> = (options: RouterOptions) => Router<State, Action>;
|
||||
|
||||
export type RouterConfigOptions = {
|
||||
routeNames: string[];
|
||||
routeParamList: ParamListBase;
|
||||
};
|
||||
|
||||
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.
|
||||
*
|
||||
* @param options.routeNames List of valid route names as defined in the screen components.
|
||||
* @param options.routeParamsList Object containing params for each route.
|
||||
*/
|
||||
getInitialState(options: RouterConfigOptions): State;
|
||||
|
||||
/**
|
||||
* Rehydrate the full navigation state from a given partial state.
|
||||
*
|
||||
* @param partialState Navigation state to rehydrate from.
|
||||
* @param options.routeNames List of valid route names as defined in the screen components.
|
||||
* @param options.routeParamsList Object containing params for each route.
|
||||
*/
|
||||
getRehydratedState(
|
||||
partialState: PartialState<State> | State,
|
||||
options: RouterConfigOptions
|
||||
): State;
|
||||
|
||||
/**
|
||||
* Take the current state and updated list of route names, and return a new state.
|
||||
*
|
||||
* @param state State object to update.
|
||||
* @param options.routeNames New list of route names.
|
||||
* @param options.routeParamsList Object containing params for each route.
|
||||
*/
|
||||
getStateForRouteNamesChange(
|
||||
state: State,
|
||||
options: RouterConfigOptions
|
||||
): State;
|
||||
|
||||
/**
|
||||
* Take the current state and key of a route, and return a new state with the route focused
|
||||
*
|
||||
* @param state State object to apply the action on.
|
||||
* @param key Key of the route to focus.
|
||||
*/
|
||||
getStateForRouteFocus(state: State, key: string): State;
|
||||
|
||||
/**
|
||||
* Take the current state and action, and return a new state.
|
||||
* If the action cannot be handled, return `null`.
|
||||
*
|
||||
* @param state State object to apply the action on.
|
||||
* @param action Action object to apply.
|
||||
* @param options.routeNames List of valid route names as defined in the screen components.
|
||||
* @param options.routeParamsList Object containing params for each route.
|
||||
*/
|
||||
getStateForAction(
|
||||
state: State,
|
||||
action: Action,
|
||||
options: RouterConfigOptions
|
||||
): State | PartialState<State> | null;
|
||||
|
||||
/**
|
||||
* Whether the action should also change focus in parent navigator
|
||||
*
|
||||
* @param action Action object to check.
|
||||
*/
|
||||
shouldActionChangeFocus(action: NavigationAction): boolean;
|
||||
|
||||
/**
|
||||
* Action creators for the router.
|
||||
*/
|
||||
actionCreators?: ActionCreators<Action>;
|
||||
};
|
||||
|
||||
export type ParamListBase = Record<string, object | undefined>;
|
||||
|
||||
export type EventMapBase = Record<
|
||||
string,
|
||||
{ data?: any; canPreventDefault?: boolean }
|
||||
@@ -231,6 +48,7 @@ export type EventArg<
|
||||
* Type of the event (e.g. `focus`, `blur`)
|
||||
*/
|
||||
readonly type: EventName;
|
||||
readonly target?: string;
|
||||
} & (CanPreventDefault extends true
|
||||
? {
|
||||
/**
|
||||
@@ -350,18 +168,6 @@ type NavigationHelpersCommon<
|
||||
| { name: RouteName; key?: string; params: ParamList[RouteName] }
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Replace the current route with a new one.
|
||||
*
|
||||
* @param name Route name of the new route.
|
||||
* @param [params] Params object for the new route.
|
||||
*/
|
||||
replace<RouteName extends keyof ParamList>(
|
||||
...args: ParamList[RouteName] extends undefined
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Reset the navigation state to the provided state.
|
||||
*
|
||||
@@ -543,7 +349,9 @@ export type Descriptor<
|
||||
export type RouteConfig<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
ScreenOptions extends object
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
/**
|
||||
* Route name of this screen.
|
||||
@@ -560,6 +368,16 @@ export type RouteConfig<
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
|
||||
/**
|
||||
* Event listeners for this screen.
|
||||
*/
|
||||
listeners?: Partial<
|
||||
{
|
||||
[EventName in keyof (EventMap &
|
||||
EventMapCore<State>)]: EventListenerCallback<EventMap, EventName>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Initial params object for the route.
|
||||
*/
|
||||
@@ -603,7 +421,9 @@ export type NavigationContainerRef =
|
||||
|
||||
export type TypedNavigator<
|
||||
ParamList extends ParamListBase,
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase,
|
||||
Navigator extends React.ComponentType<any>
|
||||
> = {
|
||||
/**
|
||||
@@ -634,6 +454,6 @@ export type TypedNavigator<
|
||||
* Component used for specifying route configuration.
|
||||
*/
|
||||
Screen: <RouteName extends keyof ParamList>(
|
||||
_: RouteConfig<ParamList, RouteName, ScreenOptions>
|
||||
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
|
||||
) => null;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import SceneView from './SceneView';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
@@ -9,18 +15,22 @@ import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import useNavigationCache from './useNavigationCache';
|
||||
import {
|
||||
Descriptor,
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
RouteConfig,
|
||||
RouteProp,
|
||||
Router,
|
||||
EventMapBase,
|
||||
} from './types';
|
||||
|
||||
type Options<State extends NavigationState, ScreenOptions extends object> = {
|
||||
type Options<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
state: State;
|
||||
screens: Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>;
|
||||
screens: Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
@@ -52,7 +62,8 @@ type Options<State extends NavigationState, ScreenOptions extends object> = {
|
||||
*/
|
||||
export default function useDescriptors<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
state,
|
||||
screens,
|
||||
@@ -67,7 +78,7 @@ export default function useDescriptors<
|
||||
onRouteFocus,
|
||||
router,
|
||||
emitter,
|
||||
}: Options<State, ScreenOptions>) {
|
||||
}: Options<State, ScreenOptions, EventMap>) {
|
||||
const [options, setOptions] = React.useState<Record<string, object>>({});
|
||||
const { trackAction } = React.useContext(NavigationBuilderContext);
|
||||
|
||||
@@ -136,6 +147,7 @@ export default function useDescriptors<
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
// @ts-ignore
|
||||
navigation,
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState, NavigationAction, PartialState } from './types';
|
||||
import {
|
||||
NavigationState,
|
||||
NavigationAction,
|
||||
PartialState,
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
type Options = {
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
reset: (state: NavigationState) => void;
|
||||
state: State;
|
||||
@@ -31,10 +36,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export default function useDevTools({ name, reset, state }: Options) {
|
||||
export default function useDevTools({ name, reset, state, enabled }: Options) {
|
||||
const devToolsRef = React.useRef<DevTools>();
|
||||
|
||||
if (
|
||||
enabled &&
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
global.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||
devToolsRef.current === undefined
|
||||
|
||||
@@ -5,12 +5,20 @@ export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
|
||||
create: (target: string) => EventConsumer<Record<string, any>>;
|
||||
};
|
||||
|
||||
type Listeners = ((data: any) => void)[];
|
||||
type Listeners = ((e: any) => void)[];
|
||||
|
||||
/**
|
||||
* Hook to manage the event system used by the navigator to notify screens of various events.
|
||||
*/
|
||||
export default function useEventEmitter(): NavigationEventEmitter {
|
||||
export default function useEventEmitter(
|
||||
listen?: (e: any) => void
|
||||
): NavigationEventEmitter {
|
||||
const listenRef = React.useRef(listen);
|
||||
|
||||
React.useEffect(() => {
|
||||
listenRef.current = listen;
|
||||
});
|
||||
|
||||
const listeners = React.useRef<Record<string, Record<string, Listeners>>>({});
|
||||
|
||||
const create = React.useCallback((target: string) => {
|
||||
@@ -60,7 +68,9 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
const callbacks =
|
||||
target !== undefined
|
||||
? items[target] && items[target].slice()
|
||||
: ([] as Listeners).concat(...Object.keys(items).map(t => items[t]));
|
||||
: ([] as Listeners)
|
||||
.concat(...Object.keys(items).map(t => items[t]))
|
||||
.filter((cb, i, self) => self.lastIndexOf(cb) === i);
|
||||
|
||||
const event: EventArg<any, any, any> = {
|
||||
get type() {
|
||||
@@ -68,8 +78,18 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
},
|
||||
};
|
||||
|
||||
if (target !== undefined) {
|
||||
Object.defineProperty(event, 'target', {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return target;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
Object.defineProperty(event, 'data', {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return data;
|
||||
},
|
||||
@@ -81,11 +101,13 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
|
||||
Object.defineProperties(event, {
|
||||
defaultPrevented: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return defaultPrevented;
|
||||
},
|
||||
},
|
||||
preventDefault: {
|
||||
enumerable: true,
|
||||
value() {
|
||||
defaultPrevented = true;
|
||||
},
|
||||
@@ -93,6 +115,8 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
listenRef.current?.(event);
|
||||
|
||||
callbacks?.forEach(cb => cb(event));
|
||||
|
||||
return event as any;
|
||||
|
||||
@@ -10,13 +10,51 @@ type EffectCallback = () => undefined | void | (() => void);
|
||||
*
|
||||
* @param callback Memoized callback containing the effect, should optionally return a cleanup function.
|
||||
*/
|
||||
export default function useFocusEffect(callback: EffectCallback) {
|
||||
export default function useFocusEffect(effect: EffectCallback) {
|
||||
const navigation = useNavigation();
|
||||
|
||||
React.useEffect(() => {
|
||||
let isFocused = false;
|
||||
let cleanup: undefined | void | (() => void);
|
||||
|
||||
const callback = () => {
|
||||
const destroy = effect();
|
||||
|
||||
if (destroy === undefined || typeof destroy === 'function') {
|
||||
return destroy;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
let message =
|
||||
'An effect function must not return anything besides a function, which is used for clean-up.';
|
||||
|
||||
if (destroy === null) {
|
||||
message +=
|
||||
" You returned 'null'. If your effect does not require clean-up, return 'undefined' (or nothing).";
|
||||
} else if (typeof (destroy as any).then === 'function') {
|
||||
message +=
|
||||
"\n\nIt looks like you wrote 'useFocusEffect(async () => ...)' or returned a Promise. " +
|
||||
'Instead, write the async function inside your effect ' +
|
||||
'and call it immediately:\n\n' +
|
||||
'useFocusEffect(\n' +
|
||||
' React.useCallback() => {\n' +
|
||||
' async function fetchData() {\n' +
|
||||
' // You can await here\n' +
|
||||
' const response = await MyAPI.getData(someId);\n' +
|
||||
' // ...\n' +
|
||||
' }\n\n' +
|
||||
' fetchData();\n' +
|
||||
' }, [someId])\n' +
|
||||
'};\n\n' +
|
||||
'See usage guide: https://reactnavigation.org/docs/use-focus-effect';
|
||||
} else {
|
||||
message += ` You returned: '${JSON.stringify(destroy)}'`;
|
||||
}
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
// We need to run the effect on intial render/dep changes if the screen is focused
|
||||
if (navigation.isFocused()) {
|
||||
cleanup = callback();
|
||||
@@ -55,5 +93,5 @@ export default function useFocusEffect(callback: EffectCallback) {
|
||||
unsubscribeFocus();
|
||||
unsubscribeBlur();
|
||||
};
|
||||
}, [callback, navigation]);
|
||||
}, [effect, navigation]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import { NavigationState } from './types';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState;
|
||||
@@ -21,17 +21,19 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
||||
// Coz the child screen can't be focused if the parent screen is out of focus
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigation?.addListener('focus', () =>
|
||||
emitter.emit({ type: 'focus', target: currentFocusedKey })
|
||||
),
|
||||
navigation?.addListener('focus', () => {
|
||||
lastFocusedKeyRef.current = currentFocusedKey;
|
||||
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
||||
}),
|
||||
[currentFocusedKey, emitter, navigation]
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigation?.addListener('blur', () =>
|
||||
emitter.emit({ type: 'blur', target: currentFocusedKey })
|
||||
),
|
||||
navigation?.addListener('blur', () => {
|
||||
lastFocusedKeyRef.current = undefined;
|
||||
emitter.emit({ type: 'blur', target: currentFocusedKey });
|
||||
}),
|
||||
[currentFocusedKey, emitter, navigation]
|
||||
);
|
||||
|
||||
@@ -60,14 +62,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitter.emit({
|
||||
type: 'focus',
|
||||
target: currentFocusedKey,
|
||||
});
|
||||
|
||||
emitter.emit({
|
||||
type: 'blur',
|
||||
target: lastFocusedKey,
|
||||
});
|
||||
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
||||
emitter.emit({ type: 'blur', target: lastFocusedKey });
|
||||
}, [currentFocusedKey, emitter, navigation]);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import NavigationBuilderContext, {
|
||||
FocusedNavigationCallback,
|
||||
FocusedNavigationListener,
|
||||
} from './NavigationBuilderContext';
|
||||
import { NavigationHelpers, ParamListBase } from './types';
|
||||
import { NavigationHelpers } from './types';
|
||||
|
||||
type Options = {
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import { NavigationProp, ParamListBase } from './types';
|
||||
import { NavigationProp } from './types';
|
||||
|
||||
/**
|
||||
* Hook to access the navigation prop of the parent screen anywhere.
|
||||
@@ -14,7 +15,7 @@ export default function useNavigation<
|
||||
|
||||
if (navigation === undefined) {
|
||||
throw new Error(
|
||||
"We couldn't find a navigation object. Is your component inside a navigator?"
|
||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { isValidElementType } from 'react-is';
|
||||
import {
|
||||
CommonActions,
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
RouterFactory,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import Screen from './Screen';
|
||||
import { navigate } from './CommonActions';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useRegisterNavigator from './useRegisterNavigator';
|
||||
import useDescriptors from './useDescriptors';
|
||||
@@ -15,16 +24,10 @@ import useChildActionListeners from './useChildActionListeners';
|
||||
import useFocusedListeners from './useFocusedListeners';
|
||||
import useFocusedListenersChildrenAdapter from './useFocusedListenersChildrenAdapter';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
DefaultNavigatorOptions,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
RouteConfig,
|
||||
Router,
|
||||
RouterFactory,
|
||||
PartialState,
|
||||
PrivateValueStore,
|
||||
NavigationAction,
|
||||
EventMapBase,
|
||||
} from './types';
|
||||
import useStateGetters from './useStateGetters';
|
||||
import useOnGetState from './useOnGetState';
|
||||
@@ -34,6 +37,7 @@ import useOnGetState from './useOnGetState';
|
||||
PrivateValueStore;
|
||||
|
||||
type NavigatorRoute = {
|
||||
key: string;
|
||||
params?: {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
@@ -52,18 +56,28 @@ const isArrayEqual = (a: any[], b: any[]) =>
|
||||
*
|
||||
* @param children React Elements to extract the config from.
|
||||
*/
|
||||
const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
const getRouteConfigsFromChildren = <
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>(
|
||||
children: React.ReactNode
|
||||
) => {
|
||||
const configs = React.Children.toArray(children).reduce<
|
||||
RouteConfig<ParamListBase, string, ScreenOptions>[]
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>[]
|
||||
>((acc, child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
if (child.type === Screen) {
|
||||
// We can only extract the config from `Screen` elements
|
||||
// If something else was rendered, it's probably a bug
|
||||
acc.push(
|
||||
child.props as RouteConfig<ParamListBase, string, ScreenOptions>
|
||||
child.props as RouteConfig<
|
||||
ParamListBase,
|
||||
string,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
@@ -72,7 +86,9 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
// When we encounter a fragment, we need to dive into its children to extract the configs
|
||||
// This is handy to conditionally define a group of screens
|
||||
acc.push(
|
||||
...getRouteConfigsFromChildren<ScreenOptions>(child.props.children)
|
||||
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
|
||||
child.props.children
|
||||
)
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
@@ -113,7 +129,7 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
|
||||
if (component !== undefined && !isValidElementType(component)) {
|
||||
throw new Error(
|
||||
`Got an invalid value for 'component' prop for the screen '${name}'. It must be a a valid React Component.`
|
||||
`Got an invalid value for 'component' prop for the screen '${name}'. It must be a valid React Component.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,7 +168,7 @@ export default function useNavigationBuilder<
|
||||
createRouter: RouterFactory<State, any, RouterOptions>,
|
||||
options: DefaultNavigatorOptions<ScreenOptions> & RouterOptions
|
||||
) {
|
||||
useRegisterNavigator();
|
||||
const navigatorKey = useRegisterNavigator();
|
||||
|
||||
const route = React.useContext(NavigationRouteContext) as
|
||||
| NavigatorRoute
|
||||
@@ -174,20 +190,29 @@ export default function useNavigationBuilder<
|
||||
})
|
||||
);
|
||||
|
||||
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce<
|
||||
Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>
|
||||
>((acc, curr) => {
|
||||
if (curr.name in acc) {
|
||||
const routeConfigs = getRouteConfigsFromChildren<
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>(children);
|
||||
|
||||
const screens = routeConfigs.reduce<
|
||||
Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>
|
||||
>((acc, config) => {
|
||||
if (config.name in acc) {
|
||||
throw new Error(
|
||||
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${curr.name}')`
|
||||
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.name}')`
|
||||
);
|
||||
}
|
||||
|
||||
acc[curr.name] = curr;
|
||||
acc[config.name] = config;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const routeNames = Object.keys(screens);
|
||||
const routeNames = routeConfigs.map(config => config.name);
|
||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
@@ -230,8 +255,8 @@ export default function useNavigationBuilder<
|
||||
state: currentState,
|
||||
getState: getCurrentState,
|
||||
setState,
|
||||
key,
|
||||
performTransaction,
|
||||
setKey,
|
||||
getKey,
|
||||
} = React.useContext(NavigationStateContext);
|
||||
|
||||
const previousStateRef = React.useRef<
|
||||
@@ -292,7 +317,7 @@ export default function useNavigationBuilder<
|
||||
// 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),
|
||||
CommonActions.navigate(route.params.screen, route.params.params),
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
@@ -308,14 +333,14 @@ export default function useNavigationBuilder<
|
||||
: 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(() => {
|
||||
const shouldUpdate = state !== nextState;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldUpdate) {
|
||||
// If the state needs to be updated, we'll schedule an update with React
|
||||
setState(nextState);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [nextState, setState, shouldUpdate]);
|
||||
|
||||
// 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
|
||||
@@ -323,11 +348,13 @@ export default function useNavigationBuilder<
|
||||
state = nextState;
|
||||
|
||||
React.useEffect(() => {
|
||||
setKey(navigatorKey);
|
||||
|
||||
return () => {
|
||||
// We need to clean up state for this navigator on unmount
|
||||
performTransaction(
|
||||
() => getCurrentState() !== undefined && setState(undefined)
|
||||
);
|
||||
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
|
||||
setState(undefined);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -340,7 +367,35 @@ export default function useNavigationBuilder<
|
||||
: (initializedStateRef.current as State);
|
||||
}, [getCurrentState, isStateInitialized]);
|
||||
|
||||
const emitter = useEventEmitter();
|
||||
const emitter = useEventEmitter(e => {
|
||||
let routeNames = [];
|
||||
|
||||
if (e.target) {
|
||||
const name = state.routes.find(route => route.key === e.target)?.name;
|
||||
|
||||
if (name) {
|
||||
routeNames.push(name);
|
||||
}
|
||||
} else {
|
||||
routeNames.push(...Object.keys(screens));
|
||||
}
|
||||
|
||||
const listeners = ([] as (((e: any) => void) | undefined)[])
|
||||
.concat(
|
||||
...routeNames.map(name => {
|
||||
const { listeners } = screens[name];
|
||||
|
||||
return listeners
|
||||
? Object.keys(listeners)
|
||||
.filter(type => type === e.type)
|
||||
.map(type => listeners[type])
|
||||
: undefined;
|
||||
})
|
||||
)
|
||||
.filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
|
||||
|
||||
listeners.forEach(listener => listener?.(e));
|
||||
});
|
||||
|
||||
useFocusEvents({ state, emitter });
|
||||
|
||||
@@ -364,7 +419,7 @@ export default function useNavigationBuilder<
|
||||
router,
|
||||
getState,
|
||||
setState,
|
||||
key,
|
||||
key: route?.key,
|
||||
listeners: actionListeners,
|
||||
routerConfigOptions: {
|
||||
routeNames,
|
||||
@@ -374,7 +429,7 @@ export default function useNavigationBuilder<
|
||||
|
||||
const onRouteFocus = useOnRouteFocus({
|
||||
router,
|
||||
key,
|
||||
key: route?.key,
|
||||
getState,
|
||||
setState,
|
||||
});
|
||||
@@ -396,7 +451,7 @@ export default function useNavigationBuilder<
|
||||
getStateForRoute,
|
||||
});
|
||||
|
||||
const descriptors = useDescriptors<State, ScreenOptions>({
|
||||
const descriptors = useDescriptors<State, ScreenOptions, EventMap>({
|
||||
state,
|
||||
screens,
|
||||
navigation,
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import * as CommonActions from './CommonActions';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
import {
|
||||
CommonActions,
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
Router,
|
||||
} from './types';
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
import { NavigationHelpers, NavigationProp } from './types';
|
||||
|
||||
type Options<State extends NavigationState> = {
|
||||
state: State;
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import * as CommonActions from './CommonActions';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
NavigationProp,
|
||||
CommonActions,
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
PrivateValueStore,
|
||||
} from './types';
|
||||
} from '@react-navigation/routers';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import { NavigationHelpers, NavigationProp, PrivateValueStore } from './types';
|
||||
|
||||
// This is to make TypeScript compiler happy
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
@@ -37,24 +34,48 @@ export default function useNavigationHelpers<
|
||||
EventMap extends Record<string, any>
|
||||
>({ onAction, getState, emitter, router }: Options<State, Action>) {
|
||||
const parentNavigationHelpers = React.useContext(NavigationContext);
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
|
||||
return React.useMemo(() => {
|
||||
const dispatch = (action: Action | ((state: State) => Action)) => {
|
||||
performTransaction(() => {
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
const dispatch = (op: Action | ((state: State) => Action)) => {
|
||||
const action = typeof op === 'function' ? op(getState()) : op;
|
||||
|
||||
const handled = onAction(payload);
|
||||
const handled = onAction(action);
|
||||
|
||||
if (!handled && process.env.NODE_ENV !== 'production') {
|
||||
console.error(
|
||||
`The action '${payload.type}' with payload '${JSON.stringify(
|
||||
payload.payload
|
||||
)}' was not handled by any navigator. If you are trying to navigate to a screen, check if the screen exists in your navigator.`
|
||||
);
|
||||
if (!handled && process.env.NODE_ENV !== 'production') {
|
||||
const payload: Record<string, any> | undefined = action.payload;
|
||||
|
||||
let message = `The action '${action.type}'${
|
||||
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
|
||||
} was not handled by any navigator.`;
|
||||
|
||||
switch (action.type) {
|
||||
case 'NAVIGATE':
|
||||
case 'PUSH':
|
||||
case 'REPLACE':
|
||||
case 'JUMP_TO':
|
||||
if (payload?.name) {
|
||||
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
|
||||
} else {
|
||||
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'GO_BACK':
|
||||
case 'POP':
|
||||
case 'POP_TO_TOP':
|
||||
message += `\n\nIs there any screen to go back to?`;
|
||||
break;
|
||||
case 'OPEN_DRAWER':
|
||||
case 'CLOSE_DRAWER':
|
||||
case 'TOGGLE_DRAWER':
|
||||
message += `\n\nIs your screen inside a Drawer navigator?`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
message += `\n\nThis is a development-only warning and won't be shown in production.`;
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
@@ -93,12 +114,5 @@ export default function useNavigationHelpers<
|
||||
},
|
||||
} as NavigationHelpers<ParamListBase, EventMap> &
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
|
||||
}, [
|
||||
router,
|
||||
getState,
|
||||
parentNavigationHelpers,
|
||||
emitter.emit,
|
||||
performTransaction,
|
||||
onAction,
|
||||
]);
|
||||
}, [router, getState, parentNavigationHelpers, emitter.emit, onAction]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import useNavigation from './useNavigation';
|
||||
import { NavigationState } from './types';
|
||||
|
||||
type Selector<T> = (state: NavigationState) => T;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
} from './NavigationBuilderContext';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
PartialState,
|
||||
Router,
|
||||
RouterConfigOptions,
|
||||
} from './types';
|
||||
} from '@react-navigation/routers';
|
||||
import NavigationBuilderContext, {
|
||||
ChildActionListener,
|
||||
} from './NavigationBuilderContext';
|
||||
|
||||
type Options = {
|
||||
router: Router<NavigationState, NavigationAction>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import { NavigationState } from './types';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
|
||||
export default function useOnGetState({
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import { NavigationAction, NavigationState, Router } from './types';
|
||||
|
||||
type Options<Action extends NavigationAction> = {
|
||||
router: Router<NavigationState, Action>;
|
||||
|
||||
@@ -23,4 +23,6 @@ export default function useRegisterNavigator() {
|
||||
|
||||
return () => unregister(key);
|
||||
}, [container, key]);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import { ParamListBase, RouteProp } from './types';
|
||||
import { RouteProp } from './types';
|
||||
|
||||
/**
|
||||
* Hook to access the route prop of the parent screen anywhere.
|
||||
@@ -14,7 +15,7 @@ export default function useRoute<
|
||||
|
||||
if (route === undefined) {
|
||||
throw new Error(
|
||||
"We couldn't find a route object. Is your component inside a navigator?"
|
||||
"Couldn't find a route object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
28
packages/core/src/useSyncState.tsx
Normal file
28
packages/core/src/useSyncState.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const UNINTIALIZED_STATE = {};
|
||||
|
||||
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
||||
|
||||
if (stateRef.current === UNINTIALIZED_STATE) {
|
||||
stateRef.current =
|
||||
// @ts-ignore
|
||||
typeof initialState === 'function' ? initialState() : initialState;
|
||||
}
|
||||
|
||||
const [state, setTrackingState] = React.useState(stateRef.current);
|
||||
|
||||
const getState = React.useCallback(() => stateRef.current, []);
|
||||
|
||||
const setState = React.useCallback((state: T) => {
|
||||
if (state === stateRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateRef.current = state;
|
||||
setTrackingState(state);
|
||||
}, []);
|
||||
|
||||
return [state, getState, setState] as const;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../routers" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,134 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.1...@react-navigation/drawer@5.3.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close drawer on pressing Esc on web ([5c4afc5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/5c4afc5cb40c1206a9d8c40efe3cf947030da48e)), closes [#6745](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6745)
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7485)
|
||||
* fix permanent sidebar position ([#7830](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7830)) ([3ea8eec](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3ea8eec4324ea82f0ed427f4662e68e1115e60ab))
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.0...@react-navigation/drawer@5.3.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.2.0...@react-navigation/drawer@5.3.0) (2020-03-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.1.1...@react-navigation/drawer@5.2.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make useIsDrawerOpen workable inside drawer content ([#7746](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7746)) ([cb46d0b](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/cb46d0bca4e17e847fff46ac94276213ac9697bf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.1.0...@react-navigation/drawer@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.7...@react-navigation/drawer@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.6...@react-navigation/drawer@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.5...@react-navigation/drawer@5.0.6) (2020-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* delay showing drawer by one frame after layout ([e0c3298](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/e0c3298e64970dc01a61401cfbd7a623eb0fd735))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.4...@react-navigation/drawer@5.0.5) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.3...@react-navigation/drawer@5.0.4) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.2...@react-navigation/drawer@5.0.3) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.1...@react-navigation/drawer@5.0.2) (2020-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove unnecessary borderless from drawer item ([031136f](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/031136f7c86eff3c9139d1baa243da9f19bc61d4)), closes [#6801](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6801)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.0-alpha.47...@react-navigation/drawer@5.0.1) (2020-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent ripple from bleeding out of drawer item ([688d16d](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/688d16de5d9f4c5116b92e3ebf3029b56a659d7a)), closes [#6801](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6801)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.47](https://github.com/react-navigation/navigation-ex/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.0-alpha.46...@react-navigation/drawer@5.0.0-alpha.47) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
@@ -2,60 +2,4 @@
|
||||
|
||||
Bottom tab navigator for React Navigation following iOS design guidelines.
|
||||
|
||||
Documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/en/next/drawer-navigator.html).
|
||||
|
||||
## Installation
|
||||
|
||||
Open a Terminal in your project's folder and run,
|
||||
|
||||
```sh
|
||||
yarn add @react-navigation/native @react-navigation/drawer
|
||||
```
|
||||
|
||||
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 react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are not using Expo, run the following:
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
|
||||
|
||||
```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.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Drawer.Navigator>
|
||||
<Drawer.Screen name="home" component={Home} options={{ title: 'Home' }} />
|
||||
<Drawer.Screen name="feed" component={Feed} options={{ title: 'Feed' }} />
|
||||
<Drawer.Screen
|
||||
name="profile"
|
||||
component={Profile}
|
||||
options={{ title: 'Profile' }}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
}
|
||||
```
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/drawer-navigator.html).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.3.2",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -11,9 +12,12 @@
|
||||
"material",
|
||||
"drawer"
|
||||
],
|
||||
"version": "5.0.0-alpha.47",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/drawer",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/drawer",
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-navigation/react-navigation/issues"
|
||||
},
|
||||
"homepage": "https://reactnavigation.org/docs/drawer-navigator.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -31,31 +35,31 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.33",
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.8.0",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.5",
|
||||
"react-native-reanimated": "^1.4.0",
|
||||
"react-native-safe-area-context": "^0.6.2",
|
||||
"react-native-screens": "^2.0.0-alpha.33",
|
||||
"typescript": "^3.7.4"
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.0-alpha.0",
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "^1.0.0",
|
||||
"react-native-reanimated": "^1.0.0",
|
||||
"react-native-safe-area-context": "^0.6.0",
|
||||
"react-native-screens": "^2.0.0-alpha.33"
|
||||
"react-native-gesture-handler": ">= 1.0.0",
|
||||
"react-native-reanimated": ">= 1.0.0",
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
createNavigatorFactory,
|
||||
useNavigationBuilder,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
DrawerNavigationState,
|
||||
DrawerRouterOptions,
|
||||
DrawerRouter,
|
||||
} from '@react-navigation/routers';
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import DrawerView from '../views/DrawerView';
|
||||
import {
|
||||
@@ -51,6 +49,8 @@ function DrawerNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap,
|
||||
typeof DrawerNavigator
|
||||
>(DrawerNavigator);
|
||||
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
NavigationProp,
|
||||
Descriptor,
|
||||
NavigationHelpers,
|
||||
DrawerNavigationState,
|
||||
DrawerActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
import { DrawerNavigationState } from '@react-navigation/routers';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
|
||||
export type Scene = {
|
||||
@@ -26,8 +27,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
|
||||
* - `back`: The drawer is revealed behind the screen on swipe.
|
||||
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
|
||||
* - `permanent`: A permanent drawer is shown as a sidebar.
|
||||
*/
|
||||
drawerType?: 'front' | 'back' | 'slide';
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
*/
|
||||
@@ -190,22 +192,8 @@ export type DrawerNavigationProp<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Open the drawer sidebar.
|
||||
*/
|
||||
openDrawer(): void;
|
||||
|
||||
/**
|
||||
* Close the drawer sidebar.
|
||||
*/
|
||||
closeDrawer(): void;
|
||||
|
||||
/**
|
||||
* Open the drawer sidebar if closed, or close if opened.
|
||||
*/
|
||||
toggleDrawer(): void;
|
||||
};
|
||||
> &
|
||||
DrawerActionHelpers<ParamList>;
|
||||
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
|
||||
5
packages/drawer/src/utils/DrawerOpenContext.tsx
Normal file
5
packages/drawer/src/utils/DrawerOpenContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const DrawerOpenContext = React.createContext<boolean | null>(null);
|
||||
|
||||
export default DrawerOpenContext;
|
||||
@@ -1,38 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation, ParamListBase } from '@react-navigation/native';
|
||||
import { DrawerNavigationProp } from '../types';
|
||||
import DrawerOpenContext from './DrawerOpenContext';
|
||||
|
||||
/**
|
||||
* Hook to detect if the drawer is open in a parent navigator.
|
||||
*/
|
||||
export default function useIsDrawerOpen() {
|
||||
const navigation = useNavigation();
|
||||
const isDrawerOpen = React.useContext(DrawerOpenContext);
|
||||
|
||||
let drawer = navigation as DrawerNavigationProp<ParamListBase>;
|
||||
|
||||
// The screen might be inside another navigator such as stack nested in drawer
|
||||
// We need to find the closest drawer navigator and add the listener there
|
||||
while (drawer && drawer.dangerouslyGetState().type !== 'drawer') {
|
||||
drawer = drawer.dangerouslyGetParent();
|
||||
if (typeof isDrawerOpen !== 'boolean') {
|
||||
throw new Error(
|
||||
"Couldn't find a drawer. Is your component inside a drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
const [isDrawerOpen, setIsDrawerOpen] = React.useState(() =>
|
||||
drawer
|
||||
? Boolean(
|
||||
drawer.dangerouslyGetState().history.find(it => it.type === 'drawer')
|
||||
)
|
||||
: false
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = drawer.addListener('state', e => {
|
||||
setIsDrawerOpen(
|
||||
Boolean(e.data.state.history.find(it => it.type === 'drawer'))
|
||||
);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [drawer, isDrawerOpen]);
|
||||
|
||||
return isDrawerOpen;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ const SPRING_CONFIG = {
|
||||
restSpeedThreshold: 0.01,
|
||||
};
|
||||
|
||||
const ANIMATED_ONE = new Animated.Value(1);
|
||||
|
||||
type Binary = 0 | 1;
|
||||
|
||||
type Renderer = (props: { progress: Animated.Node<number> }) => React.ReactNode;
|
||||
@@ -79,7 +81,7 @@ type Props = {
|
||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||
gestureEnabled: boolean;
|
||||
drawerPosition: 'left' | 'right';
|
||||
drawerType: 'front' | 'back' | 'slide';
|
||||
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
||||
keyboardDismissMode: 'none' | 'on-drag';
|
||||
swipeEdgeWidth: number;
|
||||
swipeDistanceThreshold?: number;
|
||||
@@ -125,6 +127,12 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
statusBarAnimation: 'slide',
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.addEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {
|
||||
open,
|
||||
@@ -180,8 +188,22 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
componentWillUnmount() {
|
||||
this.toggleStatusBar(false);
|
||||
this.handleEndInteraction();
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.removeEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEscape = (e: KeyboardEvent) => {
|
||||
const { open, onClose } = this.props;
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (open) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleEndInteraction = () => {
|
||||
if (this.interactionHandle !== undefined) {
|
||||
InteractionManager.clearInteractionHandle(this.interactionHandle);
|
||||
@@ -504,7 +526,9 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
// Until layout is available, drawer is hidden with opacity: 0 by default
|
||||
// Show it in the next frame when layout is available
|
||||
// If we don't delay it until the next frame, there's a visible flicker
|
||||
requestAnimationFrame(() => this.drawerOpacity.setValue(1));
|
||||
requestAnimationFrame(() =>
|
||||
requestAnimationFrame(() => this.drawerOpacity.setValue(1))
|
||||
);
|
||||
};
|
||||
|
||||
private toggleDrawer = (open: boolean) => {
|
||||
@@ -542,6 +566,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
gestureHandlerProps,
|
||||
} = this.props;
|
||||
|
||||
const isOpen = drawerType === 'permanent' ? true : open;
|
||||
const isRight = drawerPosition === 'right';
|
||||
|
||||
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
|
||||
@@ -567,8 +592,10 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
const hitSlop = isRight
|
||||
? // Extend hitSlop to the side of the screen when drawer is closed
|
||||
// This lets the user drag the drawer from the side of the screen
|
||||
{ right: 0, width: open ? undefined : swipeEdgeWidth }
|
||||
: { left: 0, width: open ? undefined : swipeEdgeWidth };
|
||||
{ right: 0, width: isOpen ? undefined : swipeEdgeWidth }
|
||||
: { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
|
||||
|
||||
const progress = drawerType === 'permanent' ? ANIMATED_ONE : this.progress;
|
||||
|
||||
return (
|
||||
<PanGestureHandler
|
||||
@@ -578,59 +605,83 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureStateChange}
|
||||
hitSlop={hitSlop}
|
||||
enabled={gestureEnabled}
|
||||
enabled={drawerType !== 'permanent' && gestureEnabled}
|
||||
{...gestureHandlerProps}
|
||||
>
|
||||
<Animated.View
|
||||
onLayout={this.handleContainerLayout}
|
||||
style={styles.main}
|
||||
style={[
|
||||
styles.main,
|
||||
{
|
||||
flexDirection:
|
||||
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
drawerType !== 'permanent' && {
|
||||
transform: [{ translateX: contentTranslateX }],
|
||||
},
|
||||
sceneContainerStyle as any,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
accessibilityElementsHidden={open}
|
||||
importantForAccessibility={open ? 'no-hide-descendants' : 'auto'}
|
||||
accessibilityElementsHidden={isOpen}
|
||||
importantForAccessibility={
|
||||
isOpen ? 'no-hide-descendants' : 'auto'
|
||||
}
|
||||
style={styles.content}
|
||||
>
|
||||
{renderSceneContent({ progress: this.progress })}
|
||||
{renderSceneContent({ progress })}
|
||||
</View>
|
||||
<TapGestureHandler onHandlerStateChange={this.handleTapStateChange}>
|
||||
<Overlay progress={this.progress} style={overlayStyle} />
|
||||
</TapGestureHandler>
|
||||
{// Disable overlay if sidebar is permanent
|
||||
drawerType === 'permanent' ? null : (
|
||||
<TapGestureHandler
|
||||
enabled={gestureEnabled}
|
||||
onHandlerStateChange={this.handleTapStateChange}
|
||||
>
|
||||
<Overlay progress={progress} style={overlayStyle} />
|
||||
</TapGestureHandler>
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.Code
|
||||
exec={block([
|
||||
onChange(this.manuallyTriggerSpring, [
|
||||
cond(eq(this.manuallyTriggerSpring, TRUE), [
|
||||
set(this.nextIsOpen, FALSE),
|
||||
call([], () => (this.currentOpenValue = false)),
|
||||
{drawerType === 'permanent' ? null : (
|
||||
<Animated.Code
|
||||
exec={block([
|
||||
onChange(this.manuallyTriggerSpring, [
|
||||
cond(eq(this.manuallyTriggerSpring, TRUE), [
|
||||
set(this.nextIsOpen, FALSE),
|
||||
call([], () => (this.currentOpenValue = false)),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])}
|
||||
/>
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
<Animated.View
|
||||
accessibilityViewIsModal={open}
|
||||
accessibilityViewIsModal={isOpen}
|
||||
removeClippedSubviews={Platform.OS !== 'ios'}
|
||||
onLayout={this.handleDrawerLayout}
|
||||
style={[
|
||||
styles.container,
|
||||
isRight ? { right: offset } : { left: offset },
|
||||
{
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
opacity: this.drawerOpacity,
|
||||
zIndex: drawerType === 'back' ? -1 : 0,
|
||||
},
|
||||
drawerType === 'permanent'
|
||||
? // Without this, the `left`/`right` values don't get reset
|
||||
isRight
|
||||
? { right: 0 }
|
||||
: { left: 0 }
|
||||
: [
|
||||
styles.nonPermanent,
|
||||
{
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
opacity: this.drawerOpacity,
|
||||
},
|
||||
isRight ? { right: offset } : { left: offset },
|
||||
{ zIndex: drawerType === 'back' ? -1 : 0 },
|
||||
],
|
||||
drawerStyle as any,
|
||||
]}
|
||||
>
|
||||
{renderDrawerContent({ progress: this.progress })}
|
||||
{renderDrawerContent({ progress })}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
@@ -641,11 +692,13 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
maxWidth: '100%',
|
||||
},
|
||||
nonPermanent: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: '80%',
|
||||
maxWidth: '100%',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user