mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-14 22:41:55 +08:00
Compare commits
202 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21f61d6eeb | ||
|
|
8774ca97e1 | ||
|
|
e653d55479 | ||
|
|
78afbffe97 | ||
|
|
762cc44578 | ||
|
|
c3bd349d77 | ||
|
|
5dcaf903f3 | ||
|
|
2d66ef93ec | ||
|
|
4fe72e3ce7 | ||
|
|
ab1f79c096 | ||
|
|
9305bfa939 | ||
|
|
0c3c450f5f | ||
|
|
7ac4c13d44 | ||
|
|
a0b9f94120 | ||
|
|
717dffdb81 | ||
|
|
9016ba00e3 | ||
|
|
9d822b95a6 | ||
|
|
52d5cb4179 | ||
|
|
af1722d1e9 | ||
|
|
0b1a718756 | ||
|
|
9ab29558d0 | ||
|
|
00c23f2c9e | ||
|
|
68e750d5a6 | ||
|
|
ced2a24aa6 | ||
|
|
ebf1345b39 | ||
|
|
df3544d9b4 | ||
|
|
c1e46f8e33 | ||
|
|
021a9111d7 | ||
|
|
d3ace96981 | ||
|
|
edbc6b1e84 | ||
|
|
c52d19bec8 | ||
|
|
6dd45fcff9 | ||
|
|
d62fbfe255 | ||
|
|
b14094619f | ||
|
|
4c4d864af2 | ||
|
|
e1969f4e17 | ||
|
|
175c07a28c | ||
|
|
2980627cbf | ||
|
|
d024ec6d74 | ||
|
|
4d1b85c751 | ||
|
|
4a818fdfb3 | ||
|
|
0194de1061 | ||
|
|
7b25c8eb2e | ||
|
|
9304a8a16c | ||
|
|
51b40879bd | ||
|
|
51f4d11fdf | ||
|
|
c2aa4bb2eb | ||
|
|
199a892a6d | ||
|
|
60cb3c9ba7 | ||
|
|
6ccceaea8b | ||
|
|
d14f38b80a | ||
|
|
c481748f00 | ||
|
|
d45dbe97dc | ||
|
|
7623945f6e | ||
|
|
1dddaff45c | ||
|
|
21b397f0d6 | ||
|
|
2ff0531695 | ||
|
|
0149e85a95 | ||
|
|
3c47716826 | ||
|
|
acc9646426 | ||
|
|
6dce0780ed | ||
|
|
dd7cff2016 | ||
|
|
740c6b6706 | ||
|
|
039017bc2a | ||
|
|
b85a1c3055 | ||
|
|
18f8188dc8 | ||
|
|
47a1229837 | ||
|
|
00b11e303e | ||
|
|
f384706741 | ||
|
|
d1a6f3e30e | ||
|
|
fd6636a8cd | ||
|
|
eb24fea8b9 | ||
|
|
85ae378d8c | ||
|
|
bea14aa26f | ||
|
|
4d1e102f8c | ||
|
|
f07cd13561 | ||
|
|
f6d06768d3 | ||
|
|
3381d680d7 | ||
|
|
fcd1cc64c1 | ||
|
|
3999fc2836 | ||
|
|
9fd2635756 | ||
|
|
6bec620a3f | ||
|
|
c7b8e2e966 | ||
|
|
719e1a7b46 | ||
|
|
10eca8b92e | ||
|
|
b66e3436a7 | ||
|
|
1c075ffb16 | ||
|
|
1ee3038a4d | ||
|
|
961b519fb1 | ||
|
|
0a19e94b23 | ||
|
|
1e73fed6de | ||
|
|
3193a30da6 | ||
|
|
499c50cd43 | ||
|
|
420f6926e1 | ||
|
|
70be3f6d86 | ||
|
|
bd35b4fc20 | ||
|
|
c511bc0b2b | ||
|
|
b4834ce703 | ||
|
|
ae5442ebe8 | ||
|
|
6dd52d35cf | ||
|
|
d6fa279d93 | ||
|
|
c3fa83efe0 | ||
|
|
f2291d110f | ||
|
|
942d2be2c7 | ||
|
|
b747e527a4 | ||
|
|
38020de80b | ||
|
|
67404f4999 | ||
|
|
2792f438fe | ||
|
|
2573b5beaa | ||
|
|
2697355ab2 | ||
|
|
a695cf9c05 | ||
|
|
c9c825bee6 | ||
|
|
b172b51f17 | ||
|
|
9c05af50b4 | ||
|
|
24febf6ea9 | ||
|
|
8cbb201f1a | ||
|
|
2467ce4ff7 | ||
|
|
5683bebfd6 | ||
|
|
78485cea69 | ||
|
|
1613915669 | ||
|
|
335a04edc1 | ||
|
|
5e0069a896 | ||
|
|
249248e741 | ||
|
|
821343fed3 | ||
|
|
82edb2581b | ||
|
|
cb67530dc5 | ||
|
|
36689e24c2 | ||
|
|
6e51f596fa | ||
|
|
402df73aa2 | ||
|
|
187aefe9c4 | ||
|
|
2613a62874 | ||
|
|
6bdf6ae4ed | ||
|
|
e2bcf5168c | ||
|
|
dfdba8d741 | ||
|
|
a3f7a5feba | ||
|
|
004c7d7ab1 | ||
|
|
49f658fbc0 | ||
|
|
cb2f157a56 | ||
|
|
c4acdaa703 | ||
|
|
f1a8bceba5 | ||
|
|
44081172d4 | ||
|
|
de5d985f3b | ||
|
|
b71de6cc79 | ||
|
|
303f0b78a5 | ||
|
|
ce3994c82c | ||
|
|
ba1f405129 | ||
|
|
d4fd906915 | ||
|
|
b7fa90bf8d | ||
|
|
9556aa9eff | ||
|
|
9a8fea8f2c | ||
|
|
9973db86f0 | ||
|
|
8432e5ab25 | ||
|
|
9bb5cfded3 | ||
|
|
4ac40b5c5d | ||
|
|
cd47915861 | ||
|
|
d649fbc669 | ||
|
|
105da6ab2f | ||
|
|
ac7f972e92 | ||
|
|
babb5027f9 | ||
|
|
78d7a66b2b | ||
|
|
a248c453ba | ||
|
|
e097df880a | ||
|
|
856449b200 | ||
|
|
d94e43c3c8 | ||
|
|
3096de6286 | ||
|
|
1c001424b5 | ||
|
|
0f2368965c | ||
|
|
61f16d3f25 | ||
|
|
853740bfaf | ||
|
|
179b6312fe | ||
|
|
043924ca48 | ||
|
|
813a5903b5 | ||
|
|
3709e652f4 | ||
|
|
5b15c7164f | ||
|
|
e030932497 | ||
|
|
adbfedcd58 | ||
|
|
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 |
@@ -1,58 +1,97 @@
|
|||||||
version: 2
|
version: 2.1
|
||||||
|
|
||||||
defaults: &defaults
|
executors:
|
||||||
docker:
|
default:
|
||||||
- image: circleci/node:10
|
docker:
|
||||||
working_directory: ~/project
|
- image: circleci/node:10
|
||||||
|
working_directory: ~/project
|
||||||
|
environment:
|
||||||
|
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||||
|
|
||||||
|
commands:
|
||||||
|
attach_project:
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/project
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
install-dependencies:
|
install-dependencies:
|
||||||
<<: *defaults
|
executor: default
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_project
|
||||||
at: ~/project
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
- yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||||
- v1-dependencies-
|
- yarn-packages-v1-{{ .Branch }}-
|
||||||
- run: yarn install --frozen-lockfile
|
- yarn-packages-v1-
|
||||||
|
- run:
|
||||||
|
name: Install project dependencies
|
||||||
|
command: yarn install --frozen-lockfile
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||||
paths: node_modules
|
paths: ~/.cache/yarn
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: .
|
root: .
|
||||||
paths: .
|
paths: .
|
||||||
|
|
||||||
lint-and-typecheck:
|
lint-and-typecheck:
|
||||||
<<: *defaults
|
executor: default
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_project
|
||||||
at: ~/project
|
- run:
|
||||||
- run: |
|
name: Lint files
|
||||||
yarn lint
|
command: yarn lint
|
||||||
yarn typescript
|
- run:
|
||||||
|
name: Typecheck files
|
||||||
|
command: yarn typescript
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
<<: *defaults
|
executor: default
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_project
|
||||||
at: ~/project
|
- run:
|
||||||
- run: |
|
name: Run unit tests
|
||||||
yarn test --coverage
|
command: yarn test --coverage
|
||||||
cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
- run:
|
||||||
- store_artifacts:
|
name: Upload test coverage
|
||||||
path: coverage
|
command: cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
||||||
destination: coverage
|
- store_artifacts:
|
||||||
|
path: coverage
|
||||||
|
destination: coverage
|
||||||
|
|
||||||
|
integration-tests:
|
||||||
|
executor: default
|
||||||
|
steps:
|
||||||
|
- attach_project
|
||||||
|
- run:
|
||||||
|
name: Install Headless Chrome dependencies
|
||||||
|
command: |
|
||||||
|
sudo apt-get install -yq \
|
||||||
|
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||||
|
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||||
|
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
|
||||||
|
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
|
||||||
|
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||||
|
- run:
|
||||||
|
name: Build example for web
|
||||||
|
command: yarn example expo build:web --no-pwa
|
||||||
|
- run:
|
||||||
|
name: Run integration tests
|
||||||
|
command: yarn example test --maxWorkers=2
|
||||||
|
|
||||||
build-packages:
|
build-packages:
|
||||||
<<: *defaults
|
executor: default
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_project
|
||||||
at: ~/project
|
- run:
|
||||||
- run: |
|
name: Build packages in the monorepo
|
||||||
yarn lerna run prepare
|
command: yarn lerna run prepare
|
||||||
node scripts/check-types-path.js
|
- run:
|
||||||
|
name: Verify paths for types
|
||||||
|
command: node scripts/check-types-path.js
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- install-dependencies
|
- install-dependencies
|
||||||
@@ -62,6 +101,9 @@ workflows:
|
|||||||
- unit-tests:
|
- unit-tests:
|
||||||
requires:
|
requires:
|
||||||
- install-dependencies
|
- install-dependencies
|
||||||
|
- integration-tests:
|
||||||
|
requires:
|
||||||
|
- install-dependencies
|
||||||
- build-packages:
|
- build-packages:
|
||||||
requires:
|
requires:
|
||||||
- install-dependencies
|
- install-dependencies
|
||||||
|
|||||||
16
.github/workflows/expo-preview.yml
vendored
16
.github/workflows/expo-preview.yml
vendored
@@ -23,20 +23,16 @@ jobs:
|
|||||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||||
expo-cache: true
|
expo-cache: true
|
||||||
|
|
||||||
- name: Get yarn cache
|
- name: Restore yarn cache
|
||||||
id: yarn-cache
|
id: yarn-cache
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
uses: actions/cache@master
|
||||||
|
|
||||||
- name: Check yarn cache
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
path: '**/node_modules'
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Publish Expo app
|
- name: Publish Expo app
|
||||||
working-directory: ./example
|
working-directory: ./example
|
||||||
@@ -44,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get expo link
|
- name: Get expo link
|
||||||
id: expo
|
id: expo
|
||||||
run: echo "::set-output name=path::@react-navigation/react-react-navigationample?release-channel=pr-${{ github.event.number }}"
|
run: echo "::set-output name=path::@react-navigation/react-navigation-example?release-channel=pr-${{ github.event.number }}"
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
uses: unsplash/comment-on-pr@master
|
uses: unsplash/comment-on-pr@master
|
||||||
|
|||||||
13
.github/workflows/expo.yml
vendored
13
.github/workflows/expo.yml
vendored
@@ -25,19 +25,16 @@ jobs:
|
|||||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||||
expo-cache: true
|
expo-cache: true
|
||||||
|
|
||||||
- name: Get yarn cache
|
- name: Restore yarn cache
|
||||||
id: yarn-cache
|
id: yarn-cache
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
uses: actions/cache@master
|
||||||
|
|
||||||
- uses: actions/cache@v1
|
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
path: '**/node_modules'
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Publish Expo app
|
- name: Publish Expo app
|
||||||
working-directory: ./example
|
working-directory: ./example
|
||||||
|
|||||||
20
.github/workflows/stale.yml
vendored
20
.github/workflows/stale.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: "Close stale issues and pull requests"
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
stale-issue-message: 'Hello 👋, this issue has been open for more than 3 months with no activity on it. If the issue is still present in the latest version, please leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution on workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix the issue.'
|
|
||||||
stale-pr-message: 'Hello 👋, this pull request has been open for more than 3 months with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.'
|
|
||||||
days-before-stale: 90
|
|
||||||
days-before-close: 7
|
|
||||||
stale-issue-label: 'stale'
|
|
||||||
stale-pr-label: 'stale'
|
|
||||||
exempt-issue-label: 'keep open'
|
|
||||||
exempt-pr-label: 'keep open'
|
|
||||||
15
.github/workflows/triage.yml
vendored
15
.github/workflows/triage.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
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.)."
|
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](https://stackoverflow.com/help/minimal-reproducible-example) (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:
|
needs-repro:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
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."
|
args: comment "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) 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:
|
question:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -36,3 +36,14 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
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."
|
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."
|
||||||
|
|||||||
27
.github/workflows/versions.yml
vendored
Normal file
27
.github/workflows/versions.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Check versions
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-versions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: react-navigation/check-versions-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
packages: |
|
||||||
|
@react-navigation/bottom-tabs
|
||||||
|
@react-navigation/compat
|
||||||
|
@react-navigation/core
|
||||||
|
@react-navigation/drawer
|
||||||
|
@react-navigation/material-bottom-tabs
|
||||||
|
@react-navigation/material-top-tabs
|
||||||
|
@react-navigation/native
|
||||||
|
@react-navigation/routers
|
||||||
|
@react-navigation/stack
|
||||||
|
react-navigation-animated-switch
|
||||||
|
react-navigation-drawer
|
||||||
|
react-navigation-material-bottom-tabs
|
||||||
|
react-navigation-stack
|
||||||
|
react-navigation-tabs
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
.gradle
|
.gradle
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
|
.history
|
||||||
|
|
||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'react-native-gesture-handler';
|
import 'react-native-gesture-handler';
|
||||||
import { registerRootComponent } from 'expo';
|
import { registerRootComponent } from 'expo';
|
||||||
|
import { Asset } from 'expo-asset';
|
||||||
|
import { Assets as StackAssets } from '@react-navigation/stack';
|
||||||
|
|
||||||
import App from './src/index';
|
import App from './src/index';
|
||||||
|
|
||||||
|
Asset.loadAsync(StackAssets);
|
||||||
|
|
||||||
registerRootComponent(App);
|
registerRootComponent(App);
|
||||||
|
|||||||
41
example/CHANGELOG.md
Normal file
41
example/CHANGELOG.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.1.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/example@5.0.0-alpha.23...@react-navigation/example@5.1.0) (2020-05-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
|
||||||
|
* clamp interpolated styles ([67798af](https://github.com/react-navigation/react-navigation/commit/67798af869dcbbf323629fc7e7cc9062d1e12c29))
|
||||||
|
* disable screens when mode is modal on older expo versions ([94d7b28](https://github.com/react-navigation/react-navigation/commit/94d7b28c0b2ce0d56c99b224610f305be6451626))
|
||||||
|
* dispatch pop early when screen is closed with gesture ([#336](https://github.com/react-navigation/react-navigation/issues/336)) ([3d937d1](https://github.com/react-navigation/react-navigation/commit/3d937d1e6571cd613e830d64f7b2e7426076d371)), closes [#267](https://github.com/react-navigation/react-navigation/issues/267)
|
||||||
|
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/react-navigation/issues/238)) ([77b7570](https://github.com/react-navigation/react-navigation/commit/77b757091c0451e20bca01138629669c7da544a8))
|
||||||
|
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
|
||||||
|
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
|
||||||
|
* rtl in native app example ([50b366e](https://github.com/react-navigation/react-navigation/commit/50b366e7341f201d29a44f20b7771b3a832b0045))
|
||||||
|
* screens integration on Android ([#294](https://github.com/react-navigation/react-navigation/issues/294)) ([9bfb295](https://github.com/react-navigation/react-navigation/commit/9bfb29562020c61b4d5c9bee278bcb1c7bdb8b67))
|
||||||
|
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/issues/6785)
|
||||||
|
* update screens for native stack ([5411816](https://github.com/react-navigation/react-navigation/commit/54118161885738a6d20b062c7e6679f3bace8424))
|
||||||
|
* wrap navigators in gesture handler root ([41a5e1a](https://github.com/react-navigation/react-navigation/commit/41a5e1a385aa5180abc3992a4c67077c37b998b9))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `animationTypeForReplace` option ([#297](https://github.com/react-navigation/react-navigation/issues/297)) ([6262f72](https://github.com/react-navigation/react-navigation/commit/6262f7298bff843571fb4b1a677d3beabe29833e))
|
||||||
|
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/react-navigation/issues/308)) ([b931ae6](https://github.com/react-navigation/react-navigation/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
|
||||||
|
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||||
|
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
|
||||||
|
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||||
|
* add custom theme support ([#211](https://github.com/react-navigation/react-navigation/issues/211)) ([00fc616](https://github.com/react-navigation/react-navigation/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
|
||||||
|
* add deeplinking to native example ([#309](https://github.com/react-navigation/react-navigation/issues/309)) ([e55e866](https://github.com/react-navigation/react-navigation/commit/e55e866af2f2163ee89bc527997cda13ffeb2abe))
|
||||||
|
* add headerStatusBarHeight option to stack ([b201fd2](https://github.com/react-navigation/react-navigation/commit/b201fd20716a2f03cb9373c72281f5d396a9356d))
|
||||||
|
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
|
||||||
|
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
|
||||||
|
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
|
||||||
|
* add preventDefault functionality in material bottom tabs ([3dede31](https://github.com/react-navigation/react-navigation/commit/3dede316ccab3b2403a475f60ce20b5c4e4cc068))
|
||||||
|
* emit appear and dismiss events for native stack ([f1df4a0](https://github.com/react-navigation/react-navigation/commit/f1df4a080877b3642e748a41a5ffc2da8c449a8c))
|
||||||
|
* initialState should take priority over deep link ([039017b](https://github.com/react-navigation/react-navigation/commit/039017bc2af69120d2d10e8f2c8a62919c37eb65))
|
||||||
|
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/react-navigation/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))
|
||||||
@@ -7,12 +7,6 @@
|
|||||||
"slug": "react-navigation-example",
|
"slug": "react-navigation-example",
|
||||||
"description": "Demo app to showcase various functionality of React Navigation",
|
"description": "Demo app to showcase various functionality of React Navigation",
|
||||||
"privacy": "public",
|
"privacy": "public",
|
||||||
"sdkVersion": "36.0.0",
|
|
||||||
"platforms": [
|
|
||||||
"ios",
|
|
||||||
"android",
|
|
||||||
"web"
|
|
||||||
],
|
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
"splash": {
|
"splash": {
|
||||||
@@ -20,15 +14,16 @@
|
|||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"updates": {
|
"sdkVersion": "37.0.0",
|
||||||
"fallbackToCacheTimeout": 0
|
"platforms": ["ios", "android", "web"],
|
||||||
},
|
|
||||||
"assetBundlePatterns": [
|
|
||||||
"**/*"
|
|
||||||
],
|
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true
|
||||||
},
|
},
|
||||||
|
"updates": {
|
||||||
|
"fallbackToCacheTimeout": 0
|
||||||
|
},
|
||||||
|
"assetBundlePatterns": ["**/*"],
|
||||||
|
"scheme": "rne",
|
||||||
"entryPoint": "App.tsx"
|
"entryPoint": "App.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module.exports = function(api) {
|
module.exports = function (api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
return {
|
return {
|
||||||
presets: ['babel-preset-expo'],
|
presets: ['babel-preset-expo'],
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"settings": {
|
|
||||||
"import/core-modules": [
|
|
||||||
"detox",
|
|
||||||
"detox/runners/jest/adapter",
|
|
||||||
"detox/runners/jest/specReporter"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"env": { "jest": true, "jasmine": true }
|
|
||||||
}
|
|
||||||
44
example/e2e/__integration_tests__/Link.test.tsx
Normal file
44
example/e2e/__integration_tests__/Link.test.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { page } from '../config/setup-playwright';
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await page.click('[data-testid=LinkComponent]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the article page', async () => {
|
||||||
|
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||||
|
'/link-component/article/gandalf'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||||
|
(it: any) => it.role === 'heading'
|
||||||
|
)?.name
|
||||||
|
).toBe('Article by Gandalf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('goes to the album page and goes back', async () => {
|
||||||
|
await page.click('[href="/link-component/music"]');
|
||||||
|
|
||||||
|
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||||
|
'/link-component/music'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||||
|
(it: any) => it.role === 'heading'
|
||||||
|
)?.name
|
||||||
|
).toBe('Albums');
|
||||||
|
|
||||||
|
await page.click('[aria-label="Article by Gandalf, back"]');
|
||||||
|
|
||||||
|
await page.waitForNavigation();
|
||||||
|
|
||||||
|
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||||
|
'/link-component/article/gandalf'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||||
|
(it: any) => it.role === 'heading'
|
||||||
|
)?.name
|
||||||
|
).toBe('Article by Gandalf');
|
||||||
|
});
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { by, element, expect, device } from 'detox';
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await device.reloadReactNative();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has dark theme toggle', async () => {
|
|
||||||
await expect(element(by.text('Dark theme'))).toBeVisible();
|
|
||||||
});
|
|
||||||
13
example/e2e/__integration_tests__/index.test.tsx
Normal file
13
example/e2e/__integration_tests__/index.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { page } from '../config/setup-playwright';
|
||||||
|
|
||||||
|
it('loads the example app', async () => {
|
||||||
|
const snapshot = await page.accessibility.snapshot();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expect(snapshot?.children?.find((it) => it.role === 'heading')?.name).toBe(
|
||||||
|
'Examples'
|
||||||
|
);
|
||||||
|
const title = await page.$eval('[role=heading]', (el) => el.textContent);
|
||||||
|
|
||||||
|
expect(title).toBe('Examples');
|
||||||
|
});
|
||||||
13
example/e2e/__integration_tests__/server.test.tsx
Normal file
13
example/e2e/__integration_tests__/server.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import fetch from 'node-fetch';
|
||||||
|
import cheerio from 'cheerio';
|
||||||
|
|
||||||
|
const server = 'http://localhost:3275';
|
||||||
|
|
||||||
|
it('renders the home page', async () => {
|
||||||
|
const res = await fetch(server);
|
||||||
|
const html = await res.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('title').text()).toBe('Examples');
|
||||||
|
});
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"setupFilesAfterEnv": ["./init.js"],
|
|
||||||
"testEnvironment": "node",
|
|
||||||
"reporters": ["detox/runners/jest/streamlineReporter"],
|
|
||||||
"verbose": true
|
|
||||||
}
|
|
||||||
24
example/e2e/config/setup-playwright.tsx
Normal file
24
example/e2e/config/setup-playwright.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
import { chromium, Browser, BrowserContext, Page } from 'playwright';
|
||||||
|
|
||||||
|
let browser: Browser;
|
||||||
|
let context: BrowserContext;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
browser = await chromium.launch();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
context = await browser.newContext();
|
||||||
|
page = await context.newPage();
|
||||||
|
|
||||||
|
await page.goto('http://localhost:3579');
|
||||||
|
});
|
||||||
|
|
||||||
|
export { browser, context, page };
|
||||||
16
example/e2e/config/setup-server.tsx
Normal file
16
example/e2e/config/setup-server.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { setup } from 'jest-dev-server';
|
||||||
|
|
||||||
|
export default async function () {
|
||||||
|
await setup([
|
||||||
|
{
|
||||||
|
command: 'yarn serve -l 3579 web-build',
|
||||||
|
launchTimeout: 50000,
|
||||||
|
port: 3579,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: 'yarn server',
|
||||||
|
launchTimeout: 50000,
|
||||||
|
port: 3275,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
5
example/e2e/config/teardown-server.tsx
Normal file
5
example/e2e/config/teardown-server.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { teardown } from 'jest-dev-server';
|
||||||
|
|
||||||
|
export default async function () {
|
||||||
|
await teardown();
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/* eslint-disable jest/no-jasmine-globals, import/no-commonjs */
|
|
||||||
|
|
||||||
const detox = require('detox');
|
|
||||||
const config = require('../../package.json').detox;
|
|
||||||
const adapter = require('detox/runners/jest/adapter');
|
|
||||||
const specReporter = require('detox/runners/jest/specReporter');
|
|
||||||
|
|
||||||
// Set the default timeout
|
|
||||||
jest.setTimeout(120000);
|
|
||||||
|
|
||||||
jasmine.getEnv().addReporter(adapter);
|
|
||||||
|
|
||||||
// This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level.
|
|
||||||
// This is strictly optional.
|
|
||||||
jasmine.getEnv().addReporter(specReporter);
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await detox.init(config);
|
|
||||||
}, 300000);
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await adapter.beforeEach();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await adapter.afterAll();
|
|
||||||
await detox.cleanup();
|
|
||||||
});
|
|
||||||
@@ -1,35 +1,38 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- boost-for-react-native (1.63.0)
|
- boost-for-react-native (1.63.0)
|
||||||
- DoubleConversion (1.1.6)
|
- DoubleConversion (1.1.6)
|
||||||
- EXAppLoaderProvider (8.0.0)
|
- EXBlur (8.1.0):
|
||||||
- EXBlur (8.0.0):
|
|
||||||
- UMCore
|
- UMCore
|
||||||
- EXConstants (8.0.0):
|
- EXConstants (9.0.0):
|
||||||
- UMConstantsInterface
|
- UMConstantsInterface
|
||||||
- UMCore
|
- UMCore
|
||||||
- EXErrorRecovery (1.0.0):
|
- EXErrorRecovery (1.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- EXFileSystem (8.0.0):
|
- EXFileSystem (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMFileSystemInterface
|
- UMFileSystemInterface
|
||||||
- EXFont (8.0.0):
|
- EXFont (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMFontInterface
|
- UMFontInterface
|
||||||
- EXKeepAwake (8.0.0):
|
- EXImageLoader (1.0.1):
|
||||||
|
- React-Core
|
||||||
- UMCore
|
- UMCore
|
||||||
- EXLinearGradient (8.0.0):
|
- UMImageLoaderInterface
|
||||||
|
- EXKeepAwake (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- EXLocation (8.0.0):
|
- EXLinearGradient (8.1.0):
|
||||||
|
- UMCore
|
||||||
|
- EXLocation (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMPermissionsInterface
|
- UMPermissionsInterface
|
||||||
- UMTaskManagerInterface
|
- UMTaskManagerInterface
|
||||||
- EXPermissions (8.0.0):
|
- EXPermissions (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMPermissionsInterface
|
- UMPermissionsInterface
|
||||||
- EXSQLite (8.0.0):
|
- EXSQLite (8.1.0):
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMFileSystemInterface
|
- UMFileSystemInterface
|
||||||
- EXWebBrowser (8.0.0):
|
- EXWebBrowser (8.2.1):
|
||||||
- UMCore
|
- UMCore
|
||||||
- FBLazyVector (0.61.5)
|
- FBLazyVector (0.61.5)
|
||||||
- FBReactNativeSpec (0.61.5):
|
- FBReactNativeSpec (0.61.5):
|
||||||
@@ -50,8 +53,6 @@ PODS:
|
|||||||
- glog
|
- glog
|
||||||
- glog (0.3.5)
|
- glog (0.3.5)
|
||||||
- RCTRequired (0.61.5)
|
- RCTRequired (0.61.5)
|
||||||
- RCTRestart (0.0.13):
|
|
||||||
- React
|
|
||||||
- RCTTypeSafety (0.61.5):
|
- RCTTypeSafety (0.61.5):
|
||||||
- FBLazyVector (= 0.61.5)
|
- FBLazyVector (= 0.61.5)
|
||||||
- Folly (= 2018.10.22.00)
|
- Folly (= 2018.10.22.00)
|
||||||
@@ -214,7 +215,9 @@ PODS:
|
|||||||
- React-cxxreact (= 0.61.5)
|
- React-cxxreact (= 0.61.5)
|
||||||
- React-jsi (= 0.61.5)
|
- React-jsi (= 0.61.5)
|
||||||
- React-jsinspector (0.61.5)
|
- React-jsinspector (0.61.5)
|
||||||
- react-native-safe-area-context (0.6.2):
|
- react-native-restart (0.0.15):
|
||||||
|
- React
|
||||||
|
- react-native-safe-area-context (1.0.0):
|
||||||
- React
|
- React
|
||||||
- React-RCTActionSheet (0.61.5):
|
- React-RCTActionSheet (0.61.5):
|
||||||
- React-Core/RCTActionSheetHeaders (= 0.61.5)
|
- React-Core/RCTActionSheetHeaders (= 0.61.5)
|
||||||
@@ -251,39 +254,41 @@ PODS:
|
|||||||
- React-cxxreact (= 0.61.5)
|
- React-cxxreact (= 0.61.5)
|
||||||
- React-jsi (= 0.61.5)
|
- React-jsi (= 0.61.5)
|
||||||
- ReactCommon/jscallinvoker (= 0.61.5)
|
- ReactCommon/jscallinvoker (= 0.61.5)
|
||||||
- RNCMaskedView (0.1.5):
|
- RNCMaskedView (0.1.10):
|
||||||
- React
|
- React
|
||||||
- RNGestureHandler (1.5.5):
|
- RNGestureHandler (1.6.1):
|
||||||
- React
|
- React
|
||||||
- RNReanimated (1.4.0):
|
- RNReanimated (1.8.0):
|
||||||
- React
|
- React
|
||||||
- RNScreens (2.0.0-alpha.33):
|
- RNScreens (2.7.0):
|
||||||
- React
|
- React
|
||||||
- UMBarCodeScannerInterface (5.0.0)
|
- UMAppLoader (1.0.2)
|
||||||
- UMCameraInterface (5.0.0)
|
- UMBarCodeScannerInterface (5.1.0)
|
||||||
- UMConstantsInterface (5.0.0)
|
- UMCameraInterface (5.1.0)
|
||||||
- UMCore (5.0.0)
|
- UMConstantsInterface (5.1.0)
|
||||||
- UMFaceDetectorInterface (5.0.0)
|
- UMCore (5.1.2)
|
||||||
- UMFileSystemInterface (5.0.0)
|
- UMFaceDetectorInterface (5.1.0)
|
||||||
- UMFontInterface (5.0.0)
|
- UMFileSystemInterface (5.1.0)
|
||||||
- UMImageLoaderInterface (5.0.0)
|
- UMFontInterface (5.1.0)
|
||||||
- UMPermissionsInterface (5.0.0)
|
- UMImageLoaderInterface (5.1.0)
|
||||||
- UMReactNativeAdapter (5.0.0):
|
- UMPermissionsInterface (5.1.0):
|
||||||
|
- UMCore
|
||||||
|
- UMReactNativeAdapter (5.2.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- UMCore
|
- UMCore
|
||||||
- UMFontInterface
|
- UMFontInterface
|
||||||
- UMSensorsInterface (5.0.0)
|
- UMSensorsInterface (5.1.0)
|
||||||
- UMTaskManagerInterface (5.0.0)
|
- UMTaskManagerInterface (5.1.0)
|
||||||
- Yoga (1.14.0)
|
- Yoga (1.14.0)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||||
- EXAppLoaderProvider (from `../../node_modules/expo-app-loader-provider/ios`)
|
|
||||||
- EXBlur (from `../../node_modules/expo-blur/ios`)
|
- EXBlur (from `../../node_modules/expo-blur/ios`)
|
||||||
- EXConstants (from `../../node_modules/expo-constants/ios`)
|
- EXConstants (from `../../node_modules/expo-constants/ios`)
|
||||||
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
|
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
|
||||||
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
|
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
|
||||||
- EXFont (from `../../node_modules/expo-font/ios`)
|
- EXFont (from `../../node_modules/expo-font/ios`)
|
||||||
|
- EXImageLoader (from `../../node_modules/expo-image-loader/ios`)
|
||||||
- EXKeepAwake (from `../../node_modules/expo-keep-awake/ios`)
|
- EXKeepAwake (from `../../node_modules/expo-keep-awake/ios`)
|
||||||
- EXLinearGradient (from `../../node_modules/expo-linear-gradient/ios`)
|
- EXLinearGradient (from `../../node_modules/expo-linear-gradient/ios`)
|
||||||
- EXLocation (from `../../node_modules/expo-location/ios`)
|
- EXLocation (from `../../node_modules/expo-location/ios`)
|
||||||
@@ -295,7 +300,6 @@ DEPENDENCIES:
|
|||||||
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||||
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
- RCTRestart (from `../../node_modules/react-native-restart/ios`)
|
|
||||||
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
|
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
|
||||||
- React (from `../../node_modules/react-native/`)
|
- React (from `../../node_modules/react-native/`)
|
||||||
- React-Core (from `../../node_modules/react-native/`)
|
- React-Core (from `../../node_modules/react-native/`)
|
||||||
@@ -306,6 +310,7 @@ DEPENDENCIES:
|
|||||||
- React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`)
|
- React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`)
|
||||||
- React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
|
- React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||||
- React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector`)
|
- React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector`)
|
||||||
|
- react-native-restart (from `../../node_modules/react-native-restart`)
|
||||||
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
|
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
|
||||||
- React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
|
- React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||||
- React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`)
|
- React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`)
|
||||||
@@ -322,10 +327,11 @@ DEPENDENCIES:
|
|||||||
- RNGestureHandler (from `../../node_modules/react-native-gesture-handler`)
|
- RNGestureHandler (from `../../node_modules/react-native-gesture-handler`)
|
||||||
- RNReanimated (from `../../node_modules/react-native-reanimated`)
|
- RNReanimated (from `../../node_modules/react-native-reanimated`)
|
||||||
- RNScreens (from `../../node_modules/react-native-screens`)
|
- RNScreens (from `../../node_modules/react-native-screens`)
|
||||||
|
- UMAppLoader (from `../../node_modules/unimodules-app-loader/ios`)
|
||||||
- UMBarCodeScannerInterface (from `../../node_modules/unimodules-barcode-scanner-interface/ios`)
|
- UMBarCodeScannerInterface (from `../../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||||
- UMCameraInterface (from `../../node_modules/unimodules-camera-interface/ios`)
|
- UMCameraInterface (from `../../node_modules/unimodules-camera-interface/ios`)
|
||||||
- UMConstantsInterface (from `../../node_modules/unimodules-constants-interface/ios`)
|
- UMConstantsInterface (from `../../node_modules/unimodules-constants-interface/ios`)
|
||||||
- "UMCore (from `../../node_modules/@unimodules/core/ios`)"
|
- "UMCore (from `../../node_modules/react-native-unimodules/node_modules/@unimodules/core/ios`)"
|
||||||
- UMFaceDetectorInterface (from `../../node_modules/unimodules-face-detector-interface/ios`)
|
- UMFaceDetectorInterface (from `../../node_modules/unimodules-face-detector-interface/ios`)
|
||||||
- UMFileSystemInterface (from `../../node_modules/unimodules-file-system-interface/ios`)
|
- UMFileSystemInterface (from `../../node_modules/unimodules-file-system-interface/ios`)
|
||||||
- UMFontInterface (from `../../node_modules/unimodules-font-interface/ios`)
|
- UMFontInterface (from `../../node_modules/unimodules-font-interface/ios`)
|
||||||
@@ -343,42 +349,30 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
DoubleConversion:
|
DoubleConversion:
|
||||||
:podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
:podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||||
EXAppLoaderProvider:
|
|
||||||
:path: !ruby/object:Pathname
|
|
||||||
path: "../../node_modules/expo-app-loader-provider/ios"
|
|
||||||
EXBlur:
|
EXBlur:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-blur/ios"
|
||||||
path: "../../node_modules/expo-blur/ios"
|
|
||||||
EXConstants:
|
EXConstants:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-constants/ios"
|
||||||
path: "../../node_modules/expo-constants/ios"
|
|
||||||
EXErrorRecovery:
|
EXErrorRecovery:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-error-recovery/ios"
|
||||||
path: "../../node_modules/expo-error-recovery/ios"
|
|
||||||
EXFileSystem:
|
EXFileSystem:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-file-system/ios"
|
||||||
path: "../../node_modules/expo-file-system/ios"
|
|
||||||
EXFont:
|
EXFont:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-font/ios"
|
||||||
path: "../../node_modules/expo-font/ios"
|
EXImageLoader:
|
||||||
|
:path: "../../node_modules/expo-image-loader/ios"
|
||||||
EXKeepAwake:
|
EXKeepAwake:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-keep-awake/ios"
|
||||||
path: "../../node_modules/expo-keep-awake/ios"
|
|
||||||
EXLinearGradient:
|
EXLinearGradient:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-linear-gradient/ios"
|
||||||
path: "../../node_modules/expo-linear-gradient/ios"
|
|
||||||
EXLocation:
|
EXLocation:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-location/ios"
|
||||||
path: "../../node_modules/expo-location/ios"
|
|
||||||
EXPermissions:
|
EXPermissions:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-permissions/ios"
|
||||||
path: "../../node_modules/expo-permissions/ios"
|
|
||||||
EXSQLite:
|
EXSQLite:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-sqlite/ios"
|
||||||
path: "../../node_modules/expo-sqlite/ios"
|
|
||||||
EXWebBrowser:
|
EXWebBrowser:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/expo-web-browser/ios"
|
||||||
path: "../../node_modules/expo-web-browser/ios"
|
|
||||||
FBLazyVector:
|
FBLazyVector:
|
||||||
:path: "../../node_modules/react-native/Libraries/FBLazyVector"
|
:path: "../../node_modules/react-native/Libraries/FBLazyVector"
|
||||||
FBReactNativeSpec:
|
FBReactNativeSpec:
|
||||||
@@ -389,8 +383,6 @@ EXTERNAL SOURCES:
|
|||||||
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
|
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||||
RCTRequired:
|
RCTRequired:
|
||||||
:path: "../../node_modules/react-native/Libraries/RCTRequired"
|
:path: "../../node_modules/react-native/Libraries/RCTRequired"
|
||||||
RCTRestart:
|
|
||||||
:path: "../../node_modules/react-native-restart/ios"
|
|
||||||
RCTTypeSafety:
|
RCTTypeSafety:
|
||||||
:path: "../../node_modules/react-native/Libraries/TypeSafety"
|
:path: "../../node_modules/react-native/Libraries/TypeSafety"
|
||||||
React:
|
React:
|
||||||
@@ -407,6 +399,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../node_modules/react-native/ReactCommon/jsiexecutor"
|
:path: "../../node_modules/react-native/ReactCommon/jsiexecutor"
|
||||||
React-jsinspector:
|
React-jsinspector:
|
||||||
:path: "../../node_modules/react-native/ReactCommon/jsinspector"
|
:path: "../../node_modules/react-native/ReactCommon/jsinspector"
|
||||||
|
react-native-restart:
|
||||||
|
:path: "../../node_modules/react-native-restart"
|
||||||
react-native-safe-area-context:
|
react-native-safe-area-context:
|
||||||
:path: "../../node_modules/react-native-safe-area-context"
|
:path: "../../node_modules/react-native-safe-area-context"
|
||||||
React-RCTActionSheet:
|
React-RCTActionSheet:
|
||||||
@@ -437,66 +431,55 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../node_modules/react-native-reanimated"
|
:path: "../../node_modules/react-native-reanimated"
|
||||||
RNScreens:
|
RNScreens:
|
||||||
:path: "../../node_modules/react-native-screens"
|
:path: "../../node_modules/react-native-screens"
|
||||||
|
UMAppLoader:
|
||||||
|
:path: "../../node_modules/unimodules-app-loader/ios"
|
||||||
UMBarCodeScannerInterface:
|
UMBarCodeScannerInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||||
path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
|
|
||||||
UMCameraInterface:
|
UMCameraInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-camera-interface/ios"
|
||||||
path: "../../node_modules/unimodules-camera-interface/ios"
|
|
||||||
UMConstantsInterface:
|
UMConstantsInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-constants-interface/ios"
|
||||||
path: "../../node_modules/unimodules-constants-interface/ios"
|
|
||||||
UMCore:
|
UMCore:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/react-native-unimodules/node_modules/@unimodules/core/ios"
|
||||||
path: "../../node_modules/@unimodules/core/ios"
|
|
||||||
UMFaceDetectorInterface:
|
UMFaceDetectorInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-face-detector-interface/ios"
|
||||||
path: "../../node_modules/unimodules-face-detector-interface/ios"
|
|
||||||
UMFileSystemInterface:
|
UMFileSystemInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-file-system-interface/ios"
|
||||||
path: "../../node_modules/unimodules-file-system-interface/ios"
|
|
||||||
UMFontInterface:
|
UMFontInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-font-interface/ios"
|
||||||
path: "../../node_modules/unimodules-font-interface/ios"
|
|
||||||
UMImageLoaderInterface:
|
UMImageLoaderInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-image-loader-interface/ios"
|
||||||
path: "../../node_modules/unimodules-image-loader-interface/ios"
|
|
||||||
UMPermissionsInterface:
|
UMPermissionsInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-permissions-interface/ios"
|
||||||
path: "../../node_modules/unimodules-permissions-interface/ios"
|
|
||||||
UMReactNativeAdapter:
|
UMReactNativeAdapter:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/@unimodules/react-native-adapter/ios"
|
||||||
path: "../../node_modules/@unimodules/react-native-adapter/ios"
|
|
||||||
UMSensorsInterface:
|
UMSensorsInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-sensors-interface/ios"
|
||||||
path: "../../node_modules/unimodules-sensors-interface/ios"
|
|
||||||
UMTaskManagerInterface:
|
UMTaskManagerInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: "../../node_modules/unimodules-task-manager-interface/ios"
|
||||||
path: "../../node_modules/unimodules-task-manager-interface/ios"
|
|
||||||
Yoga:
|
Yoga:
|
||||||
:path: "../../node_modules/react-native/ReactCommon/yoga"
|
:path: "../../node_modules/react-native/ReactCommon/yoga"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||||
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49
|
EXBlur: aa14d84bff6e9c2232fbcaf54ad809eee1cc41dc
|
||||||
EXBlur: d1604f66f89a9414f5ee65dfb23874437c1bb147
|
EXConstants: 5304709b1bea70a4828f48ba4c7fc3ec3b2d9b17
|
||||||
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146
|
EXErrorRecovery: 8f4c21ab2f51bf75defe4536f841a37de59b0661
|
||||||
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2
|
EXFileSystem: cf4232ba7c62dc49b78c2d36005f97b6fddf0b01
|
||||||
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6
|
EXFont: 8326ecf966be559f7ced7c8e221a32fc4d9ed8b0
|
||||||
EXFont: 6187b5ab46ee578d5f8e7f2ea092752e78772235
|
EXImageLoader: 5ad6896fa1ef2ee814b551873cbf7a7baccc694a
|
||||||
EXKeepAwake: 66e9f80b6d129633725a0e42f8d285c229876811
|
EXKeepAwake: d045bc2cf1ad5a04f0323cc7c894b95b414042e0
|
||||||
EXLinearGradient: 75f302f9d6484267a3f6d3252df2e7a5f00e716a
|
EXLinearGradient: 97d8095d1e4ad96f7893e010e564796ed8aeea42
|
||||||
EXLocation: 3c75d012ca92eed94d4338778d79c49d1252393a
|
EXLocation: bbd487fd96a18a3ad9725389bbb94c4a5f78edf3
|
||||||
EXPermissions: 9bc08859a675d291e89be9a0870155c27c16ac35
|
EXPermissions: 24b97f734ce9172d245a5be38ad9ccfcb6135964
|
||||||
EXSQLite: 220226a354912b100dfe913f5fe6f31762c8927e
|
EXSQLite: 877ad6c8eb169353a2f94d5ad26510ffadd46a1f
|
||||||
EXWebBrowser: db32607359fb7b55b7b7b91df32dd3d8355bb3b7
|
EXWebBrowser: 5902f99ac5ac551e5c82ff46f13a337b323aa9ea
|
||||||
FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
|
FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
|
||||||
FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
|
FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
|
||||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||||
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
|
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
|
||||||
RCTRestart: dd19aab87fc1118e05b6b5b91b959105647f56b4
|
|
||||||
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
|
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
|
||||||
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
|
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
|
||||||
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
|
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
|
||||||
@@ -505,7 +488,8 @@ SPEC CHECKSUMS:
|
|||||||
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
|
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
|
||||||
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
|
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
|
||||||
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
|
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
|
||||||
react-native-safe-area-context: 25260c5d0b9c53fd7aa88e569e2edae72af1f6a3
|
react-native-restart: fff228304625f55de2ebd4de43938110f4c888ed
|
||||||
|
react-native-safe-area-context: a346c75f2288147527365ce27b59ca6d38c27805
|
||||||
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
||||||
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
||||||
React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
|
React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
|
||||||
@@ -516,24 +500,25 @@ SPEC CHECKSUMS:
|
|||||||
React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
|
React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
|
||||||
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
|
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
|
||||||
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
|
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
|
||||||
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
|
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
|
||||||
RNGestureHandler: d2270608171c868581b840cfc692f2962c05cd17
|
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
|
||||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
RNReanimated: 955cf4068714003d2f1a6e2bae3fb1118f359aff
|
||||||
RNScreens: 1c7fd499b915c77c21e8e6c327890c5af9b4cf7e
|
RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706
|
||||||
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
|
UMAppLoader: ee77a072f9e15128f777ccd6d2d00f52ab4387e6
|
||||||
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
|
UMBarCodeScannerInterface: 9dc692b87e5f20fe277fa57aa47f45d418c3cc6c
|
||||||
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c
|
UMCameraInterface: 625878bbf2ba188a8548675e1d1d2e438a653e6d
|
||||||
UMCore: 7ab08669a8bb2e61f557c1fe9784521cb5aa28e3
|
UMConstantsInterface: 64060cf86587bcd90b1dbd804cceb6d377a308c1
|
||||||
UMFaceDetectorInterface: ce14e8e597f6a52aa66e4ab956cb5bff4fa8acf8
|
UMCore: eb200e882eadafcd31ead290770835fd648c0945
|
||||||
UMFileSystemInterface: 2ed004c9620f43f0b36b33c42ce668500850d6a4
|
UMFaceDetectorInterface: d6677d6ddc9ab95a0ca857aa7f8ba76656cc770f
|
||||||
UMFontInterface: 24fbc0a02ade6c60ad3ee3e2b5d597c8dcfc3208
|
UMFileSystemInterface: c70ea7147198b9807080f3597f26236be49b0165
|
||||||
UMImageLoaderInterface: 3976a14c588341228881ff75970fbabf122efca4
|
UMFontInterface: d9d3b27af698c5389ae9e20b99ef56a083f491fb
|
||||||
UMPermissionsInterface: 2abf9f7f4aa7110e27beaf634a7deda2d50ff3d7
|
UMImageLoaderInterface: 14dd2c46c67167491effc9e91250e9510f12709e
|
||||||
UMReactNativeAdapter: 230406e3335a8dbd4c9c0e654488a1cf3b44552f
|
UMPermissionsInterface: 5e83a9167c177e4a0f0a3539345983cc749efb3e
|
||||||
UMSensorsInterface: d708a892ef1500bdd9fc3ff03f7836c66d1634d3
|
UMReactNativeAdapter: 126da3486c1a1f11945b649d557d6c2ebb9407b2
|
||||||
UMTaskManagerInterface: a98e37a576a5220bf43b8faf33cfdc129d2f441d
|
UMSensorsInterface: 48941f70175e2975af1a9386c6d6cb16d8126805
|
||||||
|
UMTaskManagerInterface: cb890c79c63885504ddc0efd7a7d01481760aca2
|
||||||
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
|
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
|
||||||
|
|
||||||
PODFILE CHECKSUM: c48a21ff513d3eadafa50f8797207ef2be75e234
|
PODFILE CHECKSUM: c48a21ff513d3eadafa50f8797207ef2be75e234
|
||||||
|
|
||||||
COCOAPODS: 1.8.4
|
COCOAPODS: 1.9.1
|
||||||
|
|||||||
6
example/jest.config.js
Normal file
6
example/jest.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
testRegex: '/__integration_tests__/.*\\.(test|spec)\\.(js|tsx?)$',
|
||||||
|
globalSetup: './e2e/config/setup-server.tsx',
|
||||||
|
globalTeardown: './e2e/config/teardown-server.tsx',
|
||||||
|
setupFilesAfterEnv: ['./e2e/config/setup-playwright.tsx'],
|
||||||
|
};
|
||||||
@@ -8,26 +8,25 @@ const blacklist = require('metro-config/src/defaults/blacklist');
|
|||||||
const root = path.resolve(__dirname, '..');
|
const root = path.resolve(__dirname, '..');
|
||||||
const packages = path.resolve(root, 'packages');
|
const packages = path.resolve(root, 'packages');
|
||||||
|
|
||||||
|
const workspaces = fs
|
||||||
|
// List all packages under `packages/`
|
||||||
|
.readdirSync(packages)
|
||||||
|
// Ignore hidden files such as .DS_Store
|
||||||
|
.filter((p) => !p.startsWith('.'))
|
||||||
|
.map((p) => path.join(packages, p));
|
||||||
|
|
||||||
// Get the list of dependencies for all packages in the monorepo
|
// Get the list of dependencies for all packages in the monorepo
|
||||||
const modules = ['@expo/vector-icons']
|
const modules = ['@expo/vector-icons']
|
||||||
.concat(
|
.concat(
|
||||||
...fs
|
...workspaces.map((it) => {
|
||||||
// List all packages under `packages/`
|
const pak = JSON.parse(
|
||||||
.readdirSync(packages)
|
fs.readFileSync(path.join(it, 'package.json'), 'utf8')
|
||||||
// Ignore hidden files such as .DS_Store
|
);
|
||||||
.filter(p => !p.startsWith('.'))
|
|
||||||
.map(p => {
|
|
||||||
const pak = JSON.parse(
|
|
||||||
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
|
|
||||||
);
|
|
||||||
|
|
||||||
// We need to collect list of deps that this package imports
|
// We need to make sure that only one version is loaded for peerDependencies
|
||||||
// Collecting both dependencies are peerDependencies should do it
|
// So we blacklist them at the root, and alias them to the versions in example's node_modules
|
||||||
return Object.keys({
|
return pak.peerDependencies ? Object.keys(pak.peerDependencies) : [];
|
||||||
...pak.dependencies,
|
})
|
||||||
...pak.peerDependencies,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
.sort()
|
.sort()
|
||||||
.filter(
|
.filter(
|
||||||
@@ -45,15 +44,16 @@ module.exports = {
|
|||||||
watchFolders: [root],
|
watchFolders: [root],
|
||||||
|
|
||||||
resolver: {
|
resolver: {
|
||||||
// We need to blacklist `node_modules` of all our packages
|
// We need to blacklist the peerDependencies we've collected in packages' node_modules
|
||||||
// This will avoid Metro throwing duplicate module errors
|
|
||||||
blacklistRE: blacklist(
|
blacklistRE: blacklist(
|
||||||
fs
|
[].concat(
|
||||||
.readdirSync(packages)
|
...workspaces.map((it) =>
|
||||||
.map(p => path.join(packages, p))
|
modules.map(
|
||||||
.map(
|
(m) =>
|
||||||
it => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
new RegExp(`^${escape(path.join(it, 'node_modules', m))}\\/.*$`)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
// When we import a package from the monorepo, metro won't be able to find their deps
|
// When we import a package from the monorepo, metro won't be able to find their deps
|
||||||
@@ -65,7 +65,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
enhanceMiddleware: middleware => {
|
enhanceMiddleware: (middleware) => {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
// When an asset is imported outside the project root, it has wrong path on Android
|
// When an asset is imported outside the project root, it has wrong path on Android
|
||||||
// This happens for the back button in stack, so we fix the path to correct one
|
// This happens for the back button in stack, so we fix the path to correct one
|
||||||
|
|||||||
@@ -1,42 +1,60 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/example",
|
"name": "@react-navigation/example",
|
||||||
"description": "Demo app to showcase various functionality of React Navigation",
|
"description": "Demo app to showcase various functionality of React Navigation",
|
||||||
"version": "5.0.0",
|
"version": "5.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"web": "expo start:web",
|
"web": "expo start:web",
|
||||||
"native": "react-native start",
|
"native": "react-native start",
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
"ios": "react-native run-ios"
|
"ios": "react-native run-ios",
|
||||||
|
"server": "nodemon -e '.js,.ts,.tsx' --exec \"babel-node -i '/node_modules[/\\](?react-native)/' -x '.web.tsx,.web.ts,.web.js,.tsx,.ts,.js' --config-file ./server/babel.config.js server\"",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^10.0.0",
|
"@expo/vector-icons": "^10.2.0",
|
||||||
"@react-native-community/masked-view": "0.1.6",
|
"@react-native-community/masked-view": "^0.1.10",
|
||||||
"@types/react-native-restart": "^0.0.0",
|
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"expo": "^36.0.2",
|
"expo": "^37.0.8",
|
||||||
"expo-asset": "~8.0.0",
|
"expo-asset": "~8.1.3",
|
||||||
"expo-blur": "^8.0.0",
|
"expo-blur": "~8.1.0",
|
||||||
|
"koa": "^2.12.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-dom": "~16.9.0",
|
"react-dom": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-gesture-handler": "^1.5.6",
|
"react-native-gesture-handler": "^1.6.0",
|
||||||
"react-native-paper": "^3.6.0",
|
"react-native-paper": "^3.10.1",
|
||||||
"react-native-reanimated": "^1.7.0",
|
"react-native-reanimated": "^1.8.0",
|
||||||
"react-native-restart": "^0.0.13",
|
"react-native-restart": "^0.0.15",
|
||||||
"react-native-safe-area-context": "^0.7.2",
|
"react-native-safe-area-context": "^1.0.0",
|
||||||
"react-native-screens": "^2.0.0-beta.2",
|
"react-native-screens": "^2.7.0",
|
||||||
"react-native-tab-view": "2.13.0",
|
"react-native-tab-view": "2.14.0",
|
||||||
"react-native-unimodules": "^0.7.0",
|
"react-native-unimodules": "~0.9.1",
|
||||||
|
"react-native-vector-icons": "^6.6.0",
|
||||||
"react-native-web": "^0.11.7"
|
"react-native-web": "^0.11.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@expo/webpack-config": "^0.10.12",
|
"@babel/node": "^7.8.7",
|
||||||
"@types/react": "^16.9.19",
|
"@expo/webpack-config": "^0.11.19",
|
||||||
"@types/react-native": "^0.60.30",
|
"@types/cheerio": "^0.22.18",
|
||||||
"babel-preset-expo": "^8.0.0",
|
"@types/jest-dev-server": "^4.2.0",
|
||||||
"expo-cli": "^3.11.9",
|
"@types/koa": "^2.11.3",
|
||||||
"typescript": "^3.7.5"
|
"@types/node-fetch": "^2.5.7",
|
||||||
|
"@types/react": "^16.9.34",
|
||||||
|
"@types/react-dom": "^16.9.8",
|
||||||
|
"@types/react-native": "^0.62.7",
|
||||||
|
"babel-plugin-module-resolver": "^4.0.0",
|
||||||
|
"babel-preset-expo": "^8.1.0",
|
||||||
|
"cheerio": "^1.0.0-rc.3",
|
||||||
|
"expo-cli": "^3.20.1",
|
||||||
|
"jest": "^26.0.1",
|
||||||
|
"jest-dev-server": "^4.4.0",
|
||||||
|
"mock-require-assets": "^0.0.1",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"nodemon": "^2.0.4",
|
||||||
|
"playwright": "^0.14.0",
|
||||||
|
"serve": "^11.3.0",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
example/server/babel.config.js
Normal file
40
example/server/babel.config.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const packages = path.resolve(__dirname, '..', '..', 'packages');
|
||||||
|
|
||||||
|
const alias = Object.fromEntries(
|
||||||
|
fs
|
||||||
|
.readdirSync(packages)
|
||||||
|
.filter((name) => !name.startsWith('.'))
|
||||||
|
.map((name) => [
|
||||||
|
`@react-navigation/${name}`,
|
||||||
|
path.resolve(
|
||||||
|
packages,
|
||||||
|
name,
|
||||||
|
require(`../../packages/${name}/package.json`).source
|
||||||
|
),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-env',
|
||||||
|
'@babel/preset-flow',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
'@babel/preset-react',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
[
|
||||||
|
'module-resolver',
|
||||||
|
{
|
||||||
|
root: ['..'],
|
||||||
|
alias: {
|
||||||
|
'react-native': 'react-native-web',
|
||||||
|
...alias,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
54
example/server/index.tsx
Normal file
54
example/server/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import './resolve-hooks';
|
||||||
|
|
||||||
|
import Koa from 'koa';
|
||||||
|
import * as React from 'react';
|
||||||
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
import { AppRegistry } from 'react-native-web';
|
||||||
|
import { ServerContainer, ServerContainerRef } from '@react-navigation/native';
|
||||||
|
import App from '../src/index';
|
||||||
|
|
||||||
|
AppRegistry.registerComponent('App', () => App);
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3275;
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
|
||||||
|
app.use(async (ctx) => {
|
||||||
|
const { element, getStyleElement } = AppRegistry.getApplication('App');
|
||||||
|
|
||||||
|
const ref = React.createRef<ServerContainerRef>();
|
||||||
|
|
||||||
|
const html = ReactDOMServer.renderToString(
|
||||||
|
<ServerContainer
|
||||||
|
ref={ref}
|
||||||
|
location={{ pathname: ctx.path, search: ctx.search }}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
</ServerContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());
|
||||||
|
|
||||||
|
const document = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="height: 100%">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
|
||||||
|
>
|
||||||
|
${css}
|
||||||
|
<title>${ref.current?.getCurrentOptions()?.title}</title>
|
||||||
|
<body style="min-height: 100%">
|
||||||
|
<div id="root" style="display: flex; min-height: 100vh">
|
||||||
|
${html}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
ctx.body = document;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Running at http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
12
example/server/resolve-hooks.tsx
Normal file
12
example/server/resolve-hooks.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'mock-require-assets';
|
||||||
|
|
||||||
|
import Module from 'module';
|
||||||
|
|
||||||
|
// We need to make sure that .web.xx extensions are resolved before .xx
|
||||||
|
// @ts-ignore
|
||||||
|
Module._extensions = Object.fromEntries(
|
||||||
|
// @ts-ignore
|
||||||
|
Object.entries(Module._extensions).sort((a, b) => {
|
||||||
|
return b[0].split('.').length - a[0].split('.').length;
|
||||||
|
})
|
||||||
|
);
|
||||||
3
example/src/AsyncStorage.native.tsx
Normal file
3
example/src/AsyncStorage.native.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { AsyncStorage } from 'react-native';
|
||||||
|
|
||||||
|
export default AsyncStorage;
|
||||||
14
example/src/AsyncStorage.tsx
Normal file
14
example/src/AsyncStorage.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default {
|
||||||
|
getItem(key: string) {
|
||||||
|
return Promise.resolve(localStorage.getItem(key));
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
return Promise.resolve(localStorage.setItem(key, value));
|
||||||
|
},
|
||||||
|
removeItem(key: string) {
|
||||||
|
return Promise.resolve(localStorage.removeItem(key));
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
return Promise.resolve(localStorage.clear());
|
||||||
|
},
|
||||||
|
};
|
||||||
11
example/src/Restart.native.tsx
Normal file
11
example/src/Restart.native.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import RNRestart from 'react-native-restart';
|
||||||
|
import { Updates } from 'expo';
|
||||||
|
|
||||||
|
export function restartApp() {
|
||||||
|
// @ts-ignore
|
||||||
|
if (global.Expo) {
|
||||||
|
Updates.reloadFromCache();
|
||||||
|
} else {
|
||||||
|
RNRestart.Restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example/src/Restart.tsx
Normal file
1
example/src/Restart.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export function restartApp() {}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { Button } from 'react-native-paper';
|
||||||
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
import {
|
||||||
|
createBottomTabNavigator,
|
||||||
|
BottomTabNavigationProp,
|
||||||
|
} from '@react-navigation/bottom-tabs';
|
||||||
import TouchableBounce from '../Shared/TouchableBounce';
|
import TouchableBounce from '../Shared/TouchableBounce';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
import Contacts from '../Shared/Contacts';
|
import Contacts from '../Shared/Contacts';
|
||||||
@@ -22,13 +27,46 @@ type BottomTabParams = {
|
|||||||
Chat: undefined;
|
Chat: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
|
const AlbumsScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: BottomTabNavigationProp<BottomTabParams>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.setOptions({ tabBarVisible: false })}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Hide tab bar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.setOptions({ tabBarVisible: true })}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Show tab bar
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||||
|
|
||||||
export default function BottomTabsScreen() {
|
export default function BottomTabsScreen() {
|
||||||
return (
|
return (
|
||||||
<BottomTabs.Navigator
|
<BottomTabs.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
tabBarButton: props => <TouchableBounce {...props} />,
|
tabBarButton:
|
||||||
|
Platform.OS === 'web'
|
||||||
|
? undefined
|
||||||
|
: (props) => <TouchableBounce {...props} />,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
@@ -38,7 +76,7 @@ export default function BottomTabsScreen() {
|
|||||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||||
</BottomTabs.Screen>
|
</BottomTabs.Screen>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
@@ -58,7 +96,7 @@ export default function BottomTabsScreen() {
|
|||||||
/>
|
/>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Albums"
|
name="Albums"
|
||||||
component={Albums}
|
component={AlbumsScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Albums',
|
title: 'Albums',
|
||||||
tabBarIcon: getTabBarIcon('image-album'),
|
tabBarIcon: getTabBarIcon('image-album'),
|
||||||
@@ -67,3 +105,13 @@ export default function BottomTabsScreen() {
|
|||||||
</BottomTabs.Navigator>
|
</BottomTabs.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
createCompatNavigatorFactory,
|
createCompatNavigatorFactory,
|
||||||
@@ -11,25 +11,32 @@ import {
|
|||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
|
import NewsFeed from '../Shared/NewsFeed';
|
||||||
|
|
||||||
type CompatStackParams = {
|
type CompatStackParams = {
|
||||||
Article: { author: string };
|
Albums: undefined;
|
||||||
Album: undefined;
|
Nested: { author: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
type NestedStackParams = {
|
||||||
CompatStackParams,
|
Feed: undefined;
|
||||||
'Article'
|
Article: { author: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
|
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||||
|
CompatStackParams
|
||||||
>> = ({ navigation }) => {
|
>> = ({ navigation }) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.push('Album')}
|
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Push album
|
Push nested
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
@@ -39,24 +46,20 @@ const ArticleScreen: CompatScreenType<StackNavigationProp<
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: navigation.getParam('author') }} />
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
|
||||||
title: `Article by ${navigation.getParam('author')}`,
|
navigation,
|
||||||
});
|
}) => {
|
||||||
|
|
||||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
|
||||||
CompatStackParams
|
|
||||||
>> = ({ navigation }) => {
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
onPress={() => navigation.push('Article')}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Push article
|
Push article
|
||||||
@@ -69,22 +72,69 @@ const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums />
|
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CompatStack = createCompatNavigatorFactory(createStackNavigator)<
|
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||||
|
NestedStackParams,
|
||||||
|
'Article'
|
||||||
|
>> = ({ navigation }) => {
|
||||||
|
navigation.dangerouslyGetParent();
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={() => navigation.push('Albums')}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Push albums
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Article
|
||||||
|
author={{ name: navigation.getParam('author') }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
||||||
|
title: `Article by ${navigation.getParam('author')}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createCompatStackNavigator = createCompatNavigatorFactory(
|
||||||
|
createStackNavigator
|
||||||
|
);
|
||||||
|
|
||||||
|
const CompatStack = createCompatStackNavigator<
|
||||||
StackNavigationProp<CompatStackParams>
|
StackNavigationProp<CompatStackParams>
|
||||||
>(
|
>(
|
||||||
{
|
{
|
||||||
Article: {
|
Albums: AlbumsScreen,
|
||||||
screen: ArticleScreen,
|
Nested: {
|
||||||
|
screen: createCompatStackNavigator<
|
||||||
|
StackNavigationProp<NestedStackParams>
|
||||||
|
>(
|
||||||
|
{
|
||||||
|
Feed: FeedScreen,
|
||||||
|
Article: ArticleScreen,
|
||||||
|
},
|
||||||
|
{ navigationOptions: { headerShown: false } }
|
||||||
|
),
|
||||||
params: {
|
params: {
|
||||||
author: 'Gandalf',
|
author: 'Gandalf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Album: AlbumsScreen,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mode: 'modal',
|
mode: 'modal',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet } from 'react-native';
|
||||||
import { Title, Button } from 'react-native-paper';
|
import { Title, Button } from 'react-native-paper';
|
||||||
import { Feather } from '@expo/vector-icons';
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
|
|
||||||
type BottomTabParams = {
|
type BottomTabParams = {
|
||||||
@@ -15,7 +15,7 @@ export default function BottomTabsScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomTabs.Navigator>
|
<BottomTabs.Navigator>
|
||||||
{tabs.map(i => (
|
{tabs.map((i) => (
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
key={i}
|
key={i}
|
||||||
name={`tab-${i}`}
|
name={`tab-${i}`}
|
||||||
@@ -29,12 +29,14 @@ export default function BottomTabsScreen() {
|
|||||||
{() => (
|
{() => (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Title>Tab {i}</Title>
|
<Title>Tab {i}</Title>
|
||||||
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
|
<Button onPress={() => setTabs((tabs) => [...tabs, tabs.length])}>
|
||||||
Add a tab
|
Add a tab
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
|
setTabs((tabs) =>
|
||||||
|
tabs.length > 1 ? tabs.slice(0, -1) : tabs
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Remove a tab
|
Remove a tab
|
||||||
|
|||||||
162
example/src/Screens/LinkComponent.tsx
Normal file
162
example/src/Screens/LinkComponent.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||||
|
import { Button } from 'react-native-paper';
|
||||||
|
import {
|
||||||
|
Link,
|
||||||
|
StackActions,
|
||||||
|
RouteProp,
|
||||||
|
ParamListBase,
|
||||||
|
useLinkProps,
|
||||||
|
} from '@react-navigation/native';
|
||||||
|
import {
|
||||||
|
createStackNavigator,
|
||||||
|
StackNavigationProp,
|
||||||
|
} from '@react-navigation/stack';
|
||||||
|
import Article from '../Shared/Article';
|
||||||
|
import Albums from '../Shared/Albums';
|
||||||
|
|
||||||
|
type SimpleStackParams = {
|
||||||
|
Article: { author: string };
|
||||||
|
Albums: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
|
const LinkButton = ({
|
||||||
|
to,
|
||||||
|
...rest
|
||||||
|
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
||||||
|
const { onPress, ...props } = useLinkProps({ to });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
{...rest}
|
||||||
|
{...Platform.select({
|
||||||
|
web: { onClick: onPress } as any,
|
||||||
|
default: { onPress },
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArticleScreen = ({
|
||||||
|
navigation,
|
||||||
|
route,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Link
|
||||||
|
to="/link-component/music"
|
||||||
|
style={[styles.button, { padding: 8 }]}
|
||||||
|
>
|
||||||
|
Go to /link-component/music
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/link-component/music"
|
||||||
|
action={StackActions.replace('Albums')}
|
||||||
|
style={[styles.button, { padding: 8 }]}
|
||||||
|
>
|
||||||
|
Replace with /link-component/music
|
||||||
|
</Link>
|
||||||
|
<LinkButton
|
||||||
|
to="/link-component/music"
|
||||||
|
mode="contained"
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go to /link-component/music
|
||||||
|
</LinkButton>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Article
|
||||||
|
author={{ name: route.params.author }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlbumsScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Link
|
||||||
|
to="/link-component/article/babel"
|
||||||
|
style={[styles.button, { padding: 8 }]}
|
||||||
|
>
|
||||||
|
Go to /link-component/article
|
||||||
|
</Link>
|
||||||
|
<LinkButton
|
||||||
|
to="/link-component/article/babel"
|
||||||
|
mode="contained"
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go to /link-component/article
|
||||||
|
</LinkButton>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||||
|
|
||||||
|
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerShown: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleStack.Navigator {...rest}>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Article"
|
||||||
|
component={ArticleScreen}
|
||||||
|
options={({ route }) => ({
|
||||||
|
title: `Article by ${route.params.author}`,
|
||||||
|
})}
|
||||||
|
initialParams={{ author: 'Gandalf' }}
|
||||||
|
/>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Albums"
|
||||||
|
component={AlbumsScreen}
|
||||||
|
options={{ title: 'Albums' }}
|
||||||
|
/>
|
||||||
|
</SimpleStack.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttons: {
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
143
example/src/Screens/MasterDetail.tsx
Normal file
143
example/src/Screens/MasterDetail.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Dimensions, ScaledSize } from 'react-native';
|
||||||
|
import { Appbar } from 'react-native-paper';
|
||||||
|
import {
|
||||||
|
useTheme,
|
||||||
|
useNavigation,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/native';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import {
|
||||||
|
createDrawerNavigator,
|
||||||
|
DrawerNavigationProp,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerContentComponentProps,
|
||||||
|
DrawerContentOptions,
|
||||||
|
} from '@react-navigation/drawer';
|
||||||
|
import Article from '../Shared/Article';
|
||||||
|
import Albums from '../Shared/Albums';
|
||||||
|
import NewsFeed from '../Shared/NewsFeed';
|
||||||
|
|
||||||
|
type DrawerParams = {
|
||||||
|
Article: undefined;
|
||||||
|
NewsFeed: undefined;
|
||||||
|
Albums: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
|
||||||
|
|
||||||
|
const useIsLargeScreen = () => {
|
||||||
|
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);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return dimensions.width > 414;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({
|
||||||
|
onGoBack,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
onGoBack: () => void;
|
||||||
|
title: string;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
|
||||||
|
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
|
||||||
|
<Appbar.Content title={title} />
|
||||||
|
</Appbar.Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArticleScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Article" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<Article />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewsFeedScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Feed" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<NewsFeed />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Albums" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<Albums />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomDrawerContent = (
|
||||||
|
props: DrawerContentComponentProps<DrawerContentOptions>
|
||||||
|
) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
|
||||||
|
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
|
||||||
|
<Appbar.Content title="Pages" />
|
||||||
|
</Appbar.Header>
|
||||||
|
<DrawerContent {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Drawer = createDrawerNavigator<DrawerParams>();
|
||||||
|
|
||||||
|
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerShown: false,
|
||||||
|
gestureEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer.Navigator
|
||||||
|
openByDefault
|
||||||
|
drawerType={isLargeScreen ? 'permanent' : 'back'}
|
||||||
|
drawerStyle={isLargeScreen ? null : { width: '100%' }}
|
||||||
|
overlayColor="transparent"
|
||||||
|
drawerContent={(props) => <CustomDrawerContent {...props} />}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Drawer.Screen name="Article" component={ArticleScreen} />
|
||||||
|
<Drawer.Screen
|
||||||
|
name="NewsFeed"
|
||||||
|
component={NewsFeedScreen}
|
||||||
|
options={{ title: 'Feed' }}
|
||||||
|
/>
|
||||||
|
<Drawer.Screen
|
||||||
|
name="Albums"
|
||||||
|
component={AlbumsScreen}
|
||||||
|
options={{ title: 'Albums' }}
|
||||||
|
/>
|
||||||
|
</Drawer.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ export default function MaterialBottomTabsScreen() {
|
|||||||
tabBarColor: '#C9E7F8',
|
tabBarColor: '#C9E7F8',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||||
</MaterialBottomTabs.Screen>
|
</MaterialBottomTabs.Screen>
|
||||||
<MaterialBottomTabs.Screen
|
<MaterialBottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { ParamListBase } from '@react-navigation/native';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
import Contacts from '../Shared/Contacts';
|
import Contacts from '../Shared/Contacts';
|
||||||
@@ -12,7 +14,15 @@ type MaterialTopTabParams = {
|
|||||||
|
|
||||||
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
|
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
|
||||||
|
|
||||||
export default function MaterialTopTabsScreen() {
|
type Props = {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MaterialTopTabsScreen({ navigation }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
cardStyle: { flex: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaterialTopTabs.Navigator>
|
<MaterialTopTabs.Navigator>
|
||||||
<MaterialTopTabs.Screen
|
<MaterialTopTabs.Screen
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@@ -12,11 +12,13 @@ import Albums from '../Shared/Albums';
|
|||||||
|
|
||||||
type ModalStackParams = {
|
type ModalStackParams = {
|
||||||
Article: { author: string };
|
Article: { author: string };
|
||||||
Album: undefined;
|
Albums: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
|
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
const ArticleScreen = ({
|
const ArticleScreen = ({
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
@@ -29,7 +31,7 @@ const ArticleScreen = ({
|
|||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.push('Album')}
|
onPress={() => navigation.push('Albums')}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Push album
|
Push album
|
||||||
@@ -42,7 +44,10 @@ const ArticleScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
<Article
|
||||||
|
author={{ name: route.params.author }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -66,7 +71,7 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums scrollEnabled={false} />
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -107,9 +112,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
|||||||
initialParams={{ author: 'Gandalf' }}
|
initialParams={{ author: 'Gandalf' }}
|
||||||
/>
|
/>
|
||||||
<ModalPresentationStack.Screen
|
<ModalPresentationStack.Screen
|
||||||
name="Album"
|
name="Albums"
|
||||||
component={AlbumsScreen}
|
component={AlbumsScreen}
|
||||||
options={{ title: 'Album' }}
|
options={{ title: 'Albums' }}
|
||||||
/>
|
/>
|
||||||
</ModalPresentationStack.Navigator>
|
</ModalPresentationStack.Navigator>
|
||||||
);
|
);
|
||||||
|
|||||||
40
example/src/Screens/NotFound.tsx
Normal file
40
example/src/Screens/NotFound.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { Button } from 'react-native-paper';
|
||||||
|
|
||||||
|
const NotFoundScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: StackNavigationProp<{ Home: undefined }>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.title}>404 Not Found</Text>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={() => navigation.navigate('Home')}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go to home
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFoundScreen;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
title: {
|
||||||
|
fontSize: 36,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: 24,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@@ -13,11 +13,13 @@ import NewsFeed from '../Shared/NewsFeed';
|
|||||||
type SimpleStackParams = {
|
type SimpleStackParams = {
|
||||||
Article: { author: string };
|
Article: { author: string };
|
||||||
NewsFeed: undefined;
|
NewsFeed: undefined;
|
||||||
Album: undefined;
|
Albums: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
const ArticleScreen = ({
|
const ArticleScreen = ({
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
@@ -43,7 +45,10 @@ const ArticleScreen = ({
|
|||||||
Pop screen
|
Pop screen
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
<Article
|
||||||
|
author={{ name: route.params.author }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -58,7 +63,7 @@ const NewsFeedScreen = ({
|
|||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.navigate('Album')}
|
onPress={() => navigation.navigate('Albums')}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Navigate to album
|
Navigate to album
|
||||||
@@ -71,7 +76,7 @@ const NewsFeedScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<NewsFeed scrollEnabled={false} />
|
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -99,7 +104,7 @@ const AlbumsScreen = ({
|
|||||||
Pop by 2
|
Pop by 2
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums scrollEnabled={false} />
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -131,9 +136,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
options={{ title: 'Feed' }}
|
options={{ title: 'Feed' }}
|
||||||
/>
|
/>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Album"
|
name="Albums"
|
||||||
component={AlbumsScreen}
|
component={AlbumsScreen}
|
||||||
options={{ title: 'Album' }}
|
options={{ title: 'Albums' }}
|
||||||
/>
|
/>
|
||||||
</SimpleStack.Navigator>
|
</SimpleStack.Navigator>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +1,36 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet, ScrollView, Alert, Platform } from 'react-native';
|
import {
|
||||||
|
Animated,
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
Alert,
|
||||||
|
Platform,
|
||||||
|
} from 'react-native';
|
||||||
import { Button, Appbar } from 'react-native-paper';
|
import { Button, Appbar } from 'react-native-paper';
|
||||||
import { BlurView } from 'expo-blur';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackNavigationProp,
|
StackNavigationProp,
|
||||||
HeaderBackground,
|
HeaderBackground,
|
||||||
useHeaderHeight,
|
useHeaderHeight,
|
||||||
|
Header,
|
||||||
|
StackHeaderProps,
|
||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
|
import BlurView from '../Shared/BlurView';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
|
|
||||||
type SimpleStackParams = {
|
type SimpleStackParams = {
|
||||||
Article: { author: string };
|
Article: { author: string };
|
||||||
Album: undefined;
|
Albums: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
const ArticleScreen = ({
|
const ArticleScreen = ({
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
@@ -32,7 +43,7 @@ const ArticleScreen = ({
|
|||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.push('Album')}
|
onPress={() => navigation.push('Albums')}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Push album
|
Push album
|
||||||
@@ -45,7 +56,10 @@ const ArticleScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
<Article
|
||||||
|
author={{ name: route.params.author }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -75,7 +89,7 @@ const AlbumsScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums scrollEnabled={false} />
|
<Albums scrollEnabled={scrollEnabled} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -86,6 +100,25 @@ type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
|||||||
navigation: StackNavigationProp<ParamListBase>;
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function CustomHeader(props: StackHeaderProps) {
|
||||||
|
const { current, next } = props.scene.progress;
|
||||||
|
|
||||||
|
const progress = Animated.add(current, next || 0);
|
||||||
|
const opacity = progress.interpolate({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [0, 1, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header {...props} />
|
||||||
|
<Animated.Text style={[styles.banner, { opacity }]}>
|
||||||
|
Why hello there, pardner!
|
||||||
|
</Animated.Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
@@ -98,6 +131,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
component={ArticleScreen}
|
component={ArticleScreen}
|
||||||
options={({ route }) => ({
|
options={({ route }) => ({
|
||||||
title: `Article by ${route.params?.author}`,
|
title: `Article by ${route.params?.author}`,
|
||||||
|
header: CustomHeader,
|
||||||
headerTintColor: '#fff',
|
headerTintColor: '#fff',
|
||||||
headerStyle: { backgroundColor: '#ff005d' },
|
headerStyle: { backgroundColor: '#ff005d' },
|
||||||
headerBackTitleVisible: false,
|
headerBackTitleVisible: false,
|
||||||
@@ -126,10 +160,10 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
initialParams={{ author: 'Gandalf' }}
|
initialParams={{ author: 'Gandalf' }}
|
||||||
/>
|
/>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Album"
|
name="Albums"
|
||||||
component={AlbumsScreen}
|
component={AlbumsScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Album',
|
title: 'Albums',
|
||||||
headerBackTitle: 'Back',
|
headerBackTitle: 'Back',
|
||||||
headerTransparent: true,
|
headerTransparent: true,
|
||||||
headerBackground: () => (
|
headerBackground: () => (
|
||||||
@@ -155,4 +189,10 @@ const styles = StyleSheet.create({
|
|||||||
button: {
|
button: {
|
||||||
margin: 8,
|
margin: 8,
|
||||||
},
|
},
|
||||||
|
banner: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'tomato',
|
||||||
|
backgroundColor: 'papayawhip',
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||||
import { Button, Paragraph } from 'react-native-paper';
|
import { Button, Paragraph } from 'react-native-paper';
|
||||||
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
|
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +15,8 @@ type SimpleStackParams = {
|
|||||||
|
|
||||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||||
|
|
||||||
const ArticleScreen = ({
|
const ArticleScreen = ({
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
@@ -40,7 +42,10 @@ const ArticleScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
<Article
|
||||||
|
author={{ name: route.params.author }}
|
||||||
|
scrollEnabled={scrollEnabled}
|
||||||
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ScrollViewProps,
|
ScrollViewProps,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Platform,
|
Platform,
|
||||||
|
ScaledSize,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useScrollToTop } from '@react-navigation/native';
|
import { useScrollToTop } from '@react-navigation/native';
|
||||||
|
|
||||||
@@ -40,15 +41,38 @@ const COVERS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function Albums(props: Partial<ScrollViewProps>) {
|
export default function Albums(props: Partial<ScrollViewProps>) {
|
||||||
|
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);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const ref = React.useRef<ScrollView>(null);
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
useScrollToTop(ref);
|
useScrollToTop(ref);
|
||||||
|
|
||||||
|
const itemSize = dimensions.width / Math.floor(dimensions.width / 150);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
|
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
|
||||||
{COVERS.map((source, i) => (
|
{COVERS.map((source, i) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
<View
|
||||||
<View key={i} style={styles.item}>
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
styles.item,
|
||||||
|
Platform.OS !== 'web' && {
|
||||||
|
height: itemSize,
|
||||||
|
width: itemSize,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Image source={source} style={styles.photo} />
|
<Image source={source} style={styles.photo} />
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
@@ -76,10 +100,6 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
item: {
|
|
||||||
height: Dimensions.get('window').width / 2,
|
|
||||||
width: '50%',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
photo: {
|
photo: {
|
||||||
|
|||||||
3
example/src/Shared/BlurView.native.tsx
Normal file
3
example/src/Shared/BlurView.native.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { BlurView } from 'expo-blur';
|
||||||
|
|
||||||
|
export default BlurView;
|
||||||
12
example/src/Shared/BlurView.tsx
Normal file
12
example/src/Shared/BlurView.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View, ViewProps } from 'react-native';
|
||||||
|
|
||||||
|
type Props = ViewProps & {
|
||||||
|
tint: 'light' | 'dark';
|
||||||
|
intensity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export default function BlurView({ tint, intensity, ...rest }: Props) {
|
||||||
|
return <View {...rest} />;
|
||||||
|
}
|
||||||
@@ -68,10 +68,7 @@ export default function Chat(props: Partial<ScrollViewProps>) {
|
|||||||
styles.input,
|
styles.input,
|
||||||
{ backgroundColor: colors.card, color: colors.text },
|
{ backgroundColor: colors.card, color: colors.text },
|
||||||
]}
|
]}
|
||||||
placeholderTextColor={Color(colors.text)
|
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||||
.alpha(0.5)
|
|
||||||
.rgb()
|
|
||||||
.string()}
|
|
||||||
placeholder="Write a message"
|
placeholder="Write a message"
|
||||||
underlineColorAndroid="transparent"
|
underlineColorAndroid="transparent"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -51,10 +51,7 @@ export default function NewsFeed(props: Props) {
|
|||||||
<Card style={styles.card}>
|
<Card style={styles.card}>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="What's on your mind?"
|
placeholder="What's on your mind?"
|
||||||
placeholderTextColor={Color(colors.text)
|
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||||
.alpha(0.5)
|
|
||||||
.rgb()
|
|
||||||
.string()}
|
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
ScrollView,
|
ScrollView,
|
||||||
AsyncStorage,
|
|
||||||
YellowBox,
|
YellowBox,
|
||||||
Platform,
|
Platform,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
I18nManager,
|
I18nManager,
|
||||||
|
Dimensions,
|
||||||
|
ScaledSize,
|
||||||
|
Linking,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { enableScreens } from 'react-native-screens';
|
import { enableScreens } from 'react-native-screens';
|
||||||
import RNRestart from 'react-native-restart';
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { Updates } from 'expo';
|
|
||||||
import { Asset } from 'expo-asset';
|
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
|
||||||
import {
|
import {
|
||||||
Provider as PaperProvider,
|
Provider as PaperProvider,
|
||||||
DefaultTheme as PaperLightTheme,
|
DefaultTheme as PaperLightTheme,
|
||||||
@@ -20,14 +19,14 @@ import {
|
|||||||
Appbar,
|
Appbar,
|
||||||
List,
|
List,
|
||||||
Divider,
|
Divider,
|
||||||
|
Text,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
InitialState,
|
InitialState,
|
||||||
useLinking,
|
|
||||||
NavigationContainerRef,
|
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
|
PathConfig,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
createDrawerNavigator,
|
createDrawerNavigator,
|
||||||
@@ -35,12 +34,14 @@ import {
|
|||||||
} from '@react-navigation/drawer';
|
} from '@react-navigation/drawer';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
Assets as StackAssets,
|
|
||||||
StackNavigationProp,
|
StackNavigationProp,
|
||||||
HeaderStyleInterpolators,
|
HeaderStyleInterpolators,
|
||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
|
|
||||||
|
import { restartApp } from './Restart';
|
||||||
|
import AsyncStorage from './AsyncStorage';
|
||||||
import LinkingPrefixes from './LinkingPrefixes';
|
import LinkingPrefixes from './LinkingPrefixes';
|
||||||
|
import SettingsItem from './Shared/SettingsItem';
|
||||||
import SimpleStack from './Screens/SimpleStack';
|
import SimpleStack from './Screens/SimpleStack';
|
||||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||||
import StackTransparent from './Screens/StackTransparent';
|
import StackTransparent from './Screens/StackTransparent';
|
||||||
@@ -48,15 +49,20 @@ import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
|||||||
import BottomTabs from './Screens/BottomTabs';
|
import BottomTabs from './Screens/BottomTabs';
|
||||||
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
|
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
|
||||||
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
||||||
|
import NotFound from './Screens/NotFound';
|
||||||
import DynamicTabs from './Screens/DynamicTabs';
|
import DynamicTabs from './Screens/DynamicTabs';
|
||||||
import AuthFlow from './Screens/AuthFlow';
|
import AuthFlow from './Screens/AuthFlow';
|
||||||
import CompatAPI from './Screens/CompatAPI';
|
import CompatAPI from './Screens/CompatAPI';
|
||||||
import SettingsItem from './Shared/SettingsItem';
|
import MasterDetail from './Screens/MasterDetail';
|
||||||
|
import LinkComponent from './Screens/LinkComponent';
|
||||||
|
|
||||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||||
|
|
||||||
enableScreens();
|
enableScreens();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
global.REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED = true;
|
||||||
|
|
||||||
type RootDrawerParamList = {
|
type RootDrawerParamList = {
|
||||||
Root: undefined;
|
Root: undefined;
|
||||||
Another: undefined;
|
Another: undefined;
|
||||||
@@ -64,6 +70,7 @@ type RootDrawerParamList = {
|
|||||||
|
|
||||||
type RootStackParamList = {
|
type RootStackParamList = {
|
||||||
Home: undefined;
|
Home: undefined;
|
||||||
|
NotFound: undefined;
|
||||||
} & {
|
} & {
|
||||||
[P in keyof typeof SCREENS]: undefined;
|
[P in keyof typeof SCREENS]: undefined;
|
||||||
};
|
};
|
||||||
@@ -95,6 +102,10 @@ const SCREENS = {
|
|||||||
title: 'Dynamic Tabs',
|
title: 'Dynamic Tabs',
|
||||||
component: DynamicTabs,
|
component: DynamicTabs,
|
||||||
},
|
},
|
||||||
|
MasterDetail: {
|
||||||
|
title: 'Master Detail',
|
||||||
|
component: MasterDetail,
|
||||||
|
},
|
||||||
AuthFlow: {
|
AuthFlow: {
|
||||||
title: 'Auth Flow',
|
title: 'Auth Flow',
|
||||||
component: AuthFlow,
|
component: AuthFlow,
|
||||||
@@ -103,6 +114,10 @@ const SCREENS = {
|
|||||||
title: 'Compat Layer',
|
title: 'Compat Layer',
|
||||||
component: CompatAPI,
|
component: CompatAPI,
|
||||||
},
|
},
|
||||||
|
LinkComponent: {
|
||||||
|
title: '<Link />',
|
||||||
|
component: LinkComponent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Drawer = createDrawerNavigator<RootDrawerParamList>();
|
const Drawer = createDrawerNavigator<RootDrawerParamList>();
|
||||||
@@ -111,42 +126,10 @@ const Stack = createStackNavigator<RootStackParamList>();
|
|||||||
const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE';
|
const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE';
|
||||||
const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
|
const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
|
||||||
|
|
||||||
Asset.loadAsync(StackAssets);
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const containerRef = React.useRef<NavigationContainerRef>();
|
|
||||||
|
|
||||||
// To test deep linking on, run the following in the Terminal:
|
|
||||||
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
|
||||||
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
|
|
||||||
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
|
|
||||||
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
|
|
||||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
|
||||||
const { getInitialState } = useLinking(containerRef, {
|
|
||||||
prefixes: LinkingPrefixes,
|
|
||||||
config: {
|
|
||||||
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)
|
|
||||||
acc[name] = name
|
|
||||||
.replace(/([A-Z]+)/g, '-$1')
|
|
||||||
.replace(/^-/, '')
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ Home: '' }
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [theme, setTheme] = React.useState(DefaultTheme);
|
const [theme, setTheme] = React.useState(DefaultTheme);
|
||||||
|
|
||||||
const [isReady, setIsReady] = React.useState(false);
|
const [isReady, setIsReady] = React.useState(Platform.OS === 'web');
|
||||||
const [initialState, setInitialState] = React.useState<
|
const [initialState, setInitialState] = React.useState<
|
||||||
InitialState | undefined
|
InitialState | undefined
|
||||||
>();
|
>();
|
||||||
@@ -154,17 +137,18 @@ export default function App() {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const restoreState = async () => {
|
const restoreState = async () => {
|
||||||
try {
|
try {
|
||||||
let state = await getInitialState();
|
const initialUrl = await Linking.getInitialURL();
|
||||||
|
|
||||||
if (Platform.OS !== 'web' && state === undefined) {
|
if (Platform.OS !== 'web' || initialUrl === null) {
|
||||||
const savedState = await AsyncStorage.getItem(
|
const savedState = await AsyncStorage.getItem(
|
||||||
NAVIGATION_PERSISTENCE_KEY
|
NAVIGATION_PERSISTENCE_KEY
|
||||||
);
|
);
|
||||||
state = savedState ? JSON.parse(savedState) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state !== undefined) {
|
const state = savedState ? JSON.parse(savedState) : undefined;
|
||||||
setInitialState(state);
|
|
||||||
|
if (state !== undefined) {
|
||||||
|
setInitialState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
@@ -180,7 +164,7 @@ export default function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
restoreState();
|
restoreState();
|
||||||
}, [getInitialState]);
|
}, []);
|
||||||
|
|
||||||
const paperTheme = React.useMemo(() => {
|
const paperTheme = React.useMemo(() => {
|
||||||
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
|
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
|
||||||
@@ -196,27 +180,94 @@ export default function App() {
|
|||||||
};
|
};
|
||||||
}, [theme.colors, theme.dark]);
|
}, [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) {
|
if (!isReady) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLargeScreen = dimensions.width >= 1024;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaperProvider theme={paperTheme}>
|
<PaperProvider theme={paperTheme}>
|
||||||
{Platform.OS === 'ios' && (
|
{Platform.OS === 'ios' && (
|
||||||
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
|
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
|
||||||
)}
|
)}
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
ref={containerRef}
|
|
||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
onStateChange={state =>
|
onStateChange={(state) =>
|
||||||
AsyncStorage.setItem(
|
AsyncStorage.setItem(
|
||||||
NAVIGATION_PERSISTENCE_KEY,
|
NAVIGATION_PERSISTENCE_KEY,
|
||||||
JSON.stringify(state)
|
JSON.stringify(state)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
linking={{
|
||||||
|
// To test deep linking on, run the following in the Terminal:
|
||||||
|
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
||||||
|
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
|
||||||
|
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
|
||||||
|
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
|
||||||
|
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||||
|
prefixes: LinkingPrefixes,
|
||||||
|
config: {
|
||||||
|
Root: {
|
||||||
|
path: '',
|
||||||
|
initialRouteName: 'Home',
|
||||||
|
screens: Object.keys(SCREENS).reduce<PathConfig>(
|
||||||
|
(acc, name) => {
|
||||||
|
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||||
|
const path = name
|
||||||
|
.replace(/([A-Z]+)/g, '-$1')
|
||||||
|
.replace(/^-/, '')
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
acc[name] = {
|
||||||
|
path,
|
||||||
|
screens: {
|
||||||
|
Article: {
|
||||||
|
path: 'article/:author?',
|
||||||
|
parse: {
|
||||||
|
author: (author) =>
|
||||||
|
author.charAt(0).toUpperCase() +
|
||||||
|
author.slice(1).replace(/-/g, ' '),
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.toLowerCase().replace(/\s/g, '-'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Albums: 'music',
|
||||||
|
Chat: 'chat',
|
||||||
|
Contacts: 'people',
|
||||||
|
NewsFeed: 'feed',
|
||||||
|
Dialog: 'dialog',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Home: '',
|
||||||
|
NotFound: '*',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
fallback={<Text>Loading…</Text>}
|
||||||
>
|
>
|
||||||
<Drawer.Navigator>
|
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Root"
|
name="Root"
|
||||||
options={{
|
options={{
|
||||||
@@ -240,13 +291,15 @@ export default function App() {
|
|||||||
name="Home"
|
name="Home"
|
||||||
options={{
|
options={{
|
||||||
title: 'Examples',
|
title: 'Examples',
|
||||||
headerLeft: () => (
|
headerLeft: isLargeScreen
|
||||||
<Appbar.Action
|
? undefined
|
||||||
color={theme.colors.text}
|
: () => (
|
||||||
icon="menu"
|
<Appbar.Action
|
||||||
onPress={() => navigation.toggleDrawer()}
|
color={theme.colors.text}
|
||||||
/>
|
icon="menu"
|
||||||
),
|
onPress={() => navigation.toggleDrawer()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({
|
{({
|
||||||
@@ -262,12 +315,7 @@ export default function App() {
|
|||||||
value={I18nManager.isRTL}
|
value={I18nManager.isRTL}
|
||||||
onValueChange={() => {
|
onValueChange={() => {
|
||||||
I18nManager.forceRTL(!I18nManager.isRTL);
|
I18nManager.forceRTL(!I18nManager.isRTL);
|
||||||
// @ts-ignore
|
restartApp();
|
||||||
if (global.Expo) {
|
|
||||||
Updates.reloadFromCache();
|
|
||||||
} else {
|
|
||||||
RNRestart.Restart();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -280,14 +328,15 @@ export default function App() {
|
|||||||
theme.dark ? 'light' : 'dark'
|
theme.dark ? 'light' : 'dark'
|
||||||
);
|
);
|
||||||
|
|
||||||
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
|
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||||
name => (
|
(name) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={name}
|
key={name}
|
||||||
|
testID={name}
|
||||||
title={SCREENS[name].title}
|
title={SCREENS[name].title}
|
||||||
onPress={() => navigation.navigate(name)}
|
onPress={() => navigation.navigate(name)}
|
||||||
/>
|
/>
|
||||||
@@ -296,8 +345,13 @@ export default function App() {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
|
<Stack.Screen
|
||||||
|
name="NotFound"
|
||||||
|
component={NotFound}
|
||||||
|
options={{ title: 'Oops!' }}
|
||||||
|
/>
|
||||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||||
name => (
|
(name) => (
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key={name}
|
key={name}
|
||||||
name={name}
|
name={name}
|
||||||
|
|||||||
16
example/types/react-native-web.d.ts
vendored
Normal file
16
example/types/react-native-web.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
declare module 'react-native-web' {
|
||||||
|
export const AppRegistry: {
|
||||||
|
registerComponent(
|
||||||
|
name: string,
|
||||||
|
callback: () => React.ComponentType<any>
|
||||||
|
): void;
|
||||||
|
|
||||||
|
getApplication(
|
||||||
|
name: string,
|
||||||
|
options?: { initialProps: object }
|
||||||
|
): {
|
||||||
|
element: React.ReactElement;
|
||||||
|
getStyleElement(): React.ReactElement;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
1
example/web/_redirects
Normal file
1
example/web/_redirects
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/* /index.html 200
|
||||||
@@ -7,7 +7,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
|||||||
const node_modules = path.resolve(__dirname, '..', 'node_modules');
|
const node_modules = path.resolve(__dirname, '..', 'node_modules');
|
||||||
const packages = path.resolve(__dirname, '..', 'packages');
|
const packages = path.resolve(__dirname, '..', 'packages');
|
||||||
|
|
||||||
module.exports = async function(env, argv) {
|
module.exports = async function (env, argv) {
|
||||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||||
|
|
||||||
config.context = path.resolve(__dirname, '..');
|
config.context = path.resolve(__dirname, '..');
|
||||||
@@ -20,23 +20,25 @@ module.exports = async function(env, argv) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
config.resolve.plugins = config.resolve.plugins.filter(
|
config.resolve.plugins = config.resolve.plugins.filter(
|
||||||
p => !(p instanceof ModuleScopePlugin)
|
(p) => !(p instanceof ModuleScopePlugin)
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.assign(config.resolve.alias, {
|
Object.assign(config.resolve.alias, {
|
||||||
react: path.resolve(node_modules, 'react'),
|
'react': path.resolve(node_modules, 'react'),
|
||||||
'react-native': path.resolve(node_modules, 'react-native-web'),
|
'react-native': path.resolve(node_modules, 'react-native-web'),
|
||||||
'react-native-web': path.resolve(node_modules, 'react-native-web'),
|
'react-native-web': path.resolve(node_modules, 'react-native-web'),
|
||||||
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.readdirSync(packages).forEach(name => {
|
fs.readdirSync(packages)
|
||||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
.filter((name) => !name.startsWith('.'))
|
||||||
packages,
|
.forEach((name) => {
|
||||||
name,
|
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||||
'src'
|
packages,
|
||||||
);
|
name,
|
||||||
});
|
require(`../packages/${name}/package.json`).source
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const error = console.error;
|
const error = console.error;
|
||||||
|
|
||||||
console.error = (...args) =>
|
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(
|
/(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]
|
args[0]
|
||||||
)
|
)
|
||||||
|
|||||||
5
netlify.toml
Normal file
5
netlify.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
[build]
|
||||||
|
base = "/"
|
||||||
|
publish = "example/web-build"
|
||||||
|
command = "yarn example expo build:web"
|
||||||
32
package.json
32
package.json
@@ -13,12 +13,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/satya164/react-navigation.git"
|
"url": "git+https://github.com/react-navigation/react-navigation.git"
|
||||||
},
|
},
|
||||||
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
|
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||||
"typescript": "tsc --noEmit",
|
"typescript": "tsc --noEmit --composite false",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prerelease": "lerna run clean",
|
"prerelease": "lerna run clean",
|
||||||
"release": "lerna publish",
|
"release": "lerna publish",
|
||||||
@@ -26,25 +26,25 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.8.4",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"@babel/preset-flow": "^7.8.3",
|
"@babel/preset-flow": "^7.9.0",
|
||||||
"@babel/preset-react": "^7.8.3",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"@babel/preset-typescript": "^7.8.3",
|
"@babel/preset-typescript": "^7.9.0",
|
||||||
"@babel/runtime": "^7.8.4",
|
"@babel/runtime": "^7.9.6",
|
||||||
"@commitlint/config-conventional": "^8.3.4",
|
"@commitlint/config-conventional": "^8.3.4",
|
||||||
"@types/jest": "^25.1.2",
|
"@types/jest": "^25.2.1",
|
||||||
|
"babel-jest": "^26.0.1",
|
||||||
"codecov": "^3.6.5",
|
"codecov": "^3.6.5",
|
||||||
"commitlint": "^8.3.5",
|
"commitlint": "^8.3.5",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.5",
|
||||||
"detox": "^15.1.4",
|
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-satya164": "^3.1.5",
|
"eslint-config-satya164": "^3.1.7",
|
||||||
"husky": "^4.2.1",
|
"husky": "^4.2.5",
|
||||||
"jest": "^25.1.0",
|
"jest": "^26.0.1",
|
||||||
"lerna": "^3.20.2",
|
"lerna": "^3.20.2",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^2.0.5",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
|
|||||||
@@ -3,6 +3,233 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.5.1...@react-navigation/bottom-tabs@5.5.2) (2020-06-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.5.0...@react-navigation/bottom-tabs@5.5.1) (2020-05-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix type of style for various options ([9d822b9](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/9d822b95a6df797e2e63e481573e64ea7d0f9386))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.7...@react-navigation/bottom-tabs@5.5.0) (2020-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* animate changes to tabBarVisible in BottomTabBar ([#8286](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8286)) ([c1e46f8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/c1e46f8e331e0054995aa476455af204d02d4170))
|
||||||
|
* update react-native-safe-area-context to 1.0.0 ([#8182](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8182)) ([d62fbfe](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/d62fbfe255140f16b182e8b54b276a7c96f2aec6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.6...@react-navigation/bottom-tabs@5.4.7) (2020-05-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.5...@react-navigation/bottom-tabs@5.4.6) (2020-05-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.4...@react-navigation/bottom-tabs@5.4.5) (2020-05-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.3...@react-navigation/bottom-tabs@5.4.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.2...@react-navigation/bottom-tabs@5.4.3) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.0...@react-navigation/bottom-tabs@5.4.1) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.4...@react-navigation/bottom-tabs@5.4.0) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7971)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.3...@react-navigation/bottom-tabs@5.3.4) (2020-05-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.2...@react-navigation/bottom-tabs@5.3.3) (2020-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.1...@react-navigation/bottom-tabs@5.3.2) (2020-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.0...@react-navigation/bottom-tabs@5.3.1) (2020-04-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.8...@react-navigation/bottom-tabs@5.3.0) (2020-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||||
|
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.7...@react-navigation/bottom-tabs@5.2.8) (2020-04-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.5...@react-navigation/bottom-tabs@5.2.6) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.4...@react-navigation/bottom-tabs@5.2.5) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.3...@react-navigation/bottom-tabs@5.2.4) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/bottom-tabs",
|
"name": "@react-navigation/bottom-tabs",
|
||||||
"description": "Bottom tab navigator following iOS design guidelines",
|
"description": "Bottom tab navigator following iOS design guidelines",
|
||||||
"version": "5.1.1",
|
"version": "5.5.2",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
|
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
"react-native": "src/index.tsx",
|
"react-native": "src/index.tsx",
|
||||||
|
"source": "src/index.tsx",
|
||||||
"module": "lib/module/index.js",
|
"module": "lib/module/index.js",
|
||||||
"types": "lib/typescript/src/index.d.ts",
|
"types": "lib/typescript/src/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"lib"
|
"lib",
|
||||||
|
"!**/__tests__"
|
||||||
],
|
],
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -34,22 +36,24 @@
|
|||||||
"react-native-iphone-x-helper": "^1.2.1"
|
"react-native-iphone-x-helper": "^1.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.9.3",
|
"@react-native-community/bob": "^0.14.3",
|
||||||
"@react-navigation/native": "^5.0.9",
|
"@react-navigation/native": "^5.5.1",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.19",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.60.30",
|
"@types/react-native": "^0.62.7",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-safe-area-context": "^0.7.2",
|
"react-native-safe-area-context": "^1.0.0",
|
||||||
"typescript": "^3.7.5"
|
"react-native-screens": "^2.7.0",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*",
|
"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": {
|
"@react-native-community/bob": {
|
||||||
"source": "src",
|
"source": "src",
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ export { default as BottomTabBar } from './views/BottomTabBar';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
BottomTabNavigationOptions,
|
BottomTabNavigationOptions,
|
||||||
BottomTabNavigationProp,
|
BottomTabNavigationProp,
|
||||||
|
BottomTabScreenProps,
|
||||||
BottomTabBarProps,
|
BottomTabBarProps,
|
||||||
BottomTabBarOptions,
|
BottomTabBarOptions,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
|
Animated,
|
||||||
TouchableWithoutFeedbackProps,
|
TouchableWithoutFeedbackProps,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
|
GestureResponderEvent,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import {
|
import {
|
||||||
NavigationHelpers,
|
NavigationHelpers,
|
||||||
@@ -11,6 +13,8 @@ import {
|
|||||||
ParamListBase,
|
ParamListBase,
|
||||||
Descriptor,
|
Descriptor,
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
|
TabActionHelpers,
|
||||||
|
RouteProp,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
|
|
||||||
export type BottomTabNavigationEventMap = {
|
export type BottomTabNavigationEventMap = {
|
||||||
@@ -40,18 +44,15 @@ export type BottomTabNavigationProp<
|
|||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
BottomTabNavigationOptions,
|
BottomTabNavigationOptions,
|
||||||
BottomTabNavigationEventMap
|
BottomTabNavigationEventMap
|
||||||
> & {
|
> &
|
||||||
/**
|
TabActionHelpers<ParamList>;
|
||||||
* Jump to an existing tab.
|
|
||||||
*
|
export type BottomTabScreenProps<
|
||||||
* @param name Name of the route for the tab.
|
ParamList extends ParamListBase,
|
||||||
* @param [params] Params object for the route.
|
RouteName extends keyof ParamList = string
|
||||||
*/
|
> = {
|
||||||
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
|
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
||||||
...args: ParamList[RouteName] extends undefined | any
|
route: RouteProp<ParamList, RouteName>;
|
||||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
|
||||||
: [RouteName, ParamList[RouteName]]
|
|
||||||
): void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BottomTabNavigationOptions = {
|
export type BottomTabNavigationOptions = {
|
||||||
@@ -148,7 +149,7 @@ export type BottomTabBarOptions = {
|
|||||||
*/
|
*/
|
||||||
inactiveTintColor?: string;
|
inactiveTintColor?: string;
|
||||||
/**
|
/**
|
||||||
* Background olor for the active tab.
|
* Background color for the active tab.
|
||||||
*/
|
*/
|
||||||
activeBackgroundColor?: string;
|
activeBackgroundColor?: string;
|
||||||
/**
|
/**
|
||||||
@@ -184,10 +185,20 @@ export type BottomTabBarOptions = {
|
|||||||
* Whether the label position should adapt to the orientation.
|
* Whether the label position should adapt to the orientation.
|
||||||
*/
|
*/
|
||||||
adaptive?: boolean;
|
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.
|
* Style object for the tab bar container.
|
||||||
*/
|
*/
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BottomTabBarProps = BottomTabBarOptions & {
|
export type BottomTabBarProps = BottomTabBarOptions & {
|
||||||
@@ -196,6 +207,13 @@ export type BottomTabBarProps = BottomTabBarOptions & {
|
|||||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
|
export type BottomTabBarButtonProps = Omit<
|
||||||
|
TouchableWithoutFeedbackProps,
|
||||||
|
'onPress'
|
||||||
|
> & {
|
||||||
|
to?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
onPress?: (
|
||||||
|
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ import {
|
|||||||
NavigationRouteContext,
|
NavigationRouteContext,
|
||||||
CommonActions,
|
CommonActions,
|
||||||
useTheme,
|
useTheme,
|
||||||
|
useLinkBuilder,
|
||||||
} from '@react-navigation/native';
|
} 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 BottomTabItem from './BottomTabItem';
|
||||||
import { BottomTabBarProps } from '../types';
|
import { BottomTabBarProps } from '../types';
|
||||||
@@ -43,54 +44,69 @@ export default function BottomTabBar({
|
|||||||
keyboardHidesTabBar = false,
|
keyboardHidesTabBar = false,
|
||||||
labelPosition,
|
labelPosition,
|
||||||
labelStyle,
|
labelStyle,
|
||||||
|
safeAreaInsets,
|
||||||
showIcon,
|
showIcon,
|
||||||
showLabel,
|
showLabel,
|
||||||
style,
|
style,
|
||||||
tabStyle,
|
tabStyle,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
const buildLink = useLinkBuilder();
|
||||||
|
|
||||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
const focusedRoute = state.routes[state.index];
|
||||||
const [layout, setLayout] = React.useState({
|
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||||
height: 0,
|
const focusedOptions = focusedDescriptor.options;
|
||||||
width: dimensions.width,
|
|
||||||
});
|
|
||||||
const [keyboardShown, setKeyboardShown] = React.useState(false);
|
|
||||||
|
|
||||||
const [visible] = React.useState(() => new Animated.Value(1));
|
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
|
||||||
|
|
||||||
const { routes } = state;
|
const shouldShowTabBar =
|
||||||
|
focusedOptions.tabBarVisible !== false &&
|
||||||
|
!(keyboardHidesTabBar && isKeyboardShown);
|
||||||
|
|
||||||
|
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
|
||||||
|
|
||||||
|
const [visible] = React.useState(
|
||||||
|
() => new Animated.Value(shouldShowTabBar ? 1 : 0)
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (keyboardShown) {
|
if (shouldShowTabBar) {
|
||||||
Animated.timing(visible, {
|
|
||||||
toValue: 0,
|
|
||||||
duration: 200,
|
|
||||||
useNativeDriver,
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}, [keyboardShown, visible]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const handleOrientationChange = ({ window }: { window: ScaledSize }) => {
|
|
||||||
setDimensions(window);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyboardShow = () => setKeyboardShown(true);
|
|
||||||
|
|
||||||
const handleKeyboardHide = () =>
|
|
||||||
Animated.timing(visible, {
|
Animated.timing(visible, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 250,
|
duration: 250,
|
||||||
useNativeDriver,
|
useNativeDriver,
|
||||||
}).start(({ finished }) => {
|
}).start(({ finished }) => {
|
||||||
if (finished) {
|
if (finished) {
|
||||||
setKeyboardShown(false);
|
setIsTabBarHidden(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setIsTabBarHidden(true);
|
||||||
|
|
||||||
|
Animated.timing(visible, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 200,
|
||||||
|
useNativeDriver,
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}, [shouldShowTabBar, visible]);
|
||||||
|
|
||||||
|
const [dimensions, setDimensions] = React.useState(() => {
|
||||||
|
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||||
|
|
||||||
|
return { height, width };
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleOrientationChange = ({ window }: { window: ScaledSize }) => {
|
||||||
|
setDimensions(window);
|
||||||
|
};
|
||||||
|
|
||||||
Dimensions.addEventListener('change', handleOrientationChange);
|
Dimensions.addEventListener('change', handleOrientationChange);
|
||||||
|
|
||||||
|
const handleKeyboardShow = () => setIsKeyboardShown(true);
|
||||||
|
const handleKeyboardHide = () => setIsKeyboardShown(false);
|
||||||
|
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
|
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
|
||||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
|
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
|
||||||
@@ -110,12 +126,17 @@ export default function BottomTabBar({
|
|||||||
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
|
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [visible]);
|
}, []);
|
||||||
|
|
||||||
|
const [layout, setLayout] = React.useState({
|
||||||
|
height: 0,
|
||||||
|
width: dimensions.width,
|
||||||
|
});
|
||||||
|
|
||||||
const handleLayout = (e: LayoutChangeEvent) => {
|
const handleLayout = (e: LayoutChangeEvent) => {
|
||||||
const { height, width } = e.nativeEvent.layout;
|
const { height, width } = e.nativeEvent.layout;
|
||||||
|
|
||||||
setLayout(layout => {
|
setLayout((layout) => {
|
||||||
if (height === layout.height && width === layout.width) {
|
if (height === layout.height && width === layout.width) {
|
||||||
return layout;
|
return layout;
|
||||||
} else {
|
} else {
|
||||||
@@ -127,6 +148,7 @@ export default function BottomTabBar({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { routes } = state;
|
||||||
const shouldUseHorizontalLabels = () => {
|
const shouldUseHorizontalLabels = () => {
|
||||||
if (labelPosition) {
|
if (labelPosition) {
|
||||||
return labelPosition === 'beside-icon';
|
return labelPosition === 'beside-icon';
|
||||||
@@ -158,116 +180,120 @@ 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 (
|
return (
|
||||||
<SafeAreaConsumer>
|
<Animated.View
|
||||||
{insets => (
|
style={[
|
||||||
<Animated.View
|
styles.tabBar,
|
||||||
style={[
|
{
|
||||||
styles.tabBar,
|
backgroundColor: colors.card,
|
||||||
|
borderTopColor: colors.border,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: [
|
||||||
{
|
{
|
||||||
backgroundColor: colors.card,
|
translateY: visible.interpolate({
|
||||||
borderTopColor: colors.border,
|
inputRange: [0, 1],
|
||||||
|
outputRange: [layout.height + insets.bottom, 0],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
keyboardHidesTabBar
|
],
|
||||||
? {
|
// Absolutely position the tab bar so that the content is below it
|
||||||
// When the keyboard is shown, slide down the tab bar
|
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||||
transform: [
|
position: isTabBarHidden ? 'absolute' : null,
|
||||||
{
|
},
|
||||||
translateY: visible.interpolate({
|
{
|
||||||
inputRange: [0, 1],
|
height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
|
||||||
outputRange: [layout.height, 0],
|
paddingBottom: insets.bottom,
|
||||||
}),
|
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||||
},
|
},
|
||||||
],
|
style,
|
||||||
// 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
|
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||||
position: keyboardShown ? 'absolute' : null,
|
>
|
||||||
}
|
<View style={styles.content} onLayout={handleLayout}>
|
||||||
: null,
|
{routes.map((route, index) => {
|
||||||
{
|
const focused = index === state.index;
|
||||||
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
|
const { options } = descriptors[route.key];
|
||||||
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];
|
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
const event = navigation.emit({
|
const event = navigation.emit({
|
||||||
type: 'tabPress',
|
type: 'tabPress',
|
||||||
target: route.key,
|
target: route.key,
|
||||||
canPreventDefault: true,
|
canPreventDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!focused && !event.defaultPrevented) {
|
if (!focused && !event.defaultPrevented) {
|
||||||
navigation.dispatch({
|
navigation.dispatch({
|
||||||
...CommonActions.navigate(route.name),
|
...CommonActions.navigate(route.name),
|
||||||
target: state.key,
|
target: state.key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLongPress = () => {
|
const onLongPress = () => {
|
||||||
navigation.emit({
|
navigation.emit({
|
||||||
type: 'tabLongPress',
|
type: 'tabLongPress',
|
||||||
target: route.key,
|
target: route.key,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const label =
|
const label =
|
||||||
options.tabBarLabel !== undefined
|
options.tabBarLabel !== undefined
|
||||||
? options.tabBarLabel
|
? options.tabBarLabel
|
||||||
: options.title !== undefined
|
: options.title !== undefined
|
||||||
? options.title
|
? options.title
|
||||||
: route.name;
|
: route.name;
|
||||||
|
|
||||||
const accessibilityLabel =
|
const accessibilityLabel =
|
||||||
options.tabBarAccessibilityLabel !== undefined
|
options.tabBarAccessibilityLabel !== undefined
|
||||||
? options.tabBarAccessibilityLabel
|
? options.tabBarAccessibilityLabel
|
||||||
: typeof label === 'string'
|
: typeof label === 'string'
|
||||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContext.Provider
|
<NavigationContext.Provider
|
||||||
key={route.key}
|
key={route.key}
|
||||||
value={descriptors[route.key].navigation}
|
value={descriptors[route.key].navigation}
|
||||||
>
|
>
|
||||||
<NavigationRouteContext.Provider value={route}>
|
<NavigationRouteContext.Provider value={route}>
|
||||||
<BottomTabItem
|
<BottomTabItem
|
||||||
route={route}
|
route={route}
|
||||||
focused={focused}
|
focused={focused}
|
||||||
horizontal={shouldUseHorizontalLabels()}
|
horizontal={shouldUseHorizontalLabels()}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
testID={options.tabBarTestID}
|
to={buildLink(route.name, route.params)}
|
||||||
allowFontScaling={allowFontScaling}
|
testID={options.tabBarTestID}
|
||||||
activeTintColor={activeTintColor}
|
allowFontScaling={allowFontScaling}
|
||||||
inactiveTintColor={inactiveTintColor}
|
activeTintColor={activeTintColor}
|
||||||
activeBackgroundColor={activeBackgroundColor}
|
inactiveTintColor={inactiveTintColor}
|
||||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
activeBackgroundColor={activeBackgroundColor}
|
||||||
button={options.tabBarButton}
|
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||||
icon={options.tabBarIcon}
|
button={options.tabBarButton}
|
||||||
label={label}
|
icon={options.tabBarIcon}
|
||||||
showIcon={showIcon}
|
label={label}
|
||||||
showLabel={showLabel}
|
showIcon={showIcon}
|
||||||
labelStyle={labelStyle}
|
showLabel={showLabel}
|
||||||
style={tabStyle}
|
labelStyle={labelStyle}
|
||||||
/>
|
style={tabStyle}
|
||||||
</NavigationRouteContext.Provider>
|
/>
|
||||||
</NavigationContext.Provider>
|
</NavigationRouteContext.Provider>
|
||||||
);
|
</NavigationContext.Provider>
|
||||||
})}
|
);
|
||||||
</View>
|
})}
|
||||||
</Animated.View>
|
</View>
|
||||||
)}
|
</Animated.View>
|
||||||
</SafeAreaConsumer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
|
Text,
|
||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
Animated,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
Platform,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
|
GestureResponderEvent,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Route, useTheme } from '@react-navigation/native';
|
import { Link, Route, useTheme } from '@react-navigation/native';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
|
|
||||||
import TabBarIcon from './TabBarIcon';
|
import TabBarIcon from './TabBarIcon';
|
||||||
@@ -37,6 +39,10 @@ type Props = {
|
|||||||
size: number;
|
size: number;
|
||||||
color: string;
|
color: string;
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
|
/**
|
||||||
|
* URL to use for the link to the tab.
|
||||||
|
*/
|
||||||
|
to?: string;
|
||||||
/**
|
/**
|
||||||
* The button for the tab. Uses a `TouchableWithoutFeedback` by default.
|
* The button for the tab. Uses a `TouchableWithoutFeedback` by default.
|
||||||
*/
|
*/
|
||||||
@@ -50,13 +56,16 @@ type Props = {
|
|||||||
*/
|
*/
|
||||||
testID?: string;
|
testID?: string;
|
||||||
/**
|
/**
|
||||||
* Function to execute on press.
|
* Function to execute on press in React Native.
|
||||||
|
* On the web, this will use onClick.
|
||||||
*/
|
*/
|
||||||
onPress: () => void;
|
onPress: (
|
||||||
|
e: React.MouseEvent<HTMLElement, MouseEvent> | GestureResponderEvent
|
||||||
|
) => void;
|
||||||
/**
|
/**
|
||||||
* Function to execute on long press.
|
* Function to execute on long press.
|
||||||
*/
|
*/
|
||||||
onLongPress: () => void;
|
onLongPress: (e: GestureResponderEvent) => void;
|
||||||
/**
|
/**
|
||||||
* Whether the label should be aligned with the icon horizontally.
|
* Whether the label should be aligned with the icon horizontally.
|
||||||
*/
|
*/
|
||||||
@@ -104,11 +113,48 @@ export default function BottomTabBarItem({
|
|||||||
route,
|
route,
|
||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
button = ({ children, style, ...rest }: BottomTabBarButtonProps) => (
|
to,
|
||||||
<TouchableWithoutFeedback {...rest}>
|
button = ({
|
||||||
<View style={style}>{children}</View>
|
children,
|
||||||
</TouchableWithoutFeedback>
|
style,
|
||||||
),
|
onPress,
|
||||||
|
to,
|
||||||
|
accessibilityRole,
|
||||||
|
...rest
|
||||||
|
}: BottomTabBarButtonProps) => {
|
||||||
|
if (Platform.OS === 'web' && to) {
|
||||||
|
// React Native Web doesn't forward `onClick` if we use `TouchableWithoutFeedback`.
|
||||||
|
// We need to use `onClick` to be able to prevent default browser handling of links.
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
{...rest}
|
||||||
|
to={to}
|
||||||
|
style={[styles.button, style]}
|
||||||
|
onPress={(e: any) => {
|
||||||
|
if (
|
||||||
|
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
|
||||||
|
(e.button == null || e.button === 0) // ignore everything but left clicks
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
onPress?.(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
{...rest}
|
||||||
|
accessibilityRole={accessibilityRole}
|
||||||
|
onPress={onPress}
|
||||||
|
>
|
||||||
|
<View style={style}>{children}</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
accessibilityLabel,
|
accessibilityLabel,
|
||||||
testID,
|
testID,
|
||||||
onPress,
|
onPress,
|
||||||
@@ -133,9 +179,7 @@ export default function BottomTabBarItem({
|
|||||||
|
|
||||||
const inactiveTintColor =
|
const inactiveTintColor =
|
||||||
customInactiveTintColor === undefined
|
customInactiveTintColor === undefined
|
||||||
? Color(colors.text)
|
? Color(colors.text).mix(Color(colors.card), 0.5).hex()
|
||||||
.mix(Color(colors.card), 0.5)
|
|
||||||
.hex()
|
|
||||||
: customInactiveTintColor;
|
: customInactiveTintColor;
|
||||||
|
|
||||||
const renderLabel = ({ focused }: { focused: boolean }) => {
|
const renderLabel = ({ focused }: { focused: boolean }) => {
|
||||||
@@ -147,7 +191,7 @@ export default function BottomTabBarItem({
|
|||||||
|
|
||||||
if (typeof label === 'string') {
|
if (typeof label === 'string') {
|
||||||
return (
|
return (
|
||||||
<Animated.Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={[
|
style={[
|
||||||
styles.label,
|
styles.label,
|
||||||
@@ -158,14 +202,10 @@ export default function BottomTabBarItem({
|
|||||||
allowFontScaling={allowFontScaling}
|
allowFontScaling={allowFontScaling}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Animated.Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof label === 'string') {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
return label({ focused, color });
|
return label({ focused, color });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -198,6 +238,7 @@ export default function BottomTabBarItem({
|
|||||||
: inactiveBackgroundColor;
|
: inactiveBackgroundColor;
|
||||||
|
|
||||||
return button({
|
return button({
|
||||||
|
to,
|
||||||
onPress,
|
onPress,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
testID,
|
testID,
|
||||||
@@ -250,4 +291,7 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
},
|
},
|
||||||
|
button: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { TabNavigationState, useTheme } from '@react-navigation/native';
|
import {
|
||||||
|
NavigationHelpersContext,
|
||||||
|
TabNavigationState,
|
||||||
|
useTheme,
|
||||||
|
} from '@react-navigation/native';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { ScreenContainer } from 'react-native-screens';
|
import { ScreenContainer } from 'react-native-screens';
|
||||||
|
|
||||||
@@ -71,17 +75,8 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
tabBarOptions,
|
tabBarOptions,
|
||||||
state,
|
state,
|
||||||
navigation,
|
navigation,
|
||||||
|
descriptors,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { descriptors } = this.props;
|
|
||||||
const route = state.routes[state.index];
|
|
||||||
const descriptor = descriptors[route.key];
|
|
||||||
const options = descriptor.options;
|
|
||||||
|
|
||||||
if (options.tabBarVisible === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabBar({
|
return tabBar({
|
||||||
...tabBarOptions,
|
...tabBarOptions,
|
||||||
state: state,
|
state: state,
|
||||||
@@ -91,44 +86,46 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { state, descriptors, lazy } = this.props;
|
const { state, descriptors, navigation, lazy } = this.props;
|
||||||
const { routes } = state;
|
const { routes } = state;
|
||||||
const { loaded } = this.state;
|
const { loaded } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProviderCompat>
|
<NavigationHelpersContext.Provider value={navigation}>
|
||||||
<View style={styles.container}>
|
<SafeAreaProviderCompat>
|
||||||
<ScreenContainer style={styles.pages}>
|
<View style={styles.container}>
|
||||||
{routes.map((route, index) => {
|
<ScreenContainer style={styles.pages}>
|
||||||
const descriptor = descriptors[route.key];
|
{routes.map((route, index) => {
|
||||||
const { unmountOnBlur } = descriptor.options;
|
const descriptor = descriptors[route.key];
|
||||||
const isFocused = state.index === index;
|
const { unmountOnBlur } = descriptor.options;
|
||||||
|
const isFocused = state.index === index;
|
||||||
|
|
||||||
if (unmountOnBlur && !isFocused) {
|
if (unmountOnBlur && !isFocused) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||||
// Don't render a screen if we've never navigated to it
|
// Don't render a screen if we've never navigated to it
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourceSavingScene
|
<ResourceSavingScene
|
||||||
key={route.key}
|
key={route.key}
|
||||||
style={StyleSheet.absoluteFill}
|
style={StyleSheet.absoluteFill}
|
||||||
isVisible={isFocused}
|
isVisible={isFocused}
|
||||||
>
|
>
|
||||||
<SceneContent isFocused={isFocused}>
|
<SceneContent isFocused={isFocused}>
|
||||||
{descriptor.render()}
|
{descriptor.render()}
|
||||||
</SceneContent>
|
</SceneContent>
|
||||||
</ResourceSavingScene>
|
</ResourceSavingScene>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ScreenContainer>
|
</ScreenContainer>
|
||||||
{this.renderTabBar()}
|
{this.renderTabBar()}
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaProviderCompat>
|
</SafeAreaProviderCompat>
|
||||||
|
</NavigationHelpersContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { Screen, screensEnabled } from 'react-native-screens';
|
import { Screen, screensEnabled } from 'react-native-screens';
|
||||||
|
|
||||||
@@ -10,12 +9,14 @@ type Props = {
|
|||||||
style?: any;
|
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> {
|
export default class ResourceSavingScene extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
if (screensEnabled?.()) {
|
// react-native-screens is buggy on web
|
||||||
|
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||||
const { isVisible, ...rest } = this.props;
|
const { isVisible, ...rest } = this.props;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
|
style={[
|
||||||
|
styles.container,
|
||||||
|
Platform.OS === 'web'
|
||||||
|
? { display: isVisible ? 'flex' : 'none' }
|
||||||
|
: null,
|
||||||
|
style,
|
||||||
|
]}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
removeClippedSubviews={
|
removeClippedSubviews={
|
||||||
// On iOS, set removeClippedSubviews to true only when not focused
|
// On iOS, set removeClippedSubviews to true only when not focused
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Props = {
|
|||||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaConsumer>
|
<SafeAreaConsumer>
|
||||||
{insets => {
|
{(insets) => {
|
||||||
if (insets) {
|
if (insets) {
|
||||||
// If we already have insets, don't wrap the stack in another safe area provider
|
// 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
|
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||||
|
|||||||
@@ -3,6 +3,216 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.26](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.25...@react-navigation/compat@5.1.26) (2020-06-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.25](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.24...@react-navigation/compat@5.1.25) (2020-05-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.24](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.23...@react-navigation/compat@5.1.24) (2020-05-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.23](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.22...@react-navigation/compat@5.1.23) (2020-05-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.22](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.21...@react-navigation/compat@5.1.22) (2020-05-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.21](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.20...@react-navigation/compat@5.1.21) (2020-05-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.17](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.16...@react-navigation/compat@5.1.17) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.15...@react-navigation/compat@5.1.16) (2020-05-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.14...@react-navigation/compat@5.1.15) (2020-05-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix typo in navigationOptions ([8cbb201](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
|
||||||
|
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6785)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/compat",
|
"name": "@react-navigation/compat",
|
||||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||||
"version": "5.1.1",
|
"version": "5.1.26",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
"homepage": "https://reactnavigation.org/docs/compatibility.html",
|
"homepage": "https://reactnavigation.org/docs/compatibility.html",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
"react-native": "src/index.tsx",
|
"react-native": "src/index.tsx",
|
||||||
|
"source": "src/index.tsx",
|
||||||
"module": "lib/module/index.js",
|
"module": "lib/module/index.js",
|
||||||
"types": "lib/typescript/src/index.d.ts",
|
"types": "lib/typescript/src/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"lib"
|
"lib",
|
||||||
|
"!**/__tests__"
|
||||||
],
|
],
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -25,11 +27,11 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.9.3",
|
"@react-native-community/bob": "^0.14.3",
|
||||||
"@react-navigation/native": "^5.0.9",
|
"@react-navigation/native": "^5.5.1",
|
||||||
"@types/react": "^16.9.19",
|
"@types/react": "^16.9.34",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
NavigationProp,
|
NavigationProp,
|
||||||
RouteProp,
|
RouteProp,
|
||||||
EventMapBase,
|
EventMapBase,
|
||||||
|
NavigationRouteContext,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import CompatScreen from './CompatScreen';
|
import CompatScreen from './CompatScreen';
|
||||||
import ScreenPropsContext from './ScreenPropsContext';
|
import ScreenPropsContext from './ScreenPropsContext';
|
||||||
@@ -67,9 +68,12 @@ export default function createCompatNavigatorFactory<
|
|||||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||||
|
|
||||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||||
|
const parentRouteParams = React.useContext(NavigationRouteContext)
|
||||||
|
?.params;
|
||||||
|
|
||||||
const screens = React.useMemo(
|
const screens = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
routeNames.map(name => {
|
routeNames.map((name) => {
|
||||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||||
|
|
||||||
let initialParams;
|
let initialParams;
|
||||||
@@ -135,7 +139,7 @@ export default function createCompatNavigatorFactory<
|
|||||||
<Pair.Screen
|
<Pair.Screen
|
||||||
key={name}
|
key={name}
|
||||||
name={name}
|
name={name}
|
||||||
initialParams={initialParams}
|
initialParams={{ ...parentRouteParams, ...initialParams }}
|
||||||
options={screenOptions}
|
options={screenOptions}
|
||||||
>
|
>
|
||||||
{({ navigation, route }) => (
|
{({ navigation, route }) => (
|
||||||
@@ -148,7 +152,7 @@ export default function createCompatNavigatorFactory<
|
|||||||
</Pair.Screen>
|
</Pair.Screen>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
[screenProps]
|
[parentRouteParams, screenProps]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -163,7 +167,7 @@ export default function createCompatNavigatorFactory<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.navigationOtions = parentNavigationOptions;
|
Navigator.navigationOptions = parentNavigationOptions;
|
||||||
|
|
||||||
return Navigator;
|
return Navigator;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function push(routeName: string, params?: object, action?: never) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pop(n: number) {
|
export function pop(n: number = 1) {
|
||||||
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function useCompatNavigation<
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const isFirstRouteInParent = useNavigationState(
|
const isFirstRouteInParent = useNavigationState(
|
||||||
state => state.routes[0].key === route.key
|
(state) => state.routes[0].key === route.key
|
||||||
);
|
);
|
||||||
|
|
||||||
const context = React.useRef<Record<string, any>>({});
|
const context = React.useRef<Record<string, any>>({});
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ export default function withNavigation<
|
|||||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
|
WrappedComponent.displayName = `withNavigation(${
|
||||||
Comp.name})`;
|
Comp.displayName || Comp.name
|
||||||
|
})`;
|
||||||
|
|
||||||
return WrappedComponent;
|
return WrappedComponent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ export default function withNavigationFocus<
|
|||||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
|
WrappedComponent.displayName = `withNavigationFocus(${
|
||||||
Comp.name})`;
|
Comp.displayName || Comp.name
|
||||||
|
})`;
|
||||||
|
|
||||||
return WrappedComponent;
|
return WrappedComponent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,250 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.10.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.9.0...@react-navigation/core@5.10.0) (2020-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* catch missing params when they are required in navigate ([#8389](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8389)) ([8774ca9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/8774ca97e1da91e97677ecd816c85f66af296b93))
|
||||||
|
* make sure the wildcard pattern catches nested unmatched routes ([c3bd349](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c3bd349d77688011c9c55027edd66c6f39de2ade))
|
||||||
|
* only use the query params for focused route in path ([2d66ef9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2d66ef93ec9923a452415c482c40e7c6b769917c))
|
||||||
|
* prevent state change being emitted unnecessarily ([ab1f79c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/ab1f79c096e94475a4da1acf1c850d04fb1bc4cf))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add wildcard patterns for paths ([4fe72e3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/4fe72e3ce7bae9120d04e490401f3bad58ebdf5c)), closes [#8019](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8019)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.9.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.2...@react-navigation/core@5.9.0) (2020-05-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add ref to get current options in `ServerContainer` ([#8333](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8333)) ([0b1a718](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0b1a718756e208d84b20e45ca56004332308ad54))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.8.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.1...@react-navigation/core@5.8.2) (2020-05-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.8.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.0...@react-navigation/core@5.8.1) (2020-05-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.8.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.7.0...@react-navigation/core@5.8.0) (2020-05-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add getCurrentOptions ([#8277](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8277)) ([d024ec6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d024ec6d74dffe481ce6fde732c729e20c1668f4))
|
||||||
|
* add getCurrentRoute ([#8254](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8254)) ([7b25c8e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/7b25c8eb2e6f96128fd86b92615346ce55bedeca))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.1...@react-navigation/core@5.7.0) (2020-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't use Object.fromEntries ([51f4d11](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/51f4d11fdf4bd2bb06f8cd4094f051816590e62c))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.0...@react-navigation/core@5.6.1) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't use flat since it's not supported in node ([21b397f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/21b397f0d6b96ec4875d3172f47533130bb08009))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.2...@react-navigation/core@5.6.0) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ignore extra slashes in the pattern ([3c47716](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3c47716826d0dfa69dfa6112141c116723372ea1))
|
||||||
|
* ignore state updates when we're not mounted ([0149e85](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0149e85a95b90c6a9d487fa753ddbf5d01c03e3d)), closes [#8226](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8226)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* merge path patterns for nested screens ([#8253](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8253)) ([acc9646](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/acc9646426fee53558d686dfbe5fd0e35361d8c0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.0...@react-navigation/core@5.5.1) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* avoid cleaning up state when a new navigator is mounted. fixes [#8195](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8195) ([f6d0676](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f6d06768d3c36d1f5beaffcb660f3c259209f2e7))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.4.0...@react-navigation/core@5.5.0) (2020-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add support for optional params to linking ([#8196](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8196)) ([fcd1cc6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/fcd1cc64c151e4941f3f544a54b5048d853821f6))
|
||||||
|
* support params anywhere in path segement ([#8184](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8184)) ([3999fc2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3999fc28365c3a06a17d963c7be7fb7e897f99e0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.5...@react-navigation/core@5.4.0) (2020-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handle empty paths when parsing ([c3fa83e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c3fa83efe0d73db76365f8be3d6a8ca1d1289b71))
|
||||||
|
* parsing url ([bd35b4f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/bd35b4fc202c3868fb75c3675b62de67557089e1))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.4...@react-navigation/core@5.3.5) (2020-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add initial option for navigating to nested navigators ([004c7d7](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/004c7d7ab1f80faf04b2a1836ec6b79a5419e45f))
|
||||||
|
* add initial param for actions from deep link ([a3f7a5f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/a3f7a5feba2e6aa2158aeaea6cde73ae1603173e))
|
||||||
|
* handle initial: false for nested route after first initialization ([187aefe](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/187aefe9c400b499f920c212bf856414e25c5aaf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.2...@react-navigation/core@5.3.3) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* switch order of focus and blur events. closes [#7963](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7963) ([ce3994c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/ce3994c82c28669d5742017eb7627e9adf996933))
|
||||||
|
* workaround warning about setState in another component in render ([d4fd906](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d4fd906915cc20d6fb21508384c05a540d8644d8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.1...@react-navigation/core@5.3.2) (2020-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handle no path property and undefined query params ([#7911](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7911)) ([cd47915](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/cd47915861a56cd7eaa9ac79f5139cde56ca95a7))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.0...@react-navigation/core@5.3.1) (2020-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't emit events for screens that don't exist anymore ([1c00142](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1c001424b595b40f9db9343096c833f75353b099))
|
||||||
|
* only call listeners for focused screen for global events ([3096de6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3096de62868a7ed9ed65e529c8ddfa001b9be486))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* return correct value for isFocused after changing screens ([5b15c71](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5b15c7164f5503f2f0d51006a3f23bd0c58fd9b7)), closes [#7843](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7843)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* support function in listeners prop ([3709e65](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3709e652f41a16c2c2b05d5dbbe1da2017ba2c3f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/core",
|
"name": "@react-navigation/core",
|
||||||
"description": "Core utilities for building navigators",
|
"description": "Core utilities for building navigators",
|
||||||
"version": "5.2.1",
|
"version": "5.10.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
"homepage": "https://reactnavigation.org",
|
"homepage": "https://reactnavigation.org",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
"react-native": "src/index.tsx",
|
"react-native": "src/index.tsx",
|
||||||
|
"source": "src/index.tsx",
|
||||||
"module": "lib/module/index.js",
|
"module": "lib/module/index.js",
|
||||||
"types": "lib/typescript/src/index.d.ts",
|
"types": "lib/typescript/src/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"lib"
|
"lib",
|
||||||
|
"!**/__tests__"
|
||||||
],
|
],
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -29,24 +31,23 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^5.1.0",
|
"@react-navigation/routers": "^5.4.7",
|
||||||
"escape-string-regexp": "^2.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"query-string": "^6.10.1",
|
"nanoid": "^3.1.5",
|
||||||
"react-is": "^16.12.0",
|
"query-string": "^6.12.1",
|
||||||
"shortid": "^2.2.15",
|
"react-is": "^16.13.0",
|
||||||
"use-subscription": "^1.3.0"
|
"use-subscription": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.9.3",
|
"@react-native-community/bob": "^0.14.3",
|
||||||
"@types/react": "^16.9.19",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-is": "^16.7.1",
|
"@types/react-is": "^16.7.1",
|
||||||
"@types/shortid": "^0.0.29",
|
|
||||||
"@types/use-subscription": "^1.0.0",
|
"@types/use-subscription": "^1.0.0",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-native-testing-library": "^1.12.0",
|
"react-native-testing-library": "^1.13.2",
|
||||||
"react-test-renderer": "~16.12.0",
|
"react-test-renderer": "~16.13.1",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
|
|||||||
@@ -9,49 +9,26 @@ import {
|
|||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||||
|
import { ScheduleUpdateContext } from './useScheduleUpdate';
|
||||||
import useFocusedListeners from './useFocusedListeners';
|
import useFocusedListeners from './useFocusedListeners';
|
||||||
import useDevTools from './useDevTools';
|
import useDevTools from './useDevTools';
|
||||||
import useStateGetters from './useStateGetters';
|
import useStateGetters from './useStateGetters';
|
||||||
|
import useOptionsGetters from './useOptionsGetters';
|
||||||
|
import useEventEmitter from './useEventEmitter';
|
||||||
|
import useSyncState from './useSyncState';
|
||||||
import isSerializable from './isSerializable';
|
import isSerializable from './isSerializable';
|
||||||
|
|
||||||
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
||||||
import useEventEmitter from './useEventEmitter';
|
import NavigationStateContext from './NavigationStateContext';
|
||||||
import useSyncState from './useSyncState';
|
|
||||||
|
|
||||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||||
|
|
||||||
const MISSING_CONTEXT_ERROR =
|
const DEVTOOLS_CONFIG_KEY =
|
||||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
|
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED';
|
||||||
|
|
||||||
const NOT_INITIALIZED_ERROR =
|
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.";
|
"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;
|
|
||||||
}>({
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let hasWarnedForSerialization = false;
|
let hasWarnedForSerialization = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +50,7 @@ const getPartialState = (
|
|||||||
return {
|
return {
|
||||||
...partialState,
|
...partialState,
|
||||||
stale: true,
|
stale: true,
|
||||||
routes: state.routes.map(route => {
|
routes: state.routes.map((route) => {
|
||||||
if (route.state === undefined) {
|
if (route.state === undefined) {
|
||||||
return route as Route<string> & {
|
return route as Route<string> & {
|
||||||
state?: PartialState<NavigationState>;
|
state?: PartialState<NavigationState>;
|
||||||
@@ -102,7 +79,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
independent,
|
independent,
|
||||||
children,
|
children,
|
||||||
}: NavigationContainerProps,
|
}: NavigationContainerProps,
|
||||||
ref: React.Ref<NavigationContainerRef>
|
ref?: React.Ref<NavigationContainerRef>
|
||||||
) {
|
) {
|
||||||
const parent = React.useContext(NavigationStateContext);
|
const parent = React.useContext(NavigationStateContext);
|
||||||
|
|
||||||
@@ -112,7 +89,13 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [state, getState, setState] = useSyncState<State>(() =>
|
const [
|
||||||
|
state,
|
||||||
|
getState,
|
||||||
|
setState,
|
||||||
|
scheduleUpdate,
|
||||||
|
flushUpdates,
|
||||||
|
] = useSyncState<State>(() =>
|
||||||
getPartialState(initialState == null ? undefined : initialState)
|
getPartialState(initialState == null ? undefined : initialState)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -136,6 +119,9 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { trackState, trackAction } = useDevTools({
|
const { trackState, trackAction } = useDevTools({
|
||||||
|
enabled:
|
||||||
|
// @ts-ignore
|
||||||
|
DEVTOOLS_CONFIG_KEY in global ? global[DEVTOOLS_CONFIG_KEY] : false,
|
||||||
name: '@react-navigation',
|
name: '@react-navigation',
|
||||||
reset,
|
reset,
|
||||||
state,
|
state,
|
||||||
@@ -155,7 +141,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
throw new Error(NOT_INITIALIZED_ERROR);
|
throw new Error(NOT_INITIALIZED_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners[0](navigation => navigation.dispatch(action));
|
listeners[0]((navigation) => navigation.dispatch(action));
|
||||||
};
|
};
|
||||||
|
|
||||||
const canGoBack = () => {
|
const canGoBack = () => {
|
||||||
@@ -163,7 +149,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result, handled } = listeners[0](navigation =>
|
const { result, handled } = listeners[0]((navigation) =>
|
||||||
navigation.canGoBack()
|
navigation.canGoBack()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -186,8 +172,21 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
return getStateForRoute('root');
|
return getStateForRoute('root');
|
||||||
}, [getStateForRoute]);
|
}, [getStateForRoute]);
|
||||||
|
|
||||||
|
const getCurrentRoute = React.useCallback(() => {
|
||||||
|
let state = getRootState();
|
||||||
|
if (state === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
while (state.routes[state.index].state !== undefined) {
|
||||||
|
state = state.routes[state.index].state as NavigationState;
|
||||||
|
}
|
||||||
|
return state.routes[state.index];
|
||||||
|
}, [getRootState]);
|
||||||
|
|
||||||
const emitter = useEventEmitter();
|
const emitter = useEventEmitter();
|
||||||
|
|
||||||
|
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
||||||
any
|
any
|
||||||
@@ -206,6 +205,10 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
dispatch,
|
dispatch,
|
||||||
canGoBack,
|
canGoBack,
|
||||||
getRootState,
|
getRootState,
|
||||||
|
dangerouslyGetState: () => state,
|
||||||
|
dangerouslyGetParent: () => undefined,
|
||||||
|
getCurrentRoute,
|
||||||
|
getCurrentOptions,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const builderContext = React.useMemo(
|
const builderContext = React.useMemo(
|
||||||
@@ -217,6 +220,11 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
[addFocusedListener, trackAction, addStateGetter]
|
[addFocusedListener, trackAction, addStateGetter]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scheduleContext = React.useMemo(
|
||||||
|
() => ({ scheduleUpdate, flushUpdates }),
|
||||||
|
[scheduleUpdate, flushUpdates]
|
||||||
|
);
|
||||||
|
|
||||||
const context = React.useMemo(
|
const context = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
state,
|
state,
|
||||||
@@ -224,10 +232,17 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
setState,
|
setState,
|
||||||
getKey,
|
getKey,
|
||||||
setKey,
|
setKey,
|
||||||
|
addOptionsGetter,
|
||||||
}),
|
}),
|
||||||
[getKey, getState, setKey, setState, state]
|
[getKey, getState, setKey, setState, state, addOptionsGetter]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onStateChangeRef = React.useRef(onStateChange);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onStateChangeRef.current = onStateChange;
|
||||||
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (
|
if (
|
||||||
@@ -254,19 +269,21 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
trackState(getRootState);
|
trackState(getRootState);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFirstMountRef.current && onStateChange) {
|
if (!isFirstMountRef.current && onStateChangeRef.current) {
|
||||||
onStateChange(getRootState());
|
onStateChangeRef.current(getRootState());
|
||||||
}
|
}
|
||||||
|
|
||||||
isFirstMountRef.current = false;
|
isFirstMountRef.current = false;
|
||||||
}, [onStateChange, trackState, getRootState, emitter, state]);
|
}, [trackState, getRootState, emitter, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationBuilderContext.Provider value={builderContext}>
|
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
||||||
<NavigationStateContext.Provider value={context}>
|
<NavigationBuilderContext.Provider value={builderContext}>
|
||||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
<NavigationStateContext.Provider value={context}>
|
||||||
</NavigationStateContext.Provider>
|
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||||
</NavigationBuilderContext.Provider>
|
</NavigationStateContext.Provider>
|
||||||
|
</NavigationBuilderContext.Provider>
|
||||||
|
</ScheduleUpdateContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
11
packages/core/src/CurrentRenderContext.tsx
Normal file
11
packages/core/src/CurrentRenderContext.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context which holds the values for the current navigation tree.
|
||||||
|
* Intended for use in SSR. This is not safe to use on the client.
|
||||||
|
*/
|
||||||
|
const CurrentRenderContext = React.createContext<
|
||||||
|
{ options?: object } | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
export default CurrentRenderContext;
|
||||||
13
packages/core/src/NavigationHelpersContext.tsx
Normal file
13
packages/core/src/NavigationHelpersContext.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ParamListBase } from '@react-navigation/routers';
|
||||||
|
import { NavigationHelpers } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context which holds the navigation helpers of the parent navigator.
|
||||||
|
* Navigators should use this context in their view component.
|
||||||
|
*/
|
||||||
|
const NavigationHelpersContext = React.createContext<
|
||||||
|
NavigationHelpers<ParamListBase> | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
export default NavigationHelpersContext;
|
||||||
35
packages/core/src/NavigationStateContext.tsx
Normal file
35
packages/core/src/NavigationStateContext.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { NavigationState, PartialState } from '@react-navigation/routers';
|
||||||
|
|
||||||
|
const MISSING_CONTEXT_ERROR =
|
||||||
|
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
|
||||||
|
|
||||||
|
export default 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;
|
||||||
|
addOptionsGetter?: (
|
||||||
|
key: string,
|
||||||
|
getter: () => object | undefined | null
|
||||||
|
) => 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);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -5,12 +5,13 @@ import {
|
|||||||
NavigationState,
|
NavigationState,
|
||||||
PartialState,
|
PartialState,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
import NavigationStateContext from './NavigationStateContext';
|
||||||
import NavigationContext from './NavigationContext';
|
import NavigationContext from './NavigationContext';
|
||||||
import NavigationRouteContext from './NavigationRouteContext';
|
import NavigationRouteContext from './NavigationRouteContext';
|
||||||
import StaticContainer from './StaticContainer';
|
import StaticContainer from './StaticContainer';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import { NavigationProp, RouteConfig, EventMapBase } from './types';
|
import { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||||
|
import useOptionsGetters from './useOptionsGetters';
|
||||||
|
|
||||||
type Props<
|
type Props<
|
||||||
State extends NavigationState,
|
State extends NavigationState,
|
||||||
@@ -24,6 +25,7 @@ type Props<
|
|||||||
};
|
};
|
||||||
getState: () => State;
|
getState: () => State;
|
||||||
setState: (state: State) => void;
|
setState: (state: State) => void;
|
||||||
|
options: object;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,18 +42,31 @@ export default function SceneView<
|
|||||||
navigation,
|
navigation,
|
||||||
getState,
|
getState,
|
||||||
setState,
|
setState,
|
||||||
|
options,
|
||||||
}: Props<State, ScreenOptions, EventMap>) {
|
}: Props<State, ScreenOptions, EventMap>) {
|
||||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||||
|
|
||||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||||
|
|
||||||
|
const optionsRef = React.useRef<object | undefined>(options);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
optionsRef.current = options;
|
||||||
|
}, [options]);
|
||||||
|
|
||||||
|
const getOptions = React.useCallback(() => optionsRef.current, []);
|
||||||
|
|
||||||
|
const { addOptionsGetter } = useOptionsGetters({
|
||||||
|
key: route.key,
|
||||||
|
getOptions,
|
||||||
|
});
|
||||||
|
|
||||||
const setKey = React.useCallback((key: string) => {
|
const setKey = React.useCallback((key: string) => {
|
||||||
navigatorKeyRef.current = key;
|
navigatorKeyRef.current = key;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getCurrentState = React.useCallback(() => {
|
const getCurrentState = React.useCallback(() => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const currentRoute = state.routes.find(r => r.key === route.key);
|
const currentRoute = state.routes.find((r) => r.key === route.key);
|
||||||
|
|
||||||
return currentRoute ? currentRoute.state : undefined;
|
return currentRoute ? currentRoute.state : undefined;
|
||||||
}, [getState, route.key]);
|
}, [getState, route.key]);
|
||||||
@@ -62,7 +77,7 @@ export default function SceneView<
|
|||||||
|
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
routes: state.routes.map(r =>
|
routes: state.routes.map((r) =>
|
||||||
r.key === route.key ? { ...r, state: child } : r
|
r.key === route.key ? { ...r, state: child } : r
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -77,8 +92,16 @@ export default function SceneView<
|
|||||||
setState: setCurrentState,
|
setState: setCurrentState,
|
||||||
getKey,
|
getKey,
|
||||||
setKey,
|
setKey,
|
||||||
|
addOptionsGetter,
|
||||||
}),
|
}),
|
||||||
[getCurrentState, getKey, route.state, setCurrentState, setKey]
|
[
|
||||||
|
getCurrentState,
|
||||||
|
getKey,
|
||||||
|
route.state,
|
||||||
|
setCurrentState,
|
||||||
|
setKey,
|
||||||
|
addOptionsGetter,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
NavigationState,
|
NavigationState,
|
||||||
Router,
|
Router,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import BaseNavigationContainer, {
|
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||||
NavigationStateContext,
|
import NavigationStateContext from '../NavigationStateContext';
|
||||||
} from '../BaseNavigationContainer';
|
|
||||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||||
import useNavigationBuilder from '../useNavigationBuilder';
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
import Screen from '../Screen';
|
import Screen from '../Screen';
|
||||||
@@ -122,7 +121,7 @@ it('handle dispatching with ref', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -220,7 +219,7 @@ it('handle resetting state with ref', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -371,7 +370,7 @@ it('emits state events when the state changes', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
|||||||
key: String(MockRouterKey.current++),
|
key: String(MockRouterKey.current++),
|
||||||
index,
|
index,
|
||||||
routeNames,
|
routeNames,
|
||||||
routes: routeNames.map(name => ({
|
routes: routeNames.map((name) => ({
|
||||||
name,
|
name,
|
||||||
key: name,
|
key: name,
|
||||||
params: routeParamList[name],
|
params: routeParamList[name],
|
||||||
@@ -43,9 +43,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const routes = state.routes
|
const routes = state.routes
|
||||||
.filter(route => routeNames.includes(route.name))
|
.filter((route) => routeNames.includes(route.name))
|
||||||
.map(
|
.map(
|
||||||
route =>
|
(route) =>
|
||||||
({
|
({
|
||||||
...route,
|
...route,
|
||||||
key: route.key || `${route.name}-${MockRouterKey.current++}`,
|
key: route.key || `${route.name}-${MockRouterKey.current++}`,
|
||||||
@@ -73,7 +73,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getStateForRouteNamesChange(state, { routeNames }) {
|
getStateForRouteNamesChange(state, { routeNames }) {
|
||||||
const routes = state.routes.filter(route =>
|
const routes = state.routes.filter((route) =>
|
||||||
routeNames.includes(route.name)
|
routeNames.includes(route.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getStateForRouteFocus(state, key) {
|
getStateForRouteFocus(state, key) {
|
||||||
const index = state.routes.findIndex(r => r.key === key);
|
const index = state.routes.findIndex((r) => r.key === key);
|
||||||
|
|
||||||
if (index === -1 || index === state.index) {
|
if (index === -1 || index === state.index) {
|
||||||
return state;
|
return state;
|
||||||
@@ -105,7 +105,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
|||||||
|
|
||||||
case 'NAVIGATE': {
|
case 'NAVIGATE': {
|
||||||
const index = state.routes.findIndex(
|
const index = state.routes.findIndex(
|
||||||
route => route.name === action.payload.name
|
(route) => route.name === action.payload.name
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ it('gets navigate action from state', () => {
|
|||||||
author: 'jane',
|
author: 'jane',
|
||||||
},
|
},
|
||||||
screen: 'qux',
|
screen: 'qux',
|
||||||
|
initial: true,
|
||||||
},
|
},
|
||||||
screen: 'bar',
|
screen: 'bar',
|
||||||
|
initial: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: 'NAVIGATE',
|
type: 'NAVIGATE',
|
||||||
@@ -70,9 +72,11 @@ it('gets navigate action from state', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
params: {
|
params: {
|
||||||
|
initial: true,
|
||||||
screen: 'bar',
|
screen: 'bar',
|
||||||
params: {
|
params: {
|
||||||
screen: 'quz',
|
screen: 'quz',
|
||||||
|
initial: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -379,6 +379,8 @@ it("doesn't update state if action wasn't handled", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cleans up state when the navigator unmounts', () => {
|
it('cleans up state when the navigator unmounts', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
@@ -426,6 +428,8 @@ it('cleans up state when the navigator unmounts', () => {
|
|||||||
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
|
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
act(() => jest.runAllTimers());
|
||||||
|
|
||||||
expect(onStateChange).toBeCalledTimes(2);
|
expect(onStateChange).toBeCalledTimes(2);
|
||||||
expect(onStateChange).lastCalledWith(undefined);
|
expect(onStateChange).lastCalledWith(undefined);
|
||||||
});
|
});
|
||||||
@@ -626,7 +630,7 @@ it('updates route params with setParams applied to parent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles change in route names', () => {
|
it('handles change in route names', async () => {
|
||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
useNavigationBuilder(MockRouter, props);
|
useNavigationBuilder(MockRouter, props);
|
||||||
return null;
|
return null;
|
||||||
@@ -635,7 +639,7 @@ it('handles change in route names', () => {
|
|||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const root = render(
|
const root = render(
|
||||||
<BaseNavigationContainer onStateChange={onStateChange}>
|
<BaseNavigationContainer>
|
||||||
<TestNavigator initialRouteName="bar">
|
<TestNavigator initialRouteName="bar">
|
||||||
<Screen name="foo" component={jest.fn()} />
|
<Screen name="foo" component={jest.fn()} />
|
||||||
<Screen name="bar" component={jest.fn()} />
|
<Screen name="bar" component={jest.fn()} />
|
||||||
@@ -737,6 +741,366 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('navigates to nested child in a navigator with initial: false', () => {
|
||||||
|
const TestRouter: typeof MockRouter = (options) => {
|
||||||
|
const router = MockRouter(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...router,
|
||||||
|
|
||||||
|
getStateForAction(state, action, options) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'NAVIGATE': {
|
||||||
|
if (!options.routeNames.includes(action.payload.name as any)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
...state.routes,
|
||||||
|
{
|
||||||
|
key: String(MockRouterKey.current++),
|
||||||
|
name: action.payload.name,
|
||||||
|
params: action.payload.params,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
index: routes.length - 1,
|
||||||
|
routes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return router.getStateForAction(state, action, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as typeof router;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
|
||||||
|
|
||||||
|
return descriptors[state.routes[state.index].key].render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = ({ route }: any): any =>
|
||||||
|
`[${route.name}, ${JSON.stringify(route.params)}]`;
|
||||||
|
|
||||||
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const first = render(
|
||||||
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo-a" component={TestComponent} />
|
||||||
|
<Screen name="foo-b" component={TestComponent} />
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 0,
|
||||||
|
key: '0',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '1',
|
||||||
|
routeNames: ['foo-a', 'foo-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo-a',
|
||||||
|
name: 'foo-a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'foo-b',
|
||||||
|
name: 'foo-b',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
act(
|
||||||
|
() =>
|
||||||
|
navigation.current &&
|
||||||
|
navigation.current.navigate('bar', {
|
||||||
|
screen: 'bar-b',
|
||||||
|
params: { test: 42 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(first).toMatchInlineSnapshot(
|
||||||
|
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 2,
|
||||||
|
key: '0',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: 'bar',
|
||||||
|
params: { params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '3',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff', test: 42 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const second = render(
|
||||||
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo-a" component={TestComponent} />
|
||||||
|
<Screen name="foo-b" component={TestComponent} />
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 0,
|
||||||
|
key: '4',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '5',
|
||||||
|
routeNames: ['foo-a', 'foo-b'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-a', name: 'foo-a' },
|
||||||
|
{ key: 'foo-b', name: 'foo-b' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
act(
|
||||||
|
() =>
|
||||||
|
navigation.current &&
|
||||||
|
navigation.current.navigate('bar', {
|
||||||
|
screen: 'bar-b',
|
||||||
|
params: { test: 42 },
|
||||||
|
initial: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 2,
|
||||||
|
key: '4',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 2,
|
||||||
|
key: '7',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
{ key: '8', name: 'bar-b', params: { test: 42 } },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const third = render(
|
||||||
|
<BaseNavigationContainer
|
||||||
|
ref={navigation}
|
||||||
|
initialState={{
|
||||||
|
index: 1,
|
||||||
|
routes: [
|
||||||
|
{ name: 'foo' },
|
||||||
|
{
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '7',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'test',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={TestComponent} />
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 1,
|
||||||
|
key: '11',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-9', name: 'foo' },
|
||||||
|
{
|
||||||
|
key: 'bar-10',
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '14',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a-12',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b-13',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('gives access to internal state', () => {
|
it('gives access to internal state', () => {
|
||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
@@ -1126,3 +1490,128 @@ it("doesn't throw if children is null", () => {
|
|||||||
|
|
||||||
expect(() => render(element).update(element)).not.toThrowError();
|
expect(() => render(element).update(element)).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns currently focused route with getCurrentRoute', () => {
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return descriptors[state.routes[state.index].key].render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestScreen = () => null;
|
||||||
|
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const container = (
|
||||||
|
<BaseNavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="bar" options={{ a: 'b' }}>
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestScreen}
|
||||||
|
options={{ sample: 'data' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="xux" component={TestScreen} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(container).update(container);
|
||||||
|
|
||||||
|
expect(navigation.current?.getCurrentRoute()).toEqual({
|
||||||
|
key: 'bar-a',
|
||||||
|
name: 'bar-a',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns currently focused route's options with getCurrentOptions", () => {
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return descriptors[state.routes[state.index].key].render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestScreen = () => null;
|
||||||
|
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const container = (
|
||||||
|
<BaseNavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="bar" options={{ a: 'b' }}>
|
||||||
|
{() => (
|
||||||
|
<TestNavigator
|
||||||
|
initialRouteName="bar-a"
|
||||||
|
screenOptions={() => ({ sample2: 'data' })}
|
||||||
|
>
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestScreen}
|
||||||
|
options={{ sample: 'data' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="xux" component={TestScreen} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(container).update(container);
|
||||||
|
|
||||||
|
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||||
|
sample: 'data',
|
||||||
|
sample2: 'data',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw if while getting current options with no options defined', () => {
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return descriptors[state.routes[state.index].key].render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestScreen = () => null;
|
||||||
|
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const container = (
|
||||||
|
<BaseNavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="bar" options={{ a: 'b' }}>
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestScreen}
|
||||||
|
options={{ wrongKey: true }}
|
||||||
|
/>
|
||||||
|
<Screen name="bar-a" component={TestScreen} />
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(container).update(container);
|
||||||
|
|
||||||
|
expect(navigation.current?.getCurrentOptions()).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw if while getting current options with empty container', () => {
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const container = <BaseNavigationContainer ref={navigation} />;
|
||||||
|
|
||||||
|
render(container).update(container);
|
||||||
|
|
||||||
|
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ it('sets options with screenOptions prop as an object', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{state.routes.map(route => {
|
{state.routes.map((route) => {
|
||||||
const { render, options } = descriptors[route.key];
|
const { render, options } = descriptors[route.key];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -179,7 +179,7 @@ it('sets options with screenOptions prop as a fuction', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{state.routes.map(route => {
|
{state.routes.map((route) => {
|
||||||
const { render, options } = descriptors[route.key];
|
const { render, options } = descriptors[route.key];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -262,8 +262,10 @@ it('sets initial options with setOptions', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TestScreen = ({ navigation }: any): any => {
|
const TestScreen = ({ navigation }: any): any => {
|
||||||
navigation.setOptions({
|
React.useEffect(() => {
|
||||||
title: 'Hello world',
|
navigation.setOptions({
|
||||||
|
title: 'Hello world',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return 'Test screen';
|
return 'Test screen';
|
||||||
@@ -273,7 +275,7 @@ it('sets initial options with setOptions', () => {
|
|||||||
<BaseNavigationContainer>
|
<BaseNavigationContainer>
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="foo" options={{ color: 'blue' }}>
|
<Screen name="foo" options={{ color: 'blue' }}>
|
||||||
{props => <TestScreen {...props} />}
|
{(props) => <TestScreen {...props} />}
|
||||||
</Screen>
|
</Screen>
|
||||||
<Screen name="bar" component={jest.fn()} />
|
<Screen name="bar" component={jest.fn()} />
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
@@ -315,12 +317,12 @@ it('updates options with setOptions', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TestScreen = ({ navigation }: any): any => {
|
const TestScreen = ({ navigation }: any): any => {
|
||||||
navigation.setOptions({
|
|
||||||
title: 'Hello world',
|
|
||||||
description: 'Something here',
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
title: 'Hello world',
|
||||||
|
description: 'Something here',
|
||||||
|
});
|
||||||
|
|
||||||
const timer = setTimeout(() =>
|
const timer = setTimeout(() =>
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: 'Hello again',
|
title: 'Hello again',
|
||||||
@@ -338,7 +340,7 @@ it('updates options with setOptions', () => {
|
|||||||
<BaseNavigationContainer>
|
<BaseNavigationContainer>
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="foo" options={{ color: 'blue' }}>
|
<Screen name="foo" options={{ color: 'blue' }}>
|
||||||
{props => <TestScreen {...props} />}
|
{(props) => <TestScreen {...props} />}
|
||||||
</Screen>
|
</Screen>
|
||||||
<Screen name="bar" component={jest.fn()} />
|
<Screen name="bar" component={jest.fn()} />
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ it('fires focus and blur events in root navigator', () => {
|
|||||||
|
|
||||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstFocusCallback = jest.fn();
|
const firstFocusCallback = jest.fn();
|
||||||
@@ -97,6 +97,69 @@ it('fires focus and blur events in root navigator', () => {
|
|||||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires focus event after blur', () => {
|
||||||
|
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||||
|
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||||
|
MockRouter,
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||||
|
|
||||||
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
const Test = ({ route, navigation }: any) => {
|
||||||
|
React.useEffect(
|
||||||
|
() =>
|
||||||
|
navigation.addListener('focus', () => callback(route.name, 'focus')),
|
||||||
|
[navigation, route.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
() => navigation.addListener('blur', () => callback(route.name, 'blur')),
|
||||||
|
[navigation, route.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator ref={navigation}>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([['first', 'focus']]);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second'));
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([
|
||||||
|
['first', 'focus'],
|
||||||
|
['first', 'blur'],
|
||||||
|
['second', 'focus'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('first'));
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([
|
||||||
|
['first', 'focus'],
|
||||||
|
['first', 'blur'],
|
||||||
|
['second', 'focus'],
|
||||||
|
['second', 'blur'],
|
||||||
|
['first', 'focus'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('fires focus and blur events in nested navigator', () => {
|
it('fires focus and blur events in nested navigator', () => {
|
||||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||||
const { state, navigation, descriptors } = useNavigationBuilder(
|
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||||
@@ -106,7 +169,7 @@ it('fires focus and blur events in nested navigator', () => {
|
|||||||
|
|
||||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstFocusCallback = jest.fn();
|
const firstFocusCallback = jest.fn();
|
||||||
@@ -376,7 +439,7 @@ it('fires custom events added with addListener', () => {
|
|||||||
state,
|
state,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstCallback = jest.fn();
|
const firstCallback = jest.fn();
|
||||||
@@ -456,7 +519,7 @@ it("doesn't call same listener multiple times with addListener", () => {
|
|||||||
state,
|
state,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
});
|
});
|
||||||
|
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
@@ -565,12 +628,10 @@ it('fires custom events added with listeners prop', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
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(firstCallback).toBeCalledTimes(1);
|
||||||
expect(secondCallback).toBeCalledTimes(1);
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
expect(thirdCallback).toBeCalledTimes(2);
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't call same listener multiple times with listeners", () => {
|
it("doesn't call same listener multiple times with listeners", () => {
|
||||||
@@ -624,6 +685,91 @@ it("doesn't call same listener multiple times with listeners", () => {
|
|||||||
expect(callback).toBeCalledTimes(1);
|
expect(callback).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires listeners when callback is provided for 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={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => firstCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
component={jest.fn()}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="second"
|
||||||
|
listeners={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => secondCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
component={jest.fn()}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="third"
|
||||||
|
listeners={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => thirdCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
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(firstCallback).toBeCalledTimes(1);
|
||||||
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('has option to prevent default', () => {
|
it('has option to prevent default', () => {
|
||||||
expect.assertions(5);
|
expect.assertions(5);
|
||||||
|
|
||||||
@@ -640,7 +786,7 @@ it('has option to prevent default', () => {
|
|||||||
state,
|
state,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
});
|
});
|
||||||
|
|
||||||
const callback = (e: any) => {
|
const callback = (e: any) => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ it('runs focus effect on focus change', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusEffect = jest.fn();
|
const focusEffect = jest.fn();
|
||||||
@@ -107,7 +107,7 @@ it('runs focus effect when initial state is given', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusEffect = jest.fn();
|
const focusEffect = jest.fn();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ it('renders correct focus state', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ it('gets navigation prop from context', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
@@ -38,7 +38,7 @@ it("gets navigation's parent from context", () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
@@ -70,7 +70,7 @@ it("gets navigation's parent's parent from context", () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { render } from 'react-native-testing-library';
|
import { render, act } from 'react-native-testing-library';
|
||||||
import useEventEmitter from '../useEventEmitter';
|
import useEventEmitter from '../useEventEmitter';
|
||||||
import useNavigationCache from '../useNavigationCache';
|
import useNavigationCache from '../useNavigationCache';
|
||||||
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
|
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||||
|
import Screen from '../Screen';
|
||||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||||
|
|
||||||
beforeEach(() => (MockRouterKey.current = 0));
|
beforeEach(() => (MockRouterKey.current = 0));
|
||||||
@@ -40,7 +43,7 @@ it('preserves reference for navigation objects', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (previous.current) {
|
if (previous.current) {
|
||||||
Object.keys(navigations).forEach(key => {
|
Object.keys(navigations).forEach((key) => {
|
||||||
expect(navigations[key]).toBe(previous.current[key]);
|
expect(navigations[key]).toBe(previous.current[key]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -56,3 +59,136 @@ it('preserves reference for navigation objects', () => {
|
|||||||
|
|
||||||
root.update(<Test />);
|
root.update(<Test />);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns correct value for isFocused', () => {
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
|
};
|
||||||
|
|
||||||
|
let navigation: any;
|
||||||
|
|
||||||
|
const Test = (props: any) => {
|
||||||
|
navigation = props.navigation;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first">{() => null}</Screen>
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(false);
|
||||||
|
|
||||||
|
act(() => navigation.navigate('second'));
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(true);
|
||||||
|
|
||||||
|
act(() => navigation.navigate('third'));
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(false);
|
||||||
|
|
||||||
|
act(() => navigation.navigate('second'));
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct value for isFocused after changing screens', () => {
|
||||||
|
const TestRouter = (
|
||||||
|
options: Parameters<typeof MockRouter>[0]
|
||||||
|
): ReturnType<typeof MockRouter> => {
|
||||||
|
const router = MockRouter(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...router,
|
||||||
|
|
||||||
|
getStateForRouteNamesChange(state, { routeNames }) {
|
||||||
|
const routes = routeNames.map(
|
||||||
|
(name) =>
|
||||||
|
state.routes.find((r) => r.name === name) || {
|
||||||
|
name,
|
||||||
|
key: name,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
routeNames,
|
||||||
|
routes,
|
||||||
|
index: routes.length - 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
|
||||||
|
|
||||||
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
|
};
|
||||||
|
|
||||||
|
let navigation: any;
|
||||||
|
|
||||||
|
const Test = (props: any) => {
|
||||||
|
navigation = props.navigation;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = render(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first">{() => null}</Screen>
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(false);
|
||||||
|
|
||||||
|
root.update(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(true);
|
||||||
|
|
||||||
|
root.update(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
<Screen name="fourth">{() => null}</Screen>
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(true);
|
||||||
|
|
||||||
|
root.update(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
<Screen name="fourth">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.isFocused()).toBe(false);
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ it('gets the current navigation state', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
const state = useNavigationState(state => state);
|
const state = useNavigationState((state) => state);
|
||||||
|
|
||||||
callback(state);
|
callback(state);
|
||||||
|
|
||||||
@@ -62,13 +62,13 @@ it('gets the current navigation state with selector', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
const index = useNavigationState(state => state.index);
|
const index = useNavigationState((state) => state.index);
|
||||||
|
|
||||||
callback(index);
|
callback(index);
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ it('gets the correct value if selector changes', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
@@ -144,12 +144,12 @@ it('gets the correct value if selector changes', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = render(<App selector={state => state.index} />);
|
const root = render(<App selector={(state) => state.index} />);
|
||||||
|
|
||||||
expect(callback).toBeCalledTimes(1);
|
expect(callback).toBeCalledTimes(1);
|
||||||
expect(callback.mock.calls[0][0]).toBe(0);
|
expect(callback.mock.calls[0][0]).toBe(0);
|
||||||
|
|
||||||
root.update(<App selector={state => state.routes[state.index].name} />);
|
root.update(<App selector={(state) => state.routes[state.index].name} />);
|
||||||
|
|
||||||
expect(callback).toBeCalledTimes(2);
|
expect(callback).toBeCalledTimes(2);
|
||||||
expect(callback.mock.calls[1][0]).toBe('first');
|
expect(callback.mock.calls[1][0]).toBe('first');
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ it("lets children handle the action if parent didn't", () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -270,7 +270,7 @@ it("action doesn't bubble if target is specified", () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -317,7 +317,7 @@ it('logs error if no navigator handled the action', () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{state.routes.map(route => descriptors[route.key].render())}
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ it('gets route prop from context', () => {
|
|||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
return state.routes.map(route => descriptors[route.key].render());
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function createNavigatorFactory<
|
|||||||
EventMap extends EventMapBase,
|
EventMap extends EventMapBase,
|
||||||
NavigatorComponent extends React.ComponentType<any>
|
NavigatorComponent extends React.ComponentType<any>
|
||||||
>(Navigator: NavigatorComponent) {
|
>(Navigator: NavigatorComponent) {
|
||||||
return function<ParamList extends ParamListBase>(): TypedNavigator<
|
return function <ParamList extends ParamListBase>(): TypedNavigator<
|
||||||
ParamList,
|
ParamList,
|
||||||
State,
|
State,
|
||||||
ScreenOptions,
|
ScreenOptions,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PartialState, NavigationState } from '@react-navigation/routers';
|
|||||||
type NavigateParams = {
|
type NavigateParams = {
|
||||||
screen?: string;
|
screen?: string;
|
||||||
params?: NavigateParams;
|
params?: NavigateParams;
|
||||||
|
initial?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NavigateAction = {
|
type NavigateAction = {
|
||||||
@@ -35,6 +36,7 @@ export default function getActionFromState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
route = current.routes[current.routes.length - 1];
|
route = current.routes[current.routes.length - 1];
|
||||||
|
params.initial = current.routes.length === 1;
|
||||||
params.screen = route.name;
|
params.screen = route.name;
|
||||||
|
|
||||||
if (route.state) {
|
if (route.state) {
|
||||||
|
|||||||
@@ -4,19 +4,31 @@ import {
|
|||||||
PartialState,
|
PartialState,
|
||||||
Route,
|
Route,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
|
import { PathConfig } from './types';
|
||||||
|
|
||||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||||
|
|
||||||
type StringifyConfig = Record<string, (value: any) => string>;
|
type StringifyConfig = Record<string, (value: any) => string>;
|
||||||
|
|
||||||
type Options = {
|
type OptionsItem = PathConfig[string];
|
||||||
[routeName: string]:
|
|
||||||
| string
|
type ConfigItem = {
|
||||||
| {
|
pattern?: string;
|
||||||
path?: string;
|
stringify?: StringifyConfig;
|
||||||
stringify?: StringifyConfig;
|
screens?: Record<string, ConfigItem>;
|
||||||
screens?: Options;
|
};
|
||||||
};
|
|
||||||
|
const getActiveRoute = (state: State): { name: string; params?: object } => {
|
||||||
|
const route =
|
||||||
|
typeof state.index === 'number'
|
||||||
|
? state.routes[state.index]
|
||||||
|
: state.routes[state.routes.length - 1];
|
||||||
|
|
||||||
|
if (route.state) {
|
||||||
|
return getActiveRoute(route.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return route;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,120 +60,221 @@ type Options = {
|
|||||||
*/
|
*/
|
||||||
export default function getPathFromState(
|
export default function getPathFromState(
|
||||||
state?: State,
|
state?: State,
|
||||||
options: Options = {}
|
options: PathConfig = {}
|
||||||
): string {
|
): string {
|
||||||
if (state === undefined) {
|
if (state === undefined) {
|
||||||
throw Error('NavigationState not passed');
|
throw Error('NavigationState not passed');
|
||||||
}
|
}
|
||||||
let path = '/';
|
|
||||||
|
|
||||||
|
// Create a normalized configs array which will be easier to use
|
||||||
|
const configs = createNormalizedConfigs(options);
|
||||||
|
|
||||||
|
let path = '/';
|
||||||
let current: State | undefined = state;
|
let current: State | undefined = state;
|
||||||
|
|
||||||
|
const allParams: Record<string, any> = {};
|
||||||
|
|
||||||
while (current) {
|
while (current) {
|
||||||
let index = typeof current.index === 'number' ? current.index : 0;
|
let index = typeof current.index === 'number' ? current.index : 0;
|
||||||
let route = current.routes[index] as Route<string> & {
|
let route = current.routes[index] as Route<string> & {
|
||||||
state?: State;
|
state?: State;
|
||||||
};
|
};
|
||||||
let currentOptions = options;
|
|
||||||
let pattern = route.name;
|
|
||||||
|
|
||||||
while (route.name in currentOptions) {
|
let pattern: string | undefined;
|
||||||
if (typeof currentOptions[route.name] === 'string') {
|
|
||||||
pattern = currentOptions[route.name] as string;
|
let focusedParams: Record<string, any> | undefined;
|
||||||
break;
|
let focusedRoute = getActiveRoute(state);
|
||||||
} else if (typeof currentOptions[route.name] === 'object') {
|
let currentOptions = configs;
|
||||||
// if there is no `screens` property, we return pattern
|
|
||||||
if (
|
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
||||||
!(currentOptions[route.name] as {
|
let nestedRouteNames = [];
|
||||||
screens: Options;
|
|
||||||
}).screens
|
let hasNext = true;
|
||||||
) {
|
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
while (route.name in currentOptions && hasNext) {
|
||||||
break;
|
pattern = currentOptions[route.name].pattern;
|
||||||
|
|
||||||
|
nestedRouteNames.push(route.name);
|
||||||
|
|
||||||
|
if (route.params) {
|
||||||
|
const stringify = currentOptions[route.name]?.stringify;
|
||||||
|
|
||||||
|
const currentParams = fromEntries(
|
||||||
|
Object.entries(route.params).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
stringify?.[key] ? stringify[key](value) : String(value),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pattern) {
|
||||||
|
Object.assign(allParams, currentParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusedRoute === route) {
|
||||||
|
// If this is the focused route, keep the params for later use
|
||||||
|
// We save it here since it's been stringified already
|
||||||
|
focusedParams = { ...currentParams };
|
||||||
|
|
||||||
|
pattern
|
||||||
|
?.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'))
|
||||||
|
// eslint-disable-next-line no-loop-func
|
||||||
|
.forEach((p) => {
|
||||||
|
const name = getParamName(p);
|
||||||
|
|
||||||
|
// Remove the params present in the pattern since we'll only use the rest for query string
|
||||||
|
if (focusedParams) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete focusedParams[name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no `screens` property or no nested state, we return pattern
|
||||||
|
if (!currentOptions[route.name].screens || route.state === undefined) {
|
||||||
|
hasNext = false;
|
||||||
|
} else {
|
||||||
|
index =
|
||||||
|
typeof route.state.index === 'number'
|
||||||
|
? route.state.index
|
||||||
|
: route.state.routes.length - 1;
|
||||||
|
|
||||||
|
const nextRoute = route.state.routes[index];
|
||||||
|
const nestedConfig = currentOptions[route.name].screens;
|
||||||
|
|
||||||
|
// if there is config for next route name, we go deeper
|
||||||
|
if (nestedConfig && nextRoute.name in nestedConfig) {
|
||||||
|
route = nextRoute as Route<string> & { state?: State };
|
||||||
|
currentOptions = nestedConfig;
|
||||||
} else {
|
} else {
|
||||||
// if it is the end of state, we return pattern
|
// If not, there is no sense in going deeper in config
|
||||||
if (route.state === undefined) {
|
hasNext = false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't add empty path strings to path
|
if (pattern === undefined) {
|
||||||
if (pattern !== '') {
|
pattern = nestedRouteNames.join('/');
|
||||||
const config =
|
}
|
||||||
currentOptions[route.name] !== undefined
|
|
||||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
|
||||||
.stringify
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const params = route.params
|
if (currentOptions[route.name] !== undefined) {
|
||||||
? // Stringify all of the param values before we use them
|
path += pattern
|
||||||
Object.entries(route.params).reduce<{
|
.split('/')
|
||||||
[key: string]: string;
|
.map((p) => {
|
||||||
}>((acc, [key, value]) => {
|
const name = getParamName(p);
|
||||||
acc[key] = config?.[key] ? config[key](value) : String(value);
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (currentOptions[route.name] !== undefined) {
|
// We don't know what to show for wildcard patterns
|
||||||
path += pattern
|
// Showing the route name seems ok, though whatever we show here will be incorrect
|
||||||
.split('/')
|
// Since the page doesn't actually exist
|
||||||
.map(p => {
|
if (p === '*') {
|
||||||
const name = p.replace(/^:/, '');
|
return route.name;
|
||||||
|
}
|
||||||
|
|
||||||
// If the path has a pattern for a param, put the param in the path
|
// If the path has a pattern for a param, put the param in the path
|
||||||
if (params && name in params && p.startsWith(':')) {
|
if (p.startsWith(':')) {
|
||||||
const value = params[name];
|
const value = allParams[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
|
if (value === undefined && p.endsWith('?')) {
|
||||||
delete params[name];
|
// Optional params without value assigned in route.params should be ignored
|
||||||
return encodeURIComponent(value);
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeURIComponent(p);
|
return encodeURIComponent(value);
|
||||||
})
|
}
|
||||||
.join('/');
|
|
||||||
} else {
|
return encodeURIComponent(p);
|
||||||
path += encodeURIComponent(route.name);
|
})
|
||||||
|
.join('/');
|
||||||
|
} else {
|
||||||
|
path += encodeURIComponent(route.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focusedParams) {
|
||||||
|
focusedParams = focusedRoute.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.state) {
|
||||||
|
path += '/';
|
||||||
|
} else if (focusedParams) {
|
||||||
|
for (let param in focusedParams) {
|
||||||
|
if (focusedParams[param] === 'undefined') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete focusedParams[param];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.state) {
|
const query = queryString.stringify(focusedParams);
|
||||||
path += '/';
|
|
||||||
} else if (params) {
|
|
||||||
const query = queryString.stringify(params);
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
path += `?${query}`;
|
path += `?${query}`;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = route.state;
|
current = route.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
path =
|
// Remove multiple as well as trailing slashes
|
||||||
path !== '/' && path.slice(path.length - 1) === '/'
|
path = path.replace(/\/+/g, '/');
|
||||||
? path.slice(0, -1)
|
path = path.length > 1 ? path.replace(/\/$/, '') : path;
|
||||||
: path;
|
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Object.fromEntries is not available in older iOS versions
|
||||||
|
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
||||||
|
entries.reduce((acc, [k, v]) => {
|
||||||
|
acc[k] = v;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<K, V>);
|
||||||
|
|
||||||
|
const getParamName = (pattern: string) =>
|
||||||
|
pattern.replace(/^:/, '').replace(/\?$/, '');
|
||||||
|
|
||||||
|
const joinPaths = (...paths: string[]): string =>
|
||||||
|
([] as string[])
|
||||||
|
.concat(...paths.map((p) => p.split('/')))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
|
||||||
|
const createConfigItem = (
|
||||||
|
config: OptionsItem | string,
|
||||||
|
parentPattern?: string
|
||||||
|
): ConfigItem => {
|
||||||
|
if (typeof config === 'string') {
|
||||||
|
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||||
|
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||||
|
|
||||||
|
return { pattern };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an object is specified as the value (e.g. Foo: { ... }),
|
||||||
|
// It can have `path` property and `screens` prop which has nested configs
|
||||||
|
const pattern =
|
||||||
|
config.exact !== true && parentPattern && config.path
|
||||||
|
? joinPaths(parentPattern, config.path)
|
||||||
|
: config.path;
|
||||||
|
|
||||||
|
const screens = config.screens
|
||||||
|
? createNormalizedConfigs(config.screens, pattern)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
|
||||||
|
pattern: pattern?.split('/').filter(Boolean).join('/'),
|
||||||
|
stringify: config.stringify,
|
||||||
|
screens,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNormalizedConfigs = (
|
||||||
|
options: PathConfig,
|
||||||
|
pattern?: string
|
||||||
|
): Record<string, ConfigItem> =>
|
||||||
|
fromEntries(
|
||||||
|
Object.entries(options).map(([name, c]) => {
|
||||||
|
const result = createConfigItem(c, pattern);
|
||||||
|
|
||||||
|
return [name, result];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|||||||
@@ -5,25 +5,17 @@ import {
|
|||||||
PartialState,
|
PartialState,
|
||||||
InitialState,
|
InitialState,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
|
import { PathConfig } from './types';
|
||||||
|
|
||||||
type ParseConfig = Record<string, (value: string) => any>;
|
type ParseConfig = Record<string, (value: string) => any>;
|
||||||
|
|
||||||
type Options = {
|
|
||||||
[routeName: string]:
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
path?: string;
|
|
||||||
parse?: ParseConfig;
|
|
||||||
screens?: Options;
|
|
||||||
initialRouteName?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type RouteConfig = {
|
type RouteConfig = {
|
||||||
match: RegExp;
|
screen: string;
|
||||||
|
regex?: RegExp;
|
||||||
|
path: string;
|
||||||
pattern: string;
|
pattern: string;
|
||||||
routeNames: string[];
|
routeNames: string[];
|
||||||
parse: ParseConfig | undefined;
|
parse?: ParseConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InitialRouteConfig = {
|
type InitialRouteConfig = {
|
||||||
@@ -56,63 +48,128 @@ type ResultState = PartialState<NavigationState> & {
|
|||||||
*/
|
*/
|
||||||
export default function getStateFromPath(
|
export default function getStateFromPath(
|
||||||
path: string,
|
path: string,
|
||||||
options: Options = {}
|
options: PathConfig = {}
|
||||||
): ResultState | undefined {
|
): ResultState | undefined {
|
||||||
if (path === '') {
|
let initialRoutes: InitialRouteConfig[] = [];
|
||||||
|
|
||||||
|
// Create a normalized configs array which will be easier to use
|
||||||
|
const configs = ([] as RouteConfig[])
|
||||||
|
.concat(
|
||||||
|
...Object.keys(options).map((key) =>
|
||||||
|
createNormalizedConfigs(key, options, [], initialRoutes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sort config so that:
|
||||||
|
// - the most exhaustive ones are always at the beginning
|
||||||
|
// - patterns with wildcard are always at the end
|
||||||
|
|
||||||
|
// If one of the patterns starts with the other, it's more exhaustive
|
||||||
|
// So move it up
|
||||||
|
if (a.pattern.startsWith(b.pattern)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.pattern.startsWith(a.pattern)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aParts = a.pattern.split('/');
|
||||||
|
const bParts = b.pattern.split('/');
|
||||||
|
|
||||||
|
const aWildcardIndex = aParts.indexOf('*');
|
||||||
|
const bWildcardIndex = bParts.indexOf('*');
|
||||||
|
|
||||||
|
// If only one of the patterns has a wildcard, move it down in the list
|
||||||
|
if (aWildcardIndex === -1 && bWildcardIndex !== -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aWildcardIndex !== -1 && bWildcardIndex === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aWildcardIndex === bWildcardIndex) {
|
||||||
|
// If `b` has more `/`, it's more exhaustive
|
||||||
|
// So we move it up in the list
|
||||||
|
return bParts.length - aParts.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the wildcard appears later in the pattern (has higher index), it's more specific
|
||||||
|
// So we move it up in the list
|
||||||
|
return bWildcardIndex - aWildcardIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
let remaining = path
|
||||||
|
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
||||||
|
.replace(/^\//, '') // Remove extra leading slash
|
||||||
|
.replace(/\?.*$/, ''); // Remove query params which we will handle later
|
||||||
|
|
||||||
|
// Make sure there is a trailing slash
|
||||||
|
remaining = remaining.endsWith('/') ? remaining : `${remaining}/`;
|
||||||
|
|
||||||
|
if (remaining === '/') {
|
||||||
|
// We need to add special handling of empty path so navigation to empty path also works
|
||||||
|
// When handling empty path, we should only look at the root level config
|
||||||
|
const match = configs.find(
|
||||||
|
(config) =>
|
||||||
|
config.path === '' &&
|
||||||
|
config.routeNames.every(
|
||||||
|
// Make sure that none of the parent configs have a non-empty path defined
|
||||||
|
(name) => !configs.find((c) => c.screen === name)?.path
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return createNestedStateObject(
|
||||||
|
match.routeNames.map((name, i, self) => {
|
||||||
|
if (i === self.length - 1) {
|
||||||
|
return { name, params: parseQueryParams(path, match.parse) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name };
|
||||||
|
}),
|
||||||
|
initialRoutes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
let initialRoutes: InitialRouteConfig[] = [];
|
|
||||||
// Create a normalized configs array which will be easier to use
|
|
||||||
const configs = ([] as RouteConfig[]).concat(
|
|
||||||
...Object.keys(options).map(key =>
|
|
||||||
createNormalizedConfigs(key, options, [], initialRoutes)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let result: PartialState<NavigationState> | undefined;
|
let result: PartialState<NavigationState> | undefined;
|
||||||
let current: PartialState<NavigationState> | undefined;
|
let current: PartialState<NavigationState> | undefined;
|
||||||
|
|
||||||
let remaining = path
|
// We try to match the paths in 2 passes
|
||||||
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
|
// In first pass, we match the whole path against the regex instead of segments
|
||||||
.replace(/^\//, '') // Remove extra leading slash
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
||||||
.replace(/\?.*/, ''); // Remove query params which we will handle later
|
const { routeNames, allParams, remainingPath } = matchAgainstConfigs(
|
||||||
|
remaining,
|
||||||
|
configs.map((c) => ({
|
||||||
|
...c,
|
||||||
|
// Add `$` to the regex to make sure it matches till end of the path and not just beginning
|
||||||
|
regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routeNames !== undefined) {
|
||||||
|
// This will always be empty if full path matched
|
||||||
|
remaining = remainingPath;
|
||||||
|
current = createNestedStateObject(
|
||||||
|
createRouteObjects(configs, routeNames, allParams),
|
||||||
|
initialRoutes
|
||||||
|
);
|
||||||
|
result = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In second pass, we divide the path into segments and match piece by piece
|
||||||
|
// This preserves the old behaviour, but we should remove it in next major
|
||||||
while (remaining) {
|
while (remaining) {
|
||||||
let routeNames: string[] | undefined;
|
let { routeNames, allParams, remainingPath } = matchAgainstConfigs(
|
||||||
let params: Record<string, any> | undefined;
|
remaining,
|
||||||
|
configs
|
||||||
|
);
|
||||||
|
|
||||||
// Go through all configs, and see if the next path segment matches our regex
|
remaining = remainingPath;
|
||||||
for (const config of configs) {
|
|
||||||
const match = remaining.match(config.match);
|
|
||||||
|
|
||||||
// If our regex matches, we need to extract params from the path
|
|
||||||
if (match) {
|
|
||||||
routeNames = [...config.routeNames];
|
|
||||||
|
|
||||||
const paramPatterns = config.pattern
|
|
||||||
.split('/')
|
|
||||||
.filter(p => p.startsWith(':'));
|
|
||||||
|
|
||||||
if (paramPatterns.length) {
|
|
||||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
|
||||||
const key = p.replace(/^:/, '');
|
|
||||||
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
|
|
||||||
|
|
||||||
acc[key] =
|
|
||||||
config.parse && config.parse[key]
|
|
||||||
? config.parse[key](value)
|
|
||||||
: value;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the matched segment from the remaining path
|
|
||||||
remaining = remaining.replace(match[0], '');
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we hadn't matched any segments earlier, use the path as route name
|
// If we hadn't matched any segments earlier, use the path as route name
|
||||||
if (routeNames === undefined) {
|
if (routeNames === undefined) {
|
||||||
@@ -123,35 +180,11 @@ export default function getStateFromPath(
|
|||||||
remaining = segments.join('/');
|
remaining = segments.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
let state: InitialState;
|
const state = createNestedStateObject(
|
||||||
let routeName = routeNames.shift() as string;
|
createRouteObjects(configs, routeNames, allParams),
|
||||||
let initialRoute = findInitialRoute(routeName, initialRoutes);
|
initialRoutes
|
||||||
|
|
||||||
state = createNestedState(
|
|
||||||
initialRoute,
|
|
||||||
routeName,
|
|
||||||
routeNames.length === 0,
|
|
||||||
params
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (routeNames.length > 0) {
|
|
||||||
let nestedState = state;
|
|
||||||
|
|
||||||
while ((routeName = routeNames.shift() as string)) {
|
|
||||||
initialRoute = findInitialRoute(routeName, initialRoutes);
|
|
||||||
nestedState.routes[nestedState.index || 0].state = createNestedState(
|
|
||||||
initialRoute,
|
|
||||||
routeName,
|
|
||||||
routeNames.length === 0,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
if (routeNames.length > 0) {
|
|
||||||
nestedState = nestedState.routes[nestedState.index || 0]
|
|
||||||
.state as InitialState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
// The state should be nested inside the deepest route we parsed before
|
// The state should be nested inside the deepest route we parsed before
|
||||||
while (current?.routes[current.index || 0].state) {
|
while (current?.routes[current.index || 0].state) {
|
||||||
@@ -172,74 +205,118 @@ export default function getStateFromPath(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = path.split('?')[1];
|
const route = findFocusedRoute(current);
|
||||||
|
const params = parseQueryParams(
|
||||||
if (query) {
|
path,
|
||||||
while (current?.routes[current.index || 0].state) {
|
findParseConfigForRoute(route.name, configs)
|
||||||
// The query params apply to the deepest route
|
);
|
||||||
current = current.routes[current.index || 0].state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = (current as PartialState<NavigationState>).routes[
|
|
||||||
current?.index || 0
|
|
||||||
];
|
|
||||||
|
|
||||||
const params = queryString.parse(query);
|
|
||||||
const parseFunction = findParseConfigForRoute(route.name, configs);
|
|
||||||
|
|
||||||
if (parseFunction) {
|
|
||||||
Object.keys(params).forEach(name => {
|
|
||||||
if (parseFunction[name] && typeof params[name] === 'string') {
|
|
||||||
params[name] = parseFunction[name](params[name] as string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (params) {
|
||||||
route.params = { ...route.params, ...params };
|
route.params = { ...route.params, ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNormalizedConfigs(
|
const joinPaths = (...paths: string[]): string =>
|
||||||
key: string,
|
([] as string[])
|
||||||
routeConfig: Options,
|
.concat(...paths.map((p) => p.split('/')))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
|
||||||
|
const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
||||||
|
let routeNames: string[] | undefined;
|
||||||
|
let allParams: Record<string, any> | undefined;
|
||||||
|
let remainingPath = remaining;
|
||||||
|
|
||||||
|
// Go through all configs, and see if the next path segment matches our regex
|
||||||
|
for (const config of configs) {
|
||||||
|
if (!config.regex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = remainingPath.match(config.regex);
|
||||||
|
|
||||||
|
// If our regex matches, we need to extract params from the path
|
||||||
|
if (match) {
|
||||||
|
routeNames = [...config.routeNames];
|
||||||
|
|
||||||
|
const paramPatterns = config.pattern
|
||||||
|
.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'));
|
||||||
|
|
||||||
|
if (paramPatterns.length) {
|
||||||
|
allParams = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||||
|
const value = match![(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
|
||||||
|
|
||||||
|
acc[p] = value;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingPath = remainingPath.replace(match[1], '');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { routeNames, allParams, remainingPath };
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNormalizedConfigs = (
|
||||||
|
screen: string,
|
||||||
|
routeConfig: PathConfig,
|
||||||
routeNames: string[] = [],
|
routeNames: string[] = [],
|
||||||
initials: InitialRouteConfig[]
|
initials: InitialRouteConfig[],
|
||||||
): RouteConfig[] {
|
parentPattern?: string
|
||||||
|
): RouteConfig[] => {
|
||||||
const configs: RouteConfig[] = [];
|
const configs: RouteConfig[] = [];
|
||||||
|
|
||||||
routeNames.push(key);
|
routeNames.push(screen);
|
||||||
|
|
||||||
const value = routeConfig[key];
|
const config = routeConfig[screen];
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof config === 'string') {
|
||||||
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||||
if (value !== '') {
|
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||||
configs.push(createConfigItem(routeNames, value));
|
|
||||||
}
|
configs.push(createConfigItem(screen, routeNames, pattern, config));
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof config === 'object') {
|
||||||
|
let pattern: string | undefined;
|
||||||
|
|
||||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||||
// it can have `path` property and
|
// it can have `path` property and
|
||||||
// it could have `screens` prop which has nested configs
|
// it could have `screens` prop which has nested configs
|
||||||
if (value.path && value.path !== '') {
|
if (typeof config.path === 'string') {
|
||||||
configs.push(createConfigItem(routeNames, value.path, value.parse));
|
pattern =
|
||||||
|
config.exact !== true && parentPattern
|
||||||
|
? joinPaths(parentPattern, config.path)
|
||||||
|
: config.path;
|
||||||
|
|
||||||
|
configs.push(
|
||||||
|
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (value.screens) {
|
|
||||||
|
if (config.screens) {
|
||||||
// property `initialRouteName` without `screens` has no purpose
|
// property `initialRouteName` without `screens` has no purpose
|
||||||
if (value.initialRouteName) {
|
if (config.initialRouteName) {
|
||||||
initials.push({
|
initials.push({
|
||||||
initialRouteName: value.initialRouteName,
|
initialRouteName: config.initialRouteName,
|
||||||
connectedRoutes: Object.keys(value.screens),
|
connectedRoutes: Object.keys(config.screens),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.keys(value.screens).forEach(nestedConfig => {
|
|
||||||
|
Object.keys(config.screens).forEach((nestedConfig) => {
|
||||||
const result = createNormalizedConfigs(
|
const result = createNormalizedConfigs(
|
||||||
nestedConfig,
|
nestedConfig,
|
||||||
value.screens as Options,
|
config.screens as PathConfig,
|
||||||
routeNames,
|
routeNames,
|
||||||
initials
|
initials,
|
||||||
|
pattern
|
||||||
);
|
);
|
||||||
|
|
||||||
configs.push(...result);
|
configs.push(...result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -248,43 +325,62 @@ function createNormalizedConfigs(
|
|||||||
routeNames.pop();
|
routeNames.pop();
|
||||||
|
|
||||||
return configs;
|
return configs;
|
||||||
}
|
};
|
||||||
|
|
||||||
function createConfigItem(
|
const createConfigItem = (
|
||||||
|
screen: string,
|
||||||
routeNames: string[],
|
routeNames: string[],
|
||||||
pattern: string,
|
pattern: string,
|
||||||
|
path: string,
|
||||||
parse?: ParseConfig
|
parse?: ParseConfig
|
||||||
): RouteConfig {
|
): RouteConfig => {
|
||||||
const match = new RegExp(
|
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
|
||||||
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
|
pattern = pattern.split('/').filter(Boolean).join('/');
|
||||||
);
|
|
||||||
|
const regex = pattern
|
||||||
|
? new RegExp(
|
||||||
|
`^(${pattern
|
||||||
|
.split('/')
|
||||||
|
.map((it) => {
|
||||||
|
if (it.startsWith(':')) {
|
||||||
|
return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${it === '*' ? '.*' : escape(it)}\\/`;
|
||||||
|
})
|
||||||
|
.join('')})`
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
match,
|
screen,
|
||||||
|
regex,
|
||||||
pattern,
|
pattern,
|
||||||
|
path,
|
||||||
// The routeNames array is mutated, so copy it to keep the current state
|
// The routeNames array is mutated, so copy it to keep the current state
|
||||||
routeNames: [...routeNames],
|
routeNames: [...routeNames],
|
||||||
parse,
|
parse,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
function findParseConfigForRoute(
|
const findParseConfigForRoute = (
|
||||||
routeName: string,
|
routeName: string,
|
||||||
flatConfig: RouteConfig[]
|
flatConfig: RouteConfig[]
|
||||||
): ParseConfig | undefined {
|
): ParseConfig | undefined => {
|
||||||
for (const config of flatConfig) {
|
for (const config of flatConfig) {
|
||||||
if (routeName === config.routeNames[config.routeNames.length - 1]) {
|
if (routeName === config.routeNames[config.routeNames.length - 1]) {
|
||||||
return config.parse;
|
return config.parse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tries to find an initial route connected with the one passed
|
return undefined;
|
||||||
function findInitialRoute(
|
};
|
||||||
|
|
||||||
|
// Try to find an initial route connected with the one passed
|
||||||
|
const findInitialRoute = (
|
||||||
routeName: string,
|
routeName: string,
|
||||||
initialRoutes: InitialRouteConfig[]
|
initialRoutes: InitialRouteConfig[]
|
||||||
): string | undefined {
|
): string | undefined => {
|
||||||
for (const config of initialRoutes) {
|
for (const config of initialRoutes) {
|
||||||
if (config.connectedRoutes.includes(routeName)) {
|
if (config.connectedRoutes.includes(routeName)) {
|
||||||
return config.initialRouteName === routeName
|
return config.initialRouteName === routeName
|
||||||
@@ -293,28 +389,25 @@ function findInitialRoute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
};
|
||||||
|
|
||||||
// returns nested state object with values depending on whether
|
// returns state object with values depending on whether
|
||||||
// it is the end of state and if there is initialRoute for this level
|
// it is the end of state and if there is initialRoute for this level
|
||||||
function createNestedState(
|
const createStateObject = (
|
||||||
initialRoute: string | undefined,
|
initialRoute: string | undefined,
|
||||||
routeName: string,
|
routeName: string,
|
||||||
isEmpty: boolean,
|
params: Record<string, any> | undefined,
|
||||||
params?: Record<string, any> | undefined
|
isEmpty: boolean
|
||||||
): InitialState {
|
): InitialState => {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
if (initialRoute) {
|
if (initialRoute) {
|
||||||
return {
|
return {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [{ name: initialRoute }, { name: routeName as string, params }],
|
||||||
{ name: initialRoute },
|
|
||||||
{ name: routeName as string, ...(params && { params }) },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
routes: [{ name: routeName as string, ...(params && { params }) }],
|
routes: [{ name: routeName as string, params }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -323,11 +416,130 @@ function createNestedState(
|
|||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [
|
||||||
{ name: initialRoute },
|
{ name: initialRoute },
|
||||||
{ name: routeName as string, state: { routes: [] } },
|
{ name: routeName as string, params, state: { routes: [] } },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { routes: [{ name: routeName as string, state: { routes: [] } }] };
|
return {
|
||||||
|
routes: [{ name: routeName as string, params, state: { routes: [] } }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const createNestedStateObject = (
|
||||||
|
routes: { name: string; params?: object }[],
|
||||||
|
initialRoutes: InitialRouteConfig[]
|
||||||
|
) => {
|
||||||
|
let state: InitialState;
|
||||||
|
let route = routes.shift() as { name: string; params?: object };
|
||||||
|
let initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
|
|
||||||
|
state = createStateObject(
|
||||||
|
initialRoute,
|
||||||
|
route.name,
|
||||||
|
route.params,
|
||||||
|
routes.length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routes.length > 0) {
|
||||||
|
let nestedState = state;
|
||||||
|
|
||||||
|
while ((route = routes.shift() as { name: string; params?: object })) {
|
||||||
|
initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
|
|
||||||
|
const nestedStateIndex =
|
||||||
|
nestedState.index || nestedState.routes.length - 1;
|
||||||
|
|
||||||
|
nestedState.routes[nestedStateIndex].state = createStateObject(
|
||||||
|
initialRoute,
|
||||||
|
route.name,
|
||||||
|
route.params,
|
||||||
|
routes.length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routes.length > 0) {
|
||||||
|
nestedState = nestedState.routes[nestedStateIndex]
|
||||||
|
.state as InitialState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRouteObjects = (
|
||||||
|
configs: RouteConfig[],
|
||||||
|
routeNames: string[],
|
||||||
|
allParams?: Record<string, any>
|
||||||
|
) =>
|
||||||
|
routeNames.map((name) => {
|
||||||
|
const config = configs.find((c) => c.screen === name);
|
||||||
|
|
||||||
|
let params: object | undefined;
|
||||||
|
|
||||||
|
if (allParams && config?.path) {
|
||||||
|
const pattern = config.path;
|
||||||
|
|
||||||
|
if (pattern) {
|
||||||
|
const paramPatterns = pattern
|
||||||
|
.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'));
|
||||||
|
|
||||||
|
if (paramPatterns.length) {
|
||||||
|
params = paramPatterns.reduce<Record<string, any>>((acc, p) => {
|
||||||
|
const key = p.replace(/^:/, '').replace(/\?$/, '');
|
||||||
|
const value = allParams![p];
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
acc[key] =
|
||||||
|
config.parse && config.parse[key]
|
||||||
|
? config.parse[key](value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params && Object.keys(params).length) {
|
||||||
|
return { name, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name };
|
||||||
|
});
|
||||||
|
|
||||||
|
const findFocusedRoute = (state: InitialState) => {
|
||||||
|
let current: InitialState | undefined = state;
|
||||||
|
|
||||||
|
while (current?.routes[current.index || 0].state) {
|
||||||
|
// The query params apply to the deepest route
|
||||||
|
current = current.routes[current.index || 0].state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = (current as PartialState<NavigationState>).routes[
|
||||||
|
current?.index || 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseQueryParams = (
|
||||||
|
path: string,
|
||||||
|
parseConfig?: Record<string, (value: string) => any>
|
||||||
|
) => {
|
||||||
|
const query = path.split('?')[1];
|
||||||
|
const params = queryString.parse(query);
|
||||||
|
|
||||||
|
if (parseConfig) {
|
||||||
|
Object.keys(params).forEach((name) => {
|
||||||
|
if (parseConfig[name] && typeof params[name] === 'string') {
|
||||||
|
params[name] = parseConfig[name](params[name] as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(params).length ? params : undefined;
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ export * from '@react-navigation/routers';
|
|||||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||||
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
||||||
|
|
||||||
|
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
||||||
export { default as NavigationContext } from './NavigationContext';
|
export { default as NavigationContext } from './NavigationContext';
|
||||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||||
|
|
||||||
|
export { default as CurrentRenderContext } from './CurrentRenderContext';
|
||||||
|
|
||||||
export { default as useNavigationBuilder } from './useNavigationBuilder';
|
export { default as useNavigationBuilder } from './useNavigationBuilder';
|
||||||
export { default as useNavigation } from './useNavigation';
|
export { default as useNavigation } from './useNavigation';
|
||||||
export { default as useRoute } from './useRoute';
|
export { default as useRoute } from './useRoute';
|
||||||
|
|||||||
7
packages/core/src/isArrayEqual.tsx
Normal file
7
packages/core/src/isArrayEqual.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Compare two arrays with primitive values as the content.
|
||||||
|
* We need to make sure that both values and order match.
|
||||||
|
*/
|
||||||
|
export default function isArrayEqual(a: any[], b: any[]) {
|
||||||
|
return a.length === b.length && a.every((it, index) => it === b[index]);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user