mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 17:32:55 +08:00
Compare commits
127 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
220af93db5 | ||
|
|
1f27e4b1f6 | ||
|
|
9c06a92d09 | ||
|
|
e0e0f79793 | ||
|
|
c7e4bf94e6 | ||
|
|
7024d4bb81 | ||
|
|
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 |
@@ -1,58 +1,97 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/project
|
||||
executors:
|
||||
default:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/project
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||
|
||||
commands:
|
||||
attach_project:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
|
||||
jobs:
|
||||
install-dependencies:
|
||||
<<: *defaults
|
||||
executor: default
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- attach_project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
- v1-dependencies-
|
||||
- run: yarn install --frozen-lockfile
|
||||
- yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- yarn-packages-v1-{{ .Branch }}-
|
||||
- yarn-packages-v1-
|
||||
- run:
|
||||
name: Install project dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
paths: node_modules
|
||||
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths: ~/.cache/yarn
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: .
|
||||
|
||||
lint-and-typecheck:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lint
|
||||
yarn typescript
|
||||
executor: default
|
||||
steps:
|
||||
- attach_project
|
||||
- run:
|
||||
name: Lint files
|
||||
command: yarn lint
|
||||
- run:
|
||||
name: Typecheck files
|
||||
command: yarn typescript
|
||||
|
||||
unit-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn test --coverage
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
destination: coverage
|
||||
executor: default
|
||||
steps:
|
||||
- attach_project
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn test --coverage
|
||||
- run:
|
||||
name: Upload test coverage
|
||||
command: cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
||||
- 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:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lerna run prepare
|
||||
node scripts/check-types-path.js
|
||||
executor: default
|
||||
steps:
|
||||
- attach_project
|
||||
- run:
|
||||
name: Build packages in the monorepo
|
||||
command: yarn lerna run prepare
|
||||
- run:
|
||||
name: Verify paths for types
|
||||
command: node scripts/check-types-path.js
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-and-test:
|
||||
jobs:
|
||||
- install-dependencies
|
||||
@@ -62,6 +101,9 @@ workflows:
|
||||
- unit-tests:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- integration-tests:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- build-packages:
|
||||
requires:
|
||||
- install-dependencies
|
||||
|
||||
14
.github/workflows/expo-preview.yml
vendored
14
.github/workflows/expo-preview.yml
vendored
@@ -23,20 +23,16 @@ jobs:
|
||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||
expo-cache: true
|
||||
|
||||
- name: Get yarn cache
|
||||
- name: Restore yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Check yarn cache
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Publish Expo app
|
||||
working-directory: ./example
|
||||
|
||||
13
.github/workflows/expo.yml
vendored
13
.github/workflows/expo.yml
vendored
@@ -25,19 +25,16 @@ jobs:
|
||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||
expo-cache: true
|
||||
|
||||
- name: Get yarn cache
|
||||
- name: Restore yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v1
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Publish Expo app
|
||||
working-directory: ./example
|
||||
|
||||
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,6 +1,10 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { Assets as StackAssets } from '@react-navigation/stack';
|
||||
|
||||
import App from './src/index';
|
||||
|
||||
Asset.loadAsync(StackAssets);
|
||||
|
||||
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",
|
||||
"description": "Demo app to showcase various functionality of React Navigation",
|
||||
"privacy": "public",
|
||||
"sdkVersion": "37.0.0",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android",
|
||||
"web"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
@@ -20,15 +14,16 @@
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"sdkVersion": "37.0.0",
|
||||
"platforms": ["ios", "android", "web"],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"scheme": "rne",
|
||||
"entryPoint": "App.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 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:
|
||||
- boost-for-react-native (1.63.0)
|
||||
- DoubleConversion (1.1.6)
|
||||
- EXAppLoaderProvider (8.0.0)
|
||||
- EXBlur (8.0.0):
|
||||
- EXBlur (8.1.0):
|
||||
- UMCore
|
||||
- EXConstants (8.0.0):
|
||||
- EXConstants (9.0.0):
|
||||
- UMConstantsInterface
|
||||
- UMCore
|
||||
- EXErrorRecovery (1.0.0):
|
||||
- EXErrorRecovery (1.1.0):
|
||||
- UMCore
|
||||
- EXFileSystem (8.0.0):
|
||||
- EXFileSystem (8.1.0):
|
||||
- UMCore
|
||||
- UMFileSystemInterface
|
||||
- EXFont (8.0.0):
|
||||
- EXFont (8.1.0):
|
||||
- UMCore
|
||||
- UMFontInterface
|
||||
- EXKeepAwake (8.0.0):
|
||||
- EXImageLoader (1.0.1):
|
||||
- React-Core
|
||||
- UMCore
|
||||
- EXLinearGradient (8.0.0):
|
||||
- UMImageLoaderInterface
|
||||
- EXKeepAwake (8.1.0):
|
||||
- UMCore
|
||||
- EXLocation (8.0.0):
|
||||
- EXLinearGradient (8.1.0):
|
||||
- UMCore
|
||||
- EXLocation (8.1.0):
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
- UMTaskManagerInterface
|
||||
- EXPermissions (8.0.0):
|
||||
- EXPermissions (8.1.0):
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
- EXSQLite (8.0.0):
|
||||
- EXSQLite (8.1.0):
|
||||
- UMCore
|
||||
- UMFileSystemInterface
|
||||
- EXWebBrowser (8.0.0):
|
||||
- EXWebBrowser (8.2.1):
|
||||
- UMCore
|
||||
- FBLazyVector (0.61.5)
|
||||
- FBReactNativeSpec (0.61.5):
|
||||
@@ -50,8 +53,6 @@ PODS:
|
||||
- glog
|
||||
- glog (0.3.5)
|
||||
- RCTRequired (0.61.5)
|
||||
- RCTRestart (0.0.13):
|
||||
- React
|
||||
- RCTTypeSafety (0.61.5):
|
||||
- FBLazyVector (= 0.61.5)
|
||||
- Folly (= 2018.10.22.00)
|
||||
@@ -214,7 +215,9 @@ PODS:
|
||||
- React-cxxreact (= 0.61.5)
|
||||
- React-jsi (= 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-RCTActionSheet (0.61.5):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.61.5)
|
||||
@@ -251,39 +254,41 @@ PODS:
|
||||
- React-cxxreact (= 0.61.5)
|
||||
- React-jsi (= 0.61.5)
|
||||
- ReactCommon/jscallinvoker (= 0.61.5)
|
||||
- RNCMaskedView (0.1.5):
|
||||
- RNCMaskedView (0.1.10):
|
||||
- React
|
||||
- RNGestureHandler (1.5.5):
|
||||
- RNGestureHandler (1.6.1):
|
||||
- React
|
||||
- RNReanimated (1.4.0):
|
||||
- RNReanimated (1.8.0):
|
||||
- React
|
||||
- RNScreens (2.0.0-alpha.33):
|
||||
- RNScreens (2.7.0):
|
||||
- React
|
||||
- UMBarCodeScannerInterface (5.0.0)
|
||||
- UMCameraInterface (5.0.0)
|
||||
- UMConstantsInterface (5.0.0)
|
||||
- UMCore (5.0.0)
|
||||
- UMFaceDetectorInterface (5.0.0)
|
||||
- UMFileSystemInterface (5.0.0)
|
||||
- UMFontInterface (5.0.0)
|
||||
- UMImageLoaderInterface (5.0.0)
|
||||
- UMPermissionsInterface (5.0.0)
|
||||
- UMReactNativeAdapter (5.0.0):
|
||||
- UMAppLoader (1.0.2)
|
||||
- UMBarCodeScannerInterface (5.1.0)
|
||||
- UMCameraInterface (5.1.0)
|
||||
- UMConstantsInterface (5.1.0)
|
||||
- UMCore (5.1.2)
|
||||
- UMFaceDetectorInterface (5.1.0)
|
||||
- UMFileSystemInterface (5.1.0)
|
||||
- UMFontInterface (5.1.0)
|
||||
- UMImageLoaderInterface (5.1.0)
|
||||
- UMPermissionsInterface (5.1.0):
|
||||
- UMCore
|
||||
- UMReactNativeAdapter (5.2.0):
|
||||
- React-Core
|
||||
- UMCore
|
||||
- UMFontInterface
|
||||
- UMSensorsInterface (5.0.0)
|
||||
- UMTaskManagerInterface (5.0.0)
|
||||
- UMSensorsInterface (5.1.0)
|
||||
- UMTaskManagerInterface (5.1.0)
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- 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`)
|
||||
- EXConstants (from `../../node_modules/expo-constants/ios`)
|
||||
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
|
||||
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
|
||||
- EXFont (from `../../node_modules/expo-font/ios`)
|
||||
- EXImageLoader (from `../../node_modules/expo-image-loader/ios`)
|
||||
- EXKeepAwake (from `../../node_modules/expo-keep-awake/ios`)
|
||||
- EXLinearGradient (from `../../node_modules/expo-linear-gradient/ios`)
|
||||
- EXLocation (from `../../node_modules/expo-location/ios`)
|
||||
@@ -295,7 +300,6 @@ DEPENDENCIES:
|
||||
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
|
||||
- RCTRestart (from `../../node_modules/react-native-restart/ios`)
|
||||
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
|
||||
- React (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-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||
- 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-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||
- React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`)
|
||||
@@ -322,10 +327,11 @@ DEPENDENCIES:
|
||||
- RNGestureHandler (from `../../node_modules/react-native-gesture-handler`)
|
||||
- RNReanimated (from `../../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../../node_modules/react-native-screens`)
|
||||
- UMAppLoader (from `../../node_modules/unimodules-app-loader/ios`)
|
||||
- UMBarCodeScannerInterface (from `../../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||
- UMCameraInterface (from `../../node_modules/unimodules-camera-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`)
|
||||
- UMFileSystemInterface (from `../../node_modules/unimodules-file-system-interface/ios`)
|
||||
- UMFontInterface (from `../../node_modules/unimodules-font-interface/ios`)
|
||||
@@ -343,42 +349,30 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
DoubleConversion:
|
||||
:podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
EXAppLoaderProvider:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-app-loader-provider/ios"
|
||||
EXBlur:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-blur/ios"
|
||||
:path: "../../node_modules/expo-blur/ios"
|
||||
EXConstants:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-constants/ios"
|
||||
:path: "../../node_modules/expo-constants/ios"
|
||||
EXErrorRecovery:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-error-recovery/ios"
|
||||
:path: "../../node_modules/expo-error-recovery/ios"
|
||||
EXFileSystem:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-file-system/ios"
|
||||
:path: "../../node_modules/expo-file-system/ios"
|
||||
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:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-keep-awake/ios"
|
||||
:path: "../../node_modules/expo-keep-awake/ios"
|
||||
EXLinearGradient:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-linear-gradient/ios"
|
||||
:path: "../../node_modules/expo-linear-gradient/ios"
|
||||
EXLocation:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-location/ios"
|
||||
:path: "../../node_modules/expo-location/ios"
|
||||
EXPermissions:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-permissions/ios"
|
||||
:path: "../../node_modules/expo-permissions/ios"
|
||||
EXSQLite:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-sqlite/ios"
|
||||
:path: "../../node_modules/expo-sqlite/ios"
|
||||
EXWebBrowser:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/expo-web-browser/ios"
|
||||
:path: "../../node_modules/expo-web-browser/ios"
|
||||
FBLazyVector:
|
||||
:path: "../../node_modules/react-native/Libraries/FBLazyVector"
|
||||
FBReactNativeSpec:
|
||||
@@ -389,8 +383,6 @@ EXTERNAL SOURCES:
|
||||
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||
RCTRequired:
|
||||
:path: "../../node_modules/react-native/Libraries/RCTRequired"
|
||||
RCTRestart:
|
||||
:path: "../../node_modules/react-native-restart/ios"
|
||||
RCTTypeSafety:
|
||||
:path: "../../node_modules/react-native/Libraries/TypeSafety"
|
||||
React:
|
||||
@@ -407,6 +399,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native/ReactCommon/jsiexecutor"
|
||||
React-jsinspector:
|
||||
:path: "../../node_modules/react-native/ReactCommon/jsinspector"
|
||||
react-native-restart:
|
||||
:path: "../../node_modules/react-native-restart"
|
||||
react-native-safe-area-context:
|
||||
:path: "../../node_modules/react-native-safe-area-context"
|
||||
React-RCTActionSheet:
|
||||
@@ -437,66 +431,55 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-reanimated"
|
||||
RNScreens:
|
||||
:path: "../../node_modules/react-native-screens"
|
||||
UMAppLoader:
|
||||
:path: "../../node_modules/unimodules-app-loader/ios"
|
||||
UMBarCodeScannerInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||
:path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||
UMCameraInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-camera-interface/ios"
|
||||
:path: "../../node_modules/unimodules-camera-interface/ios"
|
||||
UMConstantsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-constants-interface/ios"
|
||||
:path: "../../node_modules/unimodules-constants-interface/ios"
|
||||
UMCore:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/@unimodules/core/ios"
|
||||
:path: "../../node_modules/react-native-unimodules/node_modules/@unimodules/core/ios"
|
||||
UMFaceDetectorInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-face-detector-interface/ios"
|
||||
:path: "../../node_modules/unimodules-face-detector-interface/ios"
|
||||
UMFileSystemInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-file-system-interface/ios"
|
||||
:path: "../../node_modules/unimodules-file-system-interface/ios"
|
||||
UMFontInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-font-interface/ios"
|
||||
:path: "../../node_modules/unimodules-font-interface/ios"
|
||||
UMImageLoaderInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-image-loader-interface/ios"
|
||||
:path: "../../node_modules/unimodules-image-loader-interface/ios"
|
||||
UMPermissionsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-permissions-interface/ios"
|
||||
:path: "../../node_modules/unimodules-permissions-interface/ios"
|
||||
UMReactNativeAdapter:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/@unimodules/react-native-adapter/ios"
|
||||
:path: "../../node_modules/@unimodules/react-native-adapter/ios"
|
||||
UMSensorsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-sensors-interface/ios"
|
||||
:path: "../../node_modules/unimodules-sensors-interface/ios"
|
||||
UMTaskManagerInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../../node_modules/unimodules-task-manager-interface/ios"
|
||||
:path: "../../node_modules/unimodules-task-manager-interface/ios"
|
||||
Yoga:
|
||||
:path: "../../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49
|
||||
EXBlur: d1604f66f89a9414f5ee65dfb23874437c1bb147
|
||||
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146
|
||||
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2
|
||||
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6
|
||||
EXFont: 6187b5ab46ee578d5f8e7f2ea092752e78772235
|
||||
EXKeepAwake: 66e9f80b6d129633725a0e42f8d285c229876811
|
||||
EXLinearGradient: 75f302f9d6484267a3f6d3252df2e7a5f00e716a
|
||||
EXLocation: 3c75d012ca92eed94d4338778d79c49d1252393a
|
||||
EXPermissions: 9bc08859a675d291e89be9a0870155c27c16ac35
|
||||
EXSQLite: 220226a354912b100dfe913f5fe6f31762c8927e
|
||||
EXWebBrowser: db32607359fb7b55b7b7b91df32dd3d8355bb3b7
|
||||
EXBlur: aa14d84bff6e9c2232fbcaf54ad809eee1cc41dc
|
||||
EXConstants: 5304709b1bea70a4828f48ba4c7fc3ec3b2d9b17
|
||||
EXErrorRecovery: 8f4c21ab2f51bf75defe4536f841a37de59b0661
|
||||
EXFileSystem: cf4232ba7c62dc49b78c2d36005f97b6fddf0b01
|
||||
EXFont: 8326ecf966be559f7ced7c8e221a32fc4d9ed8b0
|
||||
EXImageLoader: 5ad6896fa1ef2ee814b551873cbf7a7baccc694a
|
||||
EXKeepAwake: d045bc2cf1ad5a04f0323cc7c894b95b414042e0
|
||||
EXLinearGradient: 97d8095d1e4ad96f7893e010e564796ed8aeea42
|
||||
EXLocation: bbd487fd96a18a3ad9725389bbb94c4a5f78edf3
|
||||
EXPermissions: 24b97f734ce9172d245a5be38ad9ccfcb6135964
|
||||
EXSQLite: 877ad6c8eb169353a2f94d5ad26510ffadd46a1f
|
||||
EXWebBrowser: 5902f99ac5ac551e5c82ff46f13a337b323aa9ea
|
||||
FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
|
||||
FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
|
||||
RCTRestart: dd19aab87fc1118e05b6b5b91b959105647f56b4
|
||||
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
|
||||
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
|
||||
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
|
||||
@@ -505,7 +488,8 @@ SPEC CHECKSUMS:
|
||||
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
|
||||
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
|
||||
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
|
||||
react-native-safe-area-context: 25260c5d0b9c53fd7aa88e569e2edae72af1f6a3
|
||||
react-native-restart: fff228304625f55de2ebd4de43938110f4c888ed
|
||||
react-native-safe-area-context: a346c75f2288147527365ce27b59ca6d38c27805
|
||||
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
||||
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
||||
React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
|
||||
@@ -516,24 +500,25 @@ SPEC CHECKSUMS:
|
||||
React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
|
||||
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
|
||||
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
|
||||
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
|
||||
RNGestureHandler: d2270608171c868581b840cfc692f2962c05cd17
|
||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
||||
RNScreens: 1c7fd499b915c77c21e8e6c327890c5af9b4cf7e
|
||||
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
|
||||
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
|
||||
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c
|
||||
UMCore: 7ab08669a8bb2e61f557c1fe9784521cb5aa28e3
|
||||
UMFaceDetectorInterface: ce14e8e597f6a52aa66e4ab956cb5bff4fa8acf8
|
||||
UMFileSystemInterface: 2ed004c9620f43f0b36b33c42ce668500850d6a4
|
||||
UMFontInterface: 24fbc0a02ade6c60ad3ee3e2b5d597c8dcfc3208
|
||||
UMImageLoaderInterface: 3976a14c588341228881ff75970fbabf122efca4
|
||||
UMPermissionsInterface: 2abf9f7f4aa7110e27beaf634a7deda2d50ff3d7
|
||||
UMReactNativeAdapter: 230406e3335a8dbd4c9c0e654488a1cf3b44552f
|
||||
UMSensorsInterface: d708a892ef1500bdd9fc3ff03f7836c66d1634d3
|
||||
UMTaskManagerInterface: a98e37a576a5220bf43b8faf33cfdc129d2f441d
|
||||
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
|
||||
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
|
||||
RNReanimated: 955cf4068714003d2f1a6e2bae3fb1118f359aff
|
||||
RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706
|
||||
UMAppLoader: ee77a072f9e15128f777ccd6d2d00f52ab4387e6
|
||||
UMBarCodeScannerInterface: 9dc692b87e5f20fe277fa57aa47f45d418c3cc6c
|
||||
UMCameraInterface: 625878bbf2ba188a8548675e1d1d2e438a653e6d
|
||||
UMConstantsInterface: 64060cf86587bcd90b1dbd804cceb6d377a308c1
|
||||
UMCore: eb200e882eadafcd31ead290770835fd648c0945
|
||||
UMFaceDetectorInterface: d6677d6ddc9ab95a0ca857aa7f8ba76656cc770f
|
||||
UMFileSystemInterface: c70ea7147198b9807080f3597f26236be49b0165
|
||||
UMFontInterface: d9d3b27af698c5389ae9e20b99ef56a083f491fb
|
||||
UMImageLoaderInterface: 14dd2c46c67167491effc9e91250e9510f12709e
|
||||
UMPermissionsInterface: 5e83a9167c177e4a0f0a3539345983cc749efb3e
|
||||
UMReactNativeAdapter: 126da3486c1a1f11945b649d557d6c2ebb9407b2
|
||||
UMSensorsInterface: 48941f70175e2975af1a9386c6d6cb16d8126805
|
||||
UMTaskManagerInterface: cb890c79c63885504ddc0efd7a7d01481760aca2
|
||||
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
|
||||
|
||||
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 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
|
||||
const modules = ['@expo/vector-icons']
|
||||
.concat(
|
||||
...fs
|
||||
// List all packages under `packages/`
|
||||
.readdirSync(packages)
|
||||
// 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')
|
||||
);
|
||||
...workspaces.map((it) => {
|
||||
const pak = JSON.parse(
|
||||
fs.readFileSync(path.join(it, 'package.json'), 'utf8')
|
||||
);
|
||||
|
||||
// We need to collect list of deps that this package imports
|
||||
// Collecting both dependencies are peerDependencies should do it
|
||||
return Object.keys({
|
||||
...pak.dependencies,
|
||||
...pak.peerDependencies,
|
||||
});
|
||||
})
|
||||
// We need to make sure that only one version is loaded for peerDependencies
|
||||
// So we blacklist them at the root, and alias them to the versions in example's node_modules
|
||||
return pak.peerDependencies ? Object.keys(pak.peerDependencies) : [];
|
||||
})
|
||||
)
|
||||
.sort()
|
||||
.filter(
|
||||
@@ -45,15 +44,16 @@ module.exports = {
|
||||
watchFolders: [root],
|
||||
|
||||
resolver: {
|
||||
// We need to blacklist `node_modules` of all our packages
|
||||
// This will avoid Metro throwing duplicate module errors
|
||||
// We need to blacklist the peerDependencies we've collected in packages' node_modules
|
||||
blacklistRE: blacklist(
|
||||
fs
|
||||
.readdirSync(packages)
|
||||
.map((p) => path.join(packages, p))
|
||||
.map(
|
||||
(it) => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
||||
[].concat(
|
||||
...workspaces.map((it) =>
|
||||
modules.map(
|
||||
(m) =>
|
||||
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
|
||||
|
||||
@@ -1,41 +1,60 @@
|
||||
{
|
||||
"name": "@react-navigation/example",
|
||||
"description": "Demo app to showcase various functionality of React Navigation",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"web": "expo start:web",
|
||||
"native": "react-native start",
|
||||
"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": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-community/masked-view": "^0.1.7",
|
||||
"@expo/vector-icons": "^10.2.0",
|
||||
"@react-native-community/masked-view": "^0.1.10",
|
||||
"color": "^3.1.2",
|
||||
"expo": "^37.0.0",
|
||||
"expo": "^37.0.8",
|
||||
"expo-asset": "~8.1.3",
|
||||
"expo-blur": "~8.1.0",
|
||||
"koa": "^2.12.0",
|
||||
"react": "~16.9.0",
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-paper": "^3.7.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-restart": "^0.0.14",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-paper": "^3.10.1",
|
||||
"react-native-reanimated": "^1.8.0",
|
||||
"react-native-restart": "^0.0.15",
|
||||
"react-native-safe-area-context": "^1.0.0",
|
||||
"react-native-screens": "^2.7.0",
|
||||
"react-native-tab-view": "2.14.0",
|
||||
"react-native-unimodules": "~0.8.1",
|
||||
"react-native-unimodules": "~0.9.1",
|
||||
"react-native-vector-icons": "^6.6.0",
|
||||
"react-native-web": "^0.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/node": "^7.8.7",
|
||||
"@expo/webpack-config": "^0.11.19",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.60.22",
|
||||
"@types/cheerio": "^0.22.18",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/koa": "^2.11.3",
|
||||
"@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",
|
||||
"expo-cli": "^3.17.18",
|
||||
"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 { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
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 Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
@@ -22,24 +27,56 @@ type BottomTabParams = {
|
||||
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>();
|
||||
|
||||
export default function BottomTabsScreen() {
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
tabBarButton: (props) => <TouchableBounce {...props} />,
|
||||
tabBarButton:
|
||||
Platform.OS === 'web'
|
||||
? undefined
|
||||
: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
name="Article"
|
||||
component={SimpleStackScreen}
|
||||
options={{
|
||||
title: 'Article',
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
>
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</BottomTabs.Screen>
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
@@ -58,7 +95,7 @@ export default function BottomTabsScreen() {
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="Albums"
|
||||
component={Albums}
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
tabBarIcon: getTabBarIcon('image-album'),
|
||||
@@ -67,3 +104,13 @@ export default function BottomTabsScreen() {
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 {
|
||||
createCompatNavigatorFactory,
|
||||
@@ -11,25 +11,32 @@ import {
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
type CompatStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
Nested: { author: string };
|
||||
};
|
||||
|
||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams,
|
||||
'Article'
|
||||
type NestedStackParams = {
|
||||
Feed: undefined;
|
||||
Article: { author: string };
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Album')}
|
||||
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
Push nested
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
@@ -39,24 +46,20 @@ const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: navigation.getParam('author') }} />
|
||||
</React.Fragment>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
||||
title: `Article by ${navigation.getParam('author')}`,
|
||||
});
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
onPress={() => navigation.push('Article')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
@@ -69,22 +72,69 @@ const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums />
|
||||
</React.Fragment>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</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>
|
||||
>(
|
||||
{
|
||||
Article: {
|
||||
screen: ArticleScreen,
|
||||
Albums: AlbumsScreen,
|
||||
Nested: {
|
||||
screen: createCompatStackNavigator<
|
||||
StackNavigationProp<NestedStackParams>
|
||||
>(
|
||||
{
|
||||
Feed: FeedScreen,
|
||||
Article: ArticleScreen,
|
||||
},
|
||||
{ navigationOptions: { headerShown: false } }
|
||||
),
|
||||
params: {
|
||||
author: 'Gandalf',
|
||||
},
|
||||
},
|
||||
Album: AlbumsScreen,
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
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';
|
||||
|
||||
type BottomTabParams = {
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { Dimensions, ScaledSize } from 'react-native';
|
||||
import { Appbar } from 'react-native-paper';
|
||||
import { ParamListBase } from '@react-navigation/native';
|
||||
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';
|
||||
@@ -15,7 +21,7 @@ import NewsFeed from '../Shared/NewsFeed';
|
||||
type DrawerParams = {
|
||||
Article: undefined;
|
||||
NewsFeed: undefined;
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
|
||||
@@ -43,10 +49,11 @@ const Header = ({
|
||||
onGoBack: () => void;
|
||||
title: string;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
|
||||
return (
|
||||
<Appbar.Header>
|
||||
<Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
|
||||
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
|
||||
<Appbar.Content title={title} />
|
||||
</Appbar.Header>
|
||||
@@ -80,6 +87,23 @@ const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||
);
|
||||
};
|
||||
|
||||
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>> & {
|
||||
@@ -100,15 +124,7 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||
drawerType={isLargeScreen ? 'permanent' : 'back'}
|
||||
drawerStyle={isLargeScreen ? null : { width: '100%' }}
|
||||
overlayColor="transparent"
|
||||
drawerContent={(props) => (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
|
||||
<Appbar.Content title="Pages" />
|
||||
</Appbar.Header>
|
||||
<DrawerContent {...props} />
|
||||
</>
|
||||
)}
|
||||
drawerContent={(props) => <CustomDrawerContent {...props} />}
|
||||
{...rest}
|
||||
>
|
||||
<Drawer.Screen name="Article" component={ArticleScreen} />
|
||||
@@ -118,9 +134,9 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||
options={{ title: 'Feed' }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Album"
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
|
||||
@@ -22,14 +22,13 @@ export default function MaterialBottomTabsScreen() {
|
||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Article"
|
||||
component={SimpleStackScreen}
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: 'file-document-box',
|
||||
tabBarColor: '#C9E7F8',
|
||||
}}
|
||||
>
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</MaterialBottomTabs.Screen>
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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 Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
@@ -12,7 +14,15 @@ type 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 (
|
||||
<MaterialTopTabs.Navigator>
|
||||
<MaterialTopTabs.Screen
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -12,11 +12,13 @@ import Albums from '../Shared/Albums';
|
||||
|
||||
type ModalStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -29,7 +31,7 @@ const ArticleScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Album')}
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
@@ -42,7 +44,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -66,7 +71,7 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -86,7 +91,6 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
return (
|
||||
<ModalPresentationStack.Navigator
|
||||
mode="modal"
|
||||
headerMode="screen"
|
||||
screenOptions={({ route, navigation }) => ({
|
||||
...TransitionPresets.ModalPresentationIOS,
|
||||
cardOverlayEnabled: true,
|
||||
@@ -107,9 +111,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<ModalPresentationStack.Screen
|
||||
name="Album"
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</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 { View, StyleSheet, ScrollView } from 'react-native';
|
||||
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -13,11 +13,13 @@ import NewsFeed from '../Shared/NewsFeed';
|
||||
type SimpleStackParams = {
|
||||
Article: { author: string };
|
||||
NewsFeed: undefined;
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -43,7 +45,10 @@ const ArticleScreen = ({
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -58,7 +63,7 @@ const NewsFeedScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.navigate('Album')}
|
||||
onPress={() => navigation.navigate('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Navigate to album
|
||||
@@ -71,7 +76,7 @@ const NewsFeedScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={false} />
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -99,24 +104,24 @@ const AlbumsScreen = ({
|
||||
Pop by 2
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
||||
type Props = {
|
||||
navigation: StackNavigationProp<ParamListBase>;
|
||||
};
|
||||
|
||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
export default function SimpleStackScreen({ navigation }: Props) {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator {...rest}>
|
||||
<SimpleStack.Navigator>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
@@ -131,9 +136,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
options={{ title: 'Feed' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="Album"
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
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 { BlurView } from 'expo-blur';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useTheme, RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackNavigationProp,
|
||||
HeaderBackground,
|
||||
useHeaderHeight,
|
||||
Header,
|
||||
StackHeaderProps,
|
||||
} from '@react-navigation/stack';
|
||||
import BlurView from '../Shared/BlurView';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type SimpleStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -32,7 +43,7 @@ const ArticleScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Album')}
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
@@ -45,7 +56,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -75,7 +89,7 @@ const AlbumsScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -86,11 +100,32 @@ type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
||||
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) {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
|
||||
const { colors, dark } = useTheme();
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator {...rest}>
|
||||
<SimpleStack.Screen
|
||||
@@ -98,6 +133,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author}`,
|
||||
header: CustomHeader,
|
||||
headerTintColor: '#fff',
|
||||
headerStyle: { backgroundColor: '#ff005d' },
|
||||
headerBackTitleVisible: false,
|
||||
@@ -126,16 +162,22 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="Album"
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
title: 'Album',
|
||||
title: 'Albums',
|
||||
headerBackTitle: 'Back',
|
||||
headerTransparent: true,
|
||||
headerBackground: () => (
|
||||
<HeaderBackground style={{ backgroundColor: 'transparent' }}>
|
||||
<HeaderBackground
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.border,
|
||||
}}
|
||||
>
|
||||
<BlurView
|
||||
tint="light"
|
||||
tint={dark ? 'dark' : 'light'}
|
||||
intensity={75}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
@@ -155,4 +197,10 @@ const styles = StyleSheet.create({
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
banner: {
|
||||
textAlign: 'center',
|
||||
color: 'tomato',
|
||||
backgroundColor: 'papayawhip',
|
||||
padding: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -15,6 +15,8 @@ type SimpleStackParams = {
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -40,7 +42,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
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} />;
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
AsyncStorage,
|
||||
YellowBox,
|
||||
Platform,
|
||||
StatusBar,
|
||||
I18nManager,
|
||||
Dimensions,
|
||||
ScaledSize,
|
||||
Linking,
|
||||
} from 'react-native';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import RNRestart from 'react-native-restart';
|
||||
import { Updates } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import {
|
||||
Provider as PaperProvider,
|
||||
DefaultTheme as PaperLightTheme,
|
||||
@@ -22,14 +19,14 @@ import {
|
||||
Appbar,
|
||||
List,
|
||||
Divider,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import {
|
||||
InitialState,
|
||||
useLinking,
|
||||
NavigationContainerRef,
|
||||
NavigationContainer,
|
||||
DefaultTheme,
|
||||
DarkTheme,
|
||||
PathConfig,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
createDrawerNavigator,
|
||||
@@ -37,11 +34,12 @@ import {
|
||||
} from '@react-navigation/drawer';
|
||||
import {
|
||||
createStackNavigator,
|
||||
Assets as StackAssets,
|
||||
StackNavigationProp,
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
|
||||
import { restartApp } from './Restart';
|
||||
import AsyncStorage from './AsyncStorage';
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
@@ -51,15 +49,20 @@ import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||
import BottomTabs from './Screens/BottomTabs';
|
||||
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
|
||||
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
||||
import NotFound from './Screens/NotFound';
|
||||
import DynamicTabs from './Screens/DynamicTabs';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import MasterDetail from './Screens/MasterDetail';
|
||||
import LinkComponent from './Screens/LinkComponent';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
|
||||
enableScreens();
|
||||
|
||||
// @ts-ignore
|
||||
global.REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED = true;
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
Another: undefined;
|
||||
@@ -67,6 +70,7 @@ type RootDrawerParamList = {
|
||||
|
||||
type RootStackParamList = {
|
||||
Home: undefined;
|
||||
NotFound: undefined;
|
||||
} & {
|
||||
[P in keyof typeof SCREENS]: undefined;
|
||||
};
|
||||
@@ -110,6 +114,10 @@ const SCREENS = {
|
||||
title: 'Compat Layer',
|
||||
component: CompatAPI,
|
||||
},
|
||||
LinkComponent: {
|
||||
title: '<Link />',
|
||||
component: LinkComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const Drawer = createDrawerNavigator<RootDrawerParamList>();
|
||||
@@ -118,42 +126,10 @@ const Stack = createStackNavigator<RootStackParamList>();
|
||||
const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE';
|
||||
const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
|
||||
|
||||
Asset.loadAsync(StackAssets);
|
||||
|
||||
export default function App() {
|
||||
const containerRef = React.useRef<NavigationContainerRef>(null);
|
||||
|
||||
// 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 [isReady, setIsReady] = React.useState(false);
|
||||
const [isReady, setIsReady] = React.useState(Platform.OS === 'web');
|
||||
const [initialState, setInitialState] = React.useState<
|
||||
InitialState | undefined
|
||||
>();
|
||||
@@ -161,17 +137,18 @@ export default function App() {
|
||||
React.useEffect(() => {
|
||||
const restoreState = async () => {
|
||||
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(
|
||||
NAVIGATION_PERSISTENCE_KEY
|
||||
);
|
||||
state = savedState ? JSON.parse(savedState) : undefined;
|
||||
}
|
||||
|
||||
if (state !== undefined) {
|
||||
setInitialState(state);
|
||||
const state = savedState ? JSON.parse(savedState) : undefined;
|
||||
|
||||
if (state !== undefined) {
|
||||
setInitialState(state);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
@@ -187,7 +164,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
restoreState();
|
||||
}, [getInitialState]);
|
||||
}, []);
|
||||
|
||||
const paperTheme = React.useMemo(() => {
|
||||
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
|
||||
@@ -227,7 +204,6 @@ export default function App() {
|
||||
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
|
||||
)}
|
||||
<NavigationContainer
|
||||
ref={containerRef}
|
||||
initialState={initialState}
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage.setItem(
|
||||
@@ -236,6 +212,60 @@ export default function App() {
|
||||
)
|
||||
}
|
||||
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 drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||
<Drawer.Screen
|
||||
@@ -285,12 +315,7 @@ export default function App() {
|
||||
value={I18nManager.isRTL}
|
||||
onValueChange={() => {
|
||||
I18nManager.forceRTL(!I18nManager.isRTL);
|
||||
// @ts-ignore
|
||||
if (global.Expo) {
|
||||
Updates.reloadFromCache();
|
||||
} else {
|
||||
RNRestart.Restart();
|
||||
}
|
||||
restartApp();
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
@@ -311,6 +336,7 @@ export default function App() {
|
||||
(name) => (
|
||||
<List.Item
|
||||
key={name}
|
||||
testID={name}
|
||||
title={SCREENS[name].title}
|
||||
onPress={() => navigation.navigate(name)}
|
||||
/>
|
||||
@@ -319,6 +345,11 @@ export default function App() {
|
||||
</ScrollView>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="NotFound"
|
||||
component={NotFound}
|
||||
options={{ title: 'Oops!' }}
|
||||
/>
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
(name) => (
|
||||
<Stack.Screen
|
||||
|
||||
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
|
||||
@@ -24,19 +24,21 @@ module.exports = async function (env, argv) {
|
||||
);
|
||||
|
||||
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-web': path.resolve(node_modules, 'react-native-web'),
|
||||
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
||||
});
|
||||
|
||||
fs.readdirSync(packages).forEach((name) => {
|
||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||
packages,
|
||||
name,
|
||||
'src'
|
||||
);
|
||||
});
|
||||
fs.readdirSync(packages)
|
||||
.filter((name) => !name.startsWith('.'))
|
||||
.forEach((name) => {
|
||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||
packages,
|
||||
name,
|
||||
require(`../packages/${name}/package.json`).source
|
||||
);
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
18
package.json
18
package.json
@@ -13,7 +13,7 @@
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"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/)",
|
||||
"scripts": {
|
||||
@@ -27,23 +27,23 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"@babel/preset-flow": "^7.9.0",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@babel/runtime": "^7.9.6",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^25.2.1",
|
||||
"babel-jest": "^25.2.6",
|
||||
"babel-jest": "^26.0.1",
|
||||
"codecov": "^3.6.5",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-satya164": "^3.1.6",
|
||||
"husky": "^4.2.3",
|
||||
"jest": "^25.2.7",
|
||||
"eslint-config-satya164": "^3.1.7",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.0.1",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^2.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -3,6 +3,159 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.2.7",
|
||||
"version": "5.5.2",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -15,11 +15,13 @@
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -34,16 +36,16 @@
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@react-navigation/native": "^5.5.1",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-native": "^0.62.7",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-safe-area-context": "^1.0.0",
|
||||
"react-native-screens": "^2.7.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -15,6 +15,7 @@ export { default as BottomTabBar } from './views/BottomTabBar';
|
||||
export type {
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationProp,
|
||||
BottomTabScreenProps,
|
||||
BottomTabBarProps,
|
||||
BottomTabBarOptions,
|
||||
} from './types';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
TouchableWithoutFeedbackProps,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
GestureResponderEvent,
|
||||
} from 'react-native';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
Descriptor,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export type BottomTabNavigationEventMap = {
|
||||
@@ -44,6 +47,14 @@ export type BottomTabNavigationProp<
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type BottomTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
> = {
|
||||
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
};
|
||||
|
||||
export type BottomTabNavigationOptions = {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
@@ -187,7 +198,7 @@ export type BottomTabBarOptions = {
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>;
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
};
|
||||
|
||||
export type BottomTabBarProps = BottomTabBarOptions & {
|
||||
@@ -196,6 +207,13 @@ export type BottomTabBarProps = BottomTabBarOptions & {
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
};
|
||||
|
||||
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
|
||||
export type BottomTabBarButtonProps = Omit<
|
||||
TouchableWithoutFeedbackProps,
|
||||
'onPress'
|
||||
> & {
|
||||
to?: string;
|
||||
children: React.ReactNode;
|
||||
onPress?: (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
) => void;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NavigationRouteContext,
|
||||
CommonActions,
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -50,6 +51,45 @@ export default function BottomTabBar({
|
||||
tabStyle,
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
const buildLink = useLinkBuilder();
|
||||
|
||||
const focusedRoute = state.routes[state.index];
|
||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||
const focusedOptions = focusedDescriptor.options;
|
||||
|
||||
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
|
||||
|
||||
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(() => {
|
||||
if (shouldShowTabBar) {
|
||||
Animated.timing(visible, {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
useNativeDriver,
|
||||
}).start(({ finished }) => {
|
||||
if (finished) {
|
||||
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');
|
||||
@@ -57,46 +97,16 @@ export default function BottomTabBar({
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
const [layout, setLayout] = React.useState({
|
||||
height: 0,
|
||||
width: dimensions.width,
|
||||
});
|
||||
const [keyboardShown, setKeyboardShown] = React.useState(false);
|
||||
|
||||
const [visible] = React.useState(() => new Animated.Value(1));
|
||||
|
||||
const { routes } = state;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (keyboardShown) {
|
||||
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, {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
useNativeDriver,
|
||||
}).start(({ finished }) => {
|
||||
if (finished) {
|
||||
setKeyboardShown(false);
|
||||
}
|
||||
});
|
||||
|
||||
Dimensions.addEventListener('change', handleOrientationChange);
|
||||
|
||||
const handleKeyboardShow = () => setIsKeyboardShown(true);
|
||||
const handleKeyboardHide = () => setIsKeyboardShown(false);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
|
||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
|
||||
@@ -116,7 +126,12 @@ export default function BottomTabBar({
|
||||
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
|
||||
}
|
||||
};
|
||||
}, [visible]);
|
||||
}, []);
|
||||
|
||||
const [layout, setLayout] = React.useState({
|
||||
height: 0,
|
||||
width: dimensions.width,
|
||||
});
|
||||
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
@@ -133,6 +148,7 @@ export default function BottomTabBar({
|
||||
});
|
||||
};
|
||||
|
||||
const { routes } = state;
|
||||
const shouldUseHorizontalLabels = () => {
|
||||
if (labelPosition) {
|
||||
return labelPosition === 'beside-icon';
|
||||
@@ -181,22 +197,19 @@ export default function BottomTabBar({
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [layout.height, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: keyboardShown ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [layout.height + insets.bottom, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: isTabBarHidden ? 'absolute' : null,
|
||||
},
|
||||
{
|
||||
height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
|
||||
paddingBottom: insets.bottom,
|
||||
@@ -204,7 +217,7 @@ export default function BottomTabBar({
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
|
||||
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
@@ -260,6 +273,7 @@ export default function BottomTabBar({
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
to={buildLink(route.name, route.params)}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
GestureResponderEvent,
|
||||
} from 'react-native';
|
||||
import { Route, useTheme } from '@react-navigation/native';
|
||||
import { Link, Route, useTheme } from '@react-navigation/native';
|
||||
import Color from 'color';
|
||||
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
@@ -37,6 +39,10 @@ type Props = {
|
||||
size: number;
|
||||
color: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* URL to use for the link to the tab.
|
||||
*/
|
||||
to?: string;
|
||||
/**
|
||||
* The button for the tab. Uses a `TouchableWithoutFeedback` by default.
|
||||
*/
|
||||
@@ -50,13 +56,16 @@ type Props = {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
onLongPress: () => void;
|
||||
onLongPress: (e: GestureResponderEvent) => void;
|
||||
/**
|
||||
* Whether the label should be aligned with the icon horizontally.
|
||||
*/
|
||||
@@ -104,11 +113,48 @@ export default function BottomTabBarItem({
|
||||
route,
|
||||
label,
|
||||
icon,
|
||||
button = ({ children, style, ...rest }: BottomTabBarButtonProps) => (
|
||||
<TouchableWithoutFeedback {...rest}>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
),
|
||||
to,
|
||||
button = ({
|
||||
children,
|
||||
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,
|
||||
testID,
|
||||
onPress,
|
||||
@@ -145,7 +191,7 @@ export default function BottomTabBarItem({
|
||||
|
||||
if (typeof label === 'string') {
|
||||
return (
|
||||
<Animated.Text
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
styles.label,
|
||||
@@ -156,14 +202,10 @@ export default function BottomTabBarItem({
|
||||
allowFontScaling={allowFontScaling}
|
||||
>
|
||||
{label}
|
||||
</Animated.Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof label === 'string') {
|
||||
return label;
|
||||
}
|
||||
|
||||
return label({ focused, color });
|
||||
};
|
||||
|
||||
@@ -196,6 +238,7 @@ export default function BottomTabBarItem({
|
||||
: inactiveBackgroundColor;
|
||||
|
||||
return button({
|
||||
to,
|
||||
onPress,
|
||||
onLongPress,
|
||||
testID,
|
||||
@@ -248,4 +291,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
marginLeft: 20,
|
||||
},
|
||||
button: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as React from 'react';
|
||||
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
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
|
||||
@@ -71,17 +75,8 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
tabBarOptions,
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
} = 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({
|
||||
...tabBarOptions,
|
||||
state: state,
|
||||
@@ -91,44 +86,46 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, descriptors, lazy } = this.props;
|
||||
const { state, descriptors, navigation, lazy } = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
return (
|
||||
<SafeAreaProviderCompat>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
<SceneContent isFocused={isFocused}>
|
||||
{descriptor.render()}
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
</SafeAreaProviderCompat>
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
<SceneContent isFocused={isFocused}>
|
||||
{descriptor.render()}
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,149 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.1.9",
|
||||
"version": "5.1.26",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||
"bugs": {
|
||||
@@ -10,11 +10,13 @@
|
||||
"homepage": "https://reactnavigation.org/docs/compatibility.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -25,9 +27,9 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@types/react": "^16.9.23",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@react-navigation/native": "^5.5.1",
|
||||
"@types/react": "^16.9.34",
|
||||
"react": "~16.9.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
NavigationRouteContext,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
@@ -67,6 +68,9 @@ export default function createCompatNavigatorFactory<
|
||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const parentRouteParams = React.useContext(NavigationRouteContext)
|
||||
?.params;
|
||||
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map((name) => {
|
||||
@@ -135,7 +139,7 @@ export default function createCompatNavigatorFactory<
|
||||
<Pair.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
initialParams={initialParams}
|
||||
initialParams={{ ...parentRouteParams, ...initialParams }}
|
||||
options={screenOptions}
|
||||
>
|
||||
{({ navigation, route }) => (
|
||||
@@ -148,7 +152,7 @@ export default function createCompatNavigatorFactory<
|
||||
</Pair.Screen>
|
||||
);
|
||||
}),
|
||||
[screenProps]
|
||||
[parentRouteParams, screenProps]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -163,7 +167,7 @@ export default function createCompatNavigatorFactory<
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.navigationOtions = parentNavigationOptions;
|
||||
Navigator.navigationOptions = parentNavigationOptions;
|
||||
|
||||
return Navigator;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,170 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.3.4",
|
||||
"version": "5.10.0",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -15,11 +15,13 @@
|
||||
"homepage": "https://reactnavigation.org",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -29,21 +31,21 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.4.0",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"nanoid": "^3.0.2",
|
||||
"query-string": "^6.12.0",
|
||||
"@react-navigation/routers": "^5.4.7",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.5",
|
||||
"query-string": "^6.12.1",
|
||||
"react-is": "^16.13.0",
|
||||
"use-subscription": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"@types/use-subscription": "^1.0.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-native-testing-library": "^1.13.2",
|
||||
"react-test-renderer": "~16.13.1",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
|
||||
@@ -13,46 +13,22 @@ import { ScheduleUpdateContext } from './useScheduleUpdate';
|
||||
import useFocusedListeners from './useFocusedListeners';
|
||||
import useDevTools from './useDevTools';
|
||||
import useStateGetters from './useStateGetters';
|
||||
import useOptionsGetters from './useOptionsGetters';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useSyncState from './useSyncState';
|
||||
import isSerializable from './isSerializable';
|
||||
|
||||
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
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.";
|
||||
const DEVTOOLS_CONFIG_KEY =
|
||||
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED';
|
||||
|
||||
const NOT_INITIALIZED_ERROR =
|
||||
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
|
||||
|
||||
export const NavigationStateContext = React.createContext<{
|
||||
isDefault?: true;
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
getKey: () => string | undefined;
|
||||
setKey: (key: string) => void;
|
||||
getState: () => NavigationState | PartialState<NavigationState> | undefined;
|
||||
setState: (
|
||||
state: NavigationState | PartialState<NavigationState> | undefined
|
||||
) => void;
|
||||
}>({
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -143,7 +119,9 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
enabled: false,
|
||||
enabled:
|
||||
// @ts-ignore
|
||||
DEVTOOLS_CONFIG_KEY in global ? global[DEVTOOLS_CONFIG_KEY] : false,
|
||||
name: '@react-navigation',
|
||||
reset,
|
||||
state,
|
||||
@@ -194,8 +172,21 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
return getStateForRoute('root');
|
||||
}, [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 { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
||||
any
|
||||
@@ -214,6 +205,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
dispatch,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
dangerouslyGetState: () => state,
|
||||
dangerouslyGetParent: () => undefined,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
}));
|
||||
|
||||
const builderContext = React.useMemo(
|
||||
@@ -237,10 +232,17 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
setState,
|
||||
getKey,
|
||||
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(() => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (
|
||||
@@ -267,12 +269,12 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
trackState(getRootState);
|
||||
}
|
||||
|
||||
if (!isFirstMountRef.current && onStateChange) {
|
||||
onStateChange(getRootState());
|
||||
if (!isFirstMountRef.current && onStateChangeRef.current) {
|
||||
onStateChangeRef.current(getRootState());
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
}, [onStateChange, trackState, getRootState, emitter, state]);
|
||||
}, [trackState, getRootState, emitter, state]);
|
||||
|
||||
return (
|
||||
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
||||
|
||||
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,
|
||||
PartialState,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||
import useOptionsGetters from './useOptionsGetters';
|
||||
|
||||
type Props<
|
||||
State extends NavigationState,
|
||||
@@ -24,6 +25,7 @@ type Props<
|
||||
};
|
||||
getState: () => State;
|
||||
setState: (state: State) => void;
|
||||
options: object;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,11 +42,24 @@ export default function SceneView<
|
||||
navigation,
|
||||
getState,
|
||||
setState,
|
||||
options,
|
||||
}: Props<State, ScreenOptions, EventMap>) {
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
|
||||
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) => {
|
||||
navigatorKeyRef.current = key;
|
||||
}, []);
|
||||
@@ -77,8 +92,16 @@ export default function SceneView<
|
||||
setState: setCurrentState,
|
||||
getKey,
|
||||
setKey,
|
||||
addOptionsGetter,
|
||||
}),
|
||||
[getCurrentState, getKey, route.state, setCurrentState, setKey]
|
||||
[
|
||||
getCurrentState,
|
||||
getKey,
|
||||
route.state,
|
||||
setCurrentState,
|
||||
setKey,
|
||||
addOptionsGetter,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,9 +5,8 @@ import {
|
||||
NavigationState,
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import BaseNavigationContainer, {
|
||||
NavigationStateContext,
|
||||
} from '../BaseNavigationContainer';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import NavigationStateContext from '../NavigationStateContext';
|
||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import Screen from '../Screen';
|
||||
|
||||
@@ -117,7 +117,8 @@ it("doesn't add query param for empty params", () => {
|
||||
});
|
||||
|
||||
it('handles state with config with nested screens', () => {
|
||||
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||
const path =
|
||||
'/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
@@ -182,8 +183,77 @@ it('handles state with config with nested screens', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles state with config with nested screens and exact', () => {
|
||||
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
stringify: {
|
||||
author: (author: string) => author.toLowerCase(),
|
||||
id: (id: number) => `x${id}`,
|
||||
unknown: (_: unknown) => 'x',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: '10',
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles state with config with nested screens and unused configs', () => {
|
||||
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
|
||||
const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
@@ -239,6 +309,66 @@ it('handles state with config with nested screens and unused configs', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles state with config with nested screens and unused configs with exact', () => {
|
||||
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
unknown: (_: unknown) => 'x',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object with stringify in it', () => {
|
||||
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
@@ -252,7 +382,6 @@ it('handles nested object with stringify in it', () => {
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
@@ -312,8 +441,82 @@ it('handles nested object with stringify in it', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object with stringify in it with exact', () => {
|
||||
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
path: 'bis/:author',
|
||||
exact: true,
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: '42',
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object for second route depth', () => {
|
||||
const path = '/baz';
|
||||
const path = '/foo/bar/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
@@ -351,7 +554,95 @@ it('handles nested object for second route depth', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object for second route depth and and path and stringify in roots', () => {
|
||||
it('handles nested object for second route depth with exact', () => {
|
||||
const path = '/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
path: 'bar',
|
||||
screens: {
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Baz' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object for second route depth and path and stringify in roots', () => {
|
||||
const path = '/foo/dathomir/bar/42/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo/:planet',
|
||||
stringify: {
|
||||
id: (id: number) => `planet=${id}`,
|
||||
},
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
path: 'bar/:id',
|
||||
parse: {
|
||||
id: Number,
|
||||
},
|
||||
screens: {
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
params: { planet: 'dathomir' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Baz', params: { id: 42 } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles nested object for second route depth and path and stringify in roots with exact', () => {
|
||||
const path = '/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
@@ -370,7 +661,10 @@ it('handles nested object for second route depth and and path and stringify in r
|
||||
id: Number,
|
||||
},
|
||||
screens: {
|
||||
Baz: 'baz',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -426,8 +720,51 @@ it('ignores empty string paths', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('keeps query params if path is empty', () => {
|
||||
const path = '/?foo=42';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
screens: {
|
||||
Qux: {
|
||||
path: '',
|
||||
parse: { foo: Number },
|
||||
},
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Qux', params: { foo: 42 } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toEqual(
|
||||
path
|
||||
);
|
||||
});
|
||||
|
||||
it('cuts nested configs too', () => {
|
||||
const path = '/baz';
|
||||
const path = '/foo/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
@@ -435,7 +772,48 @@ it('cuts nested configs too', () => {
|
||||
Bar: '',
|
||||
},
|
||||
},
|
||||
Baz: { path: 'baz' },
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Baz' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('cuts nested configs too with exact', () => {
|
||||
const path = '/baz';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
@@ -461,7 +839,7 @@ it('cuts nested configs too', () => {
|
||||
});
|
||||
|
||||
it('handles empty path at the end', () => {
|
||||
const path = '/bar';
|
||||
const path = '/foo/bar';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
@@ -495,6 +873,8 @@ it('handles empty path at the end', () => {
|
||||
});
|
||||
|
||||
it('returns "/" for empty path', () => {
|
||||
const path = '/';
|
||||
|
||||
const config = {
|
||||
Foo: {
|
||||
path: '',
|
||||
@@ -519,7 +899,8 @@ it('returns "/" for empty path', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/');
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('parses no path specified', () => {
|
||||
@@ -595,7 +976,6 @@ it('strips undefined query params', () => {
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
@@ -635,7 +1015,79 @@ it('strips undefined query params', () => {
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
answer: undefined,
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('strips undefined query params with exact', () => {
|
||||
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
path: 'bis/:author',
|
||||
exact: true,
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: 10,
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
@@ -668,7 +1120,6 @@ it('handles stripping all query params', () => {
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
@@ -707,9 +1158,6 @@ it('handles stripping all query params', () => {
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
count: undefined,
|
||||
answer: undefined,
|
||||
valid: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -727,3 +1175,265 @@ it('handles stripping all query params', () => {
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('handles stripping all query params with exact', () => {
|
||||
const path = '/bar/sweet/apple/foo/bis/jane';
|
||||
const config = {
|
||||
Foo: {
|
||||
path: 'foo',
|
||||
screens: {
|
||||
Foe: {
|
||||
path: 'foe',
|
||||
},
|
||||
},
|
||||
},
|
||||
Bar: 'bar/:type/:fruit',
|
||||
Baz: {
|
||||
path: 'baz',
|
||||
screens: {
|
||||
Bos: 'bos',
|
||||
Bis: {
|
||||
path: 'bis/:author',
|
||||
exact: true,
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple', type: 'sweet' },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Baz',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bis',
|
||||
params: {
|
||||
author: 'Jane',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('replaces undefined query params', () => {
|
||||
const path = '/bar/undefined/apple';
|
||||
const config = {
|
||||
Bar: 'bar/:type/:fruit',
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { fruit: 'apple' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('matches wildcard patterns at root', () => {
|
||||
const path = '/test/bar/42/whatever';
|
||||
const config = {
|
||||
404: '*',
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [{ name: '404' }],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/404');
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe('/404');
|
||||
});
|
||||
|
||||
it('matches wildcard patterns at nested level', () => {
|
||||
const path = '/bar/42/whatever/baz/initt';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
404: '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { id: '42' },
|
||||
state: {
|
||||
routes: [{ name: '404' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/bar/42/404');
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(
|
||||
'/bar/42/404'
|
||||
);
|
||||
});
|
||||
|
||||
it('matches wildcard patterns at nested level with exact', () => {
|
||||
const path = '/whatever';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
404: {
|
||||
path: '*',
|
||||
exact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Baz: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: '404' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/404');
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe('/404');
|
||||
});
|
||||
|
||||
it('tries to match wildcard patterns at the end', () => {
|
||||
const path = '/bar/42/test';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
404: '*',
|
||||
Test: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
params: { id: '42' },
|
||||
state: {
|
||||
routes: [{ name: 'Test' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('uses nearest parent wildcard match for unmatched paths', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
404: '*',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [{ name: '404' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/404');
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe('/404');
|
||||
});
|
||||
|
||||
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', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
@@ -426,6 +428,8 @@ it('cleans up state when the navigator unmounts', () => {
|
||||
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
|
||||
);
|
||||
|
||||
act(() => jest.runAllTimers());
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(2);
|
||||
expect(onStateChange).lastCalledWith(undefined);
|
||||
});
|
||||
@@ -1486,3 +1490,128 @@ it("doesn't throw if children is null", () => {
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -4,19 +4,31 @@ import {
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import { PathConfig } from './types';
|
||||
|
||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||
|
||||
type StringifyConfig = Record<string, (value: any) => string>;
|
||||
|
||||
type Options = {
|
||||
[routeName: string]:
|
||||
| string
|
||||
| {
|
||||
path?: string;
|
||||
stringify?: StringifyConfig;
|
||||
screens?: Options;
|
||||
};
|
||||
type OptionsItem = PathConfig[string];
|
||||
|
||||
type ConfigItem = {
|
||||
pattern?: string;
|
||||
stringify?: StringifyConfig;
|
||||
screens?: Record<string, ConfigItem>;
|
||||
};
|
||||
|
||||
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,137 +60,221 @@ type Options = {
|
||||
*/
|
||||
export default function getPathFromState(
|
||||
state?: State,
|
||||
options: Options = {}
|
||||
options: PathConfig = {}
|
||||
): string {
|
||||
if (state === undefined) {
|
||||
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;
|
||||
|
||||
const allParams: Record<string, any> = {};
|
||||
|
||||
while (current) {
|
||||
let index = typeof current.index === 'number' ? current.index : 0;
|
||||
let route = current.routes[index] as Route<string> & {
|
||||
state?: State;
|
||||
};
|
||||
let currentOptions = options;
|
||||
let pattern = route.name;
|
||||
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
||||
let nestedRouteNames = '';
|
||||
|
||||
while (route.name in currentOptions) {
|
||||
if (typeof currentOptions[route.name] === 'string') {
|
||||
pattern = currentOptions[route.name] as string;
|
||||
break;
|
||||
} else if (typeof currentOptions[route.name] === 'object') {
|
||||
// if there is no `screens` property, we return pattern
|
||||
if (
|
||||
!(currentOptions[route.name] as {
|
||||
screens: Options;
|
||||
}).screens
|
||||
) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
break;
|
||||
let pattern: string | undefined;
|
||||
|
||||
let focusedParams: Record<string, any> | undefined;
|
||||
let focusedRoute = getActiveRoute(state);
|
||||
let currentOptions = configs;
|
||||
|
||||
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
||||
let nestedRouteNames = [];
|
||||
|
||||
let hasNext = true;
|
||||
|
||||
while (route.name in currentOptions && hasNext) {
|
||||
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 {
|
||||
// if it is the end of state, we return pattern
|
||||
if (route.state === undefined) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
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) {
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
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;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If not, there is no sense in going deeper in config
|
||||
hasNext = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern === undefined) {
|
||||
// cut the first `/`
|
||||
pattern = nestedRouteNames.substring(1);
|
||||
pattern = nestedRouteNames.join('/');
|
||||
}
|
||||
|
||||
// we don't add empty path strings to path
|
||||
if (pattern !== '') {
|
||||
const config =
|
||||
currentOptions[route.name] !== undefined
|
||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
||||
.stringify
|
||||
: undefined;
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map((p) => {
|
||||
const name = getParamName(p);
|
||||
|
||||
const params = route.params
|
||||
? // Stringify all of the param values before we use them
|
||||
Object.entries(route.params).reduce<{
|
||||
[key: string]: string;
|
||||
}>((acc, [key, value]) => {
|
||||
acc[key] = config?.[key] ? config[key](value) : String(value);
|
||||
return acc;
|
||||
}, {})
|
||||
: undefined;
|
||||
// We don't know what to show for wildcard patterns
|
||||
// Showing the route name seems ok, though whatever we show here will be incorrect
|
||||
// Since the page doesn't actually exist
|
||||
if (p === '*') {
|
||||
return route.name;
|
||||
}
|
||||
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map((p) => {
|
||||
const name = p.replace(/^:/, '');
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
if (p.startsWith(':')) {
|
||||
const value = allParams[name];
|
||||
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
if (params && name in params && p.startsWith(':')) {
|
||||
const value = params[name];
|
||||
// Remove the used value from the params object since we'll use the rest for query string
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
if (value === undefined && p.endsWith('?')) {
|
||||
// Optional params without value assigned in route.params should be ignored
|
||||
return '';
|
||||
}
|
||||
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.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) {
|
||||
path += '/';
|
||||
} else if (params) {
|
||||
for (let param in params) {
|
||||
if (params[param] === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[param];
|
||||
}
|
||||
}
|
||||
const query = queryString.stringify(params);
|
||||
const query = queryString.stringify(focusedParams);
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
}
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
}
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
}
|
||||
|
||||
path =
|
||||
path !== '/' && path.slice(path.length - 1) === '/'
|
||||
? path.slice(0, -1)
|
||||
: path;
|
||||
// Remove multiple as well as trailing slashes
|
||||
path = path.replace(/\/+/g, '/');
|
||||
path = path.length > 1 ? path.replace(/\/$/, '') : 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,
|
||||
InitialState,
|
||||
} from '@react-navigation/routers';
|
||||
import { PathConfig } from './types';
|
||||
|
||||
type ParseConfig = Record<string, (value: string) => any>;
|
||||
|
||||
type Options = {
|
||||
[routeName: string]:
|
||||
| string
|
||||
| {
|
||||
path?: string;
|
||||
parse?: ParseConfig;
|
||||
screens?: Options;
|
||||
initialRouteName?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type RouteConfig = {
|
||||
match: RegExp;
|
||||
screen: string;
|
||||
regex?: RegExp;
|
||||
path: string;
|
||||
pattern: string;
|
||||
routeNames: string[];
|
||||
parse: ParseConfig | undefined;
|
||||
parse?: ParseConfig;
|
||||
};
|
||||
|
||||
type InitialRouteConfig = {
|
||||
@@ -56,63 +48,128 @@ type ResultState = PartialState<NavigationState> & {
|
||||
*/
|
||||
export default function getStateFromPath(
|
||||
path: string,
|
||||
options: Options = {}
|
||||
options: PathConfig = {}
|
||||
): 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;
|
||||
}
|
||||
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 current: PartialState<NavigationState> | undefined;
|
||||
|
||||
let remaining = path
|
||||
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
|
||||
.replace(/^\//, '') // Remove extra leading slash
|
||||
.replace(/\?.*/, ''); // Remove query params which we will handle later
|
||||
// We try to match the paths in 2 passes
|
||||
// In first pass, we match the whole path against the regex instead of segments
|
||||
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
||||
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) {
|
||||
let routeNames: string[] | undefined;
|
||||
let params: Record<string, any> | undefined;
|
||||
let { routeNames, allParams, remainingPath } = matchAgainstConfigs(
|
||||
remaining,
|
||||
configs
|
||||
);
|
||||
|
||||
// Go through all configs, and see if the next path segment matches our regex
|
||||
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;
|
||||
}
|
||||
}
|
||||
remaining = remainingPath;
|
||||
|
||||
// If we hadn't matched any segments earlier, use the path as route name
|
||||
if (routeNames === undefined) {
|
||||
@@ -123,35 +180,11 @@ export default function getStateFromPath(
|
||||
remaining = segments.join('/');
|
||||
}
|
||||
|
||||
let state: InitialState;
|
||||
let routeName = routeNames.shift() as string;
|
||||
let initialRoute = findInitialRoute(routeName, initialRoutes);
|
||||
|
||||
state = createNestedState(
|
||||
initialRoute,
|
||||
routeName,
|
||||
routeNames.length === 0,
|
||||
params
|
||||
const state = createNestedStateObject(
|
||||
createRouteObjects(configs, routeNames, allParams),
|
||||
initialRoutes
|
||||
);
|
||||
|
||||
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) {
|
||||
// The state should be nested inside the deepest route we parsed before
|
||||
while (current?.routes[current.index || 0].state) {
|
||||
@@ -172,74 +205,118 @@ export default function getStateFromPath(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const query = path.split('?')[1];
|
||||
|
||||
if (query) {
|
||||
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
|
||||
];
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
const route = findFocusedRoute(current);
|
||||
const params = parseQueryParams(
|
||||
path,
|
||||
findParseConfigForRoute(route.name, configs)
|
||||
);
|
||||
|
||||
if (params) {
|
||||
route.params = { ...route.params, ...params };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function createNormalizedConfigs(
|
||||
key: string,
|
||||
routeConfig: Options,
|
||||
const joinPaths = (...paths: string[]): string =>
|
||||
([] as string[])
|
||||
.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[] = [],
|
||||
initials: InitialRouteConfig[]
|
||||
): RouteConfig[] {
|
||||
initials: InitialRouteConfig[],
|
||||
parentPattern?: string
|
||||
): 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 (value !== '') {
|
||||
configs.push(createConfigItem(routeNames, value));
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||
|
||||
configs.push(createConfigItem(screen, routeNames, pattern, config));
|
||||
} else if (typeof config === 'object') {
|
||||
let pattern: string | undefined;
|
||||
|
||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||
// it can have `path` property and
|
||||
// it could have `screens` prop which has nested configs
|
||||
if (value.path && value.path !== '') {
|
||||
configs.push(createConfigItem(routeNames, value.path, value.parse));
|
||||
if (typeof config.path === 'string') {
|
||||
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
|
||||
if (value.initialRouteName) {
|
||||
if (config.initialRouteName) {
|
||||
initials.push({
|
||||
initialRouteName: value.initialRouteName,
|
||||
connectedRoutes: Object.keys(value.screens),
|
||||
initialRouteName: config.initialRouteName,
|
||||
connectedRoutes: Object.keys(config.screens),
|
||||
});
|
||||
}
|
||||
Object.keys(value.screens).forEach((nestedConfig) => {
|
||||
|
||||
Object.keys(config.screens).forEach((nestedConfig) => {
|
||||
const result = createNormalizedConfigs(
|
||||
nestedConfig,
|
||||
value.screens as Options,
|
||||
config.screens as PathConfig,
|
||||
routeNames,
|
||||
initials
|
||||
initials,
|
||||
pattern
|
||||
);
|
||||
|
||||
configs.push(...result);
|
||||
});
|
||||
}
|
||||
@@ -248,43 +325,62 @@ function createNormalizedConfigs(
|
||||
routeNames.pop();
|
||||
|
||||
return configs;
|
||||
}
|
||||
};
|
||||
|
||||
function createConfigItem(
|
||||
const createConfigItem = (
|
||||
screen: string,
|
||||
routeNames: string[],
|
||||
pattern: string,
|
||||
path: string,
|
||||
parse?: ParseConfig
|
||||
): RouteConfig {
|
||||
const match = new RegExp(
|
||||
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
|
||||
);
|
||||
): RouteConfig => {
|
||||
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
|
||||
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 {
|
||||
match,
|
||||
screen,
|
||||
regex,
|
||||
pattern,
|
||||
path,
|
||||
// The routeNames array is mutated, so copy it to keep the current state
|
||||
routeNames: [...routeNames],
|
||||
parse,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function findParseConfigForRoute(
|
||||
const findParseConfigForRoute = (
|
||||
routeName: string,
|
||||
flatConfig: RouteConfig[]
|
||||
): ParseConfig | undefined {
|
||||
): ParseConfig | undefined => {
|
||||
for (const config of flatConfig) {
|
||||
if (routeName === config.routeNames[config.routeNames.length - 1]) {
|
||||
return config.parse;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// tries to find an initial route connected with the one passed
|
||||
function findInitialRoute(
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Try to find an initial route connected with the one passed
|
||||
const findInitialRoute = (
|
||||
routeName: string,
|
||||
initialRoutes: InitialRouteConfig[]
|
||||
): string | undefined {
|
||||
): string | undefined => {
|
||||
for (const config of initialRoutes) {
|
||||
if (config.connectedRoutes.includes(routeName)) {
|
||||
return config.initialRouteName === routeName
|
||||
@@ -293,28 +389,25 @@ function findInitialRoute(
|
||||
}
|
||||
}
|
||||
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
|
||||
function createNestedState(
|
||||
const createStateObject = (
|
||||
initialRoute: string | undefined,
|
||||
routeName: string,
|
||||
isEmpty: boolean,
|
||||
params?: Record<string, any> | undefined
|
||||
): InitialState {
|
||||
params: Record<string, any> | undefined,
|
||||
isEmpty: boolean
|
||||
): InitialState => {
|
||||
if (isEmpty) {
|
||||
if (initialRoute) {
|
||||
return {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ name: initialRoute },
|
||||
{ name: routeName as string, ...(params && { params }) },
|
||||
],
|
||||
routes: [{ name: initialRoute }, { name: routeName as string, params }],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
routes: [{ name: routeName as string, ...(params && { params }) }],
|
||||
routes: [{ name: routeName as string, params }],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -323,11 +416,130 @@ function createNestedState(
|
||||
index: 1,
|
||||
routes: [
|
||||
{ name: initialRoute },
|
||||
{ name: routeName as string, state: { routes: [] } },
|
||||
{ name: routeName as string, params, state: { routes: [] } },
|
||||
],
|
||||
};
|
||||
} 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 createNavigatorFactory } from './createNavigatorFactory';
|
||||
|
||||
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
||||
export { default as NavigationContext } from './NavigationContext';
|
||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||
|
||||
export { default as CurrentRenderContext } from './CurrentRenderContext';
|
||||
|
||||
export { default as useNavigationBuilder } from './useNavigationBuilder';
|
||||
export { default as useNavigation } from './useNavigation';
|
||||
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]);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ type NavigationHelpersCommon<
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
navigate<RouteName extends keyof ParamList>(
|
||||
...args: ParamList[RouteName] extends undefined | any
|
||||
...args: undefined extends ParamList[RouteName]
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
@@ -193,6 +193,20 @@ type NavigationHelpersCommon<
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
canGoBack(): boolean;
|
||||
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
* dangerouslyGetState is to discourage developers to use internal navigation's state.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
dangerouslyGetState(): State;
|
||||
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
|
||||
|
||||
export type NavigationHelpers<
|
||||
@@ -254,20 +268,6 @@ export type NavigationProp<
|
||||
* @param options Options object for the route.
|
||||
*/
|
||||
setOptions(options: Partial<ScreenOptions>): void;
|
||||
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
* dangerouslyGetState is to discourage developers to use internal navigation's state.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
dangerouslyGetState(): State;
|
||||
} & EventConsumer<EventMap & EventMapCore<State>> &
|
||||
PrivateValueStore<ParamList, RouteName, EventMap>;
|
||||
|
||||
@@ -422,6 +422,14 @@ export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
||||
* Get the rehydrated navigation state of the navigation tree.
|
||||
*/
|
||||
getRootState(): NavigationState;
|
||||
/**
|
||||
* Get the currently focused navigation route.
|
||||
*/
|
||||
getCurrentRoute(): Route<string> | undefined;
|
||||
/**
|
||||
* Get the currently focused route's options.
|
||||
*/
|
||||
getCurrentOptions(): object | undefined;
|
||||
};
|
||||
|
||||
export type TypedNavigator<
|
||||
@@ -462,3 +470,16 @@ export type TypedNavigator<
|
||||
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
|
||||
) => null;
|
||||
};
|
||||
|
||||
export type PathConfig = {
|
||||
[routeName: string]:
|
||||
| string
|
||||
| {
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
parse?: Record<string, (value: string) => any>;
|
||||
stringify?: Record<string, (value: any) => string>;
|
||||
screens?: PathConfig;
|
||||
initialRouteName?: string;
|
||||
};
|
||||
};
|
||||
|
||||
28
packages/core/src/useCurrentRender.tsx
Normal file
28
packages/core/src/useCurrentRender.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import CurrentRenderContext from './CurrentRenderContext';
|
||||
import { Descriptor, NavigationHelpers } from './types';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
descriptors: {
|
||||
[key: string]: Descriptor<ParamListBase, string, NavigationState, object>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Write the current options, so that server renderer can get current values
|
||||
* Mutating values like this is not safe in async mode, but it doesn't apply to SSR
|
||||
*/
|
||||
export default function useCurrentRender({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
}: Options) {
|
||||
const current = React.useContext(CurrentRenderContext);
|
||||
|
||||
if (current && navigation.isFocused()) {
|
||||
current.options = descriptors[state.routes[state.index].key].options;
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,28 @@ export default function useDescriptors<
|
||||
const screen = screens[route.name];
|
||||
const navigation = navigations[route.key];
|
||||
|
||||
const routeOptions = {
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...(typeof screenOptions === 'object' || screenOptions == null
|
||||
? screenOptions
|
||||
: screenOptions({
|
||||
// @ts-ignore
|
||||
route,
|
||||
navigation,
|
||||
})),
|
||||
// The `options` prop passed to `Screen` elements
|
||||
...(typeof screen.options === 'object' || screen.options == null
|
||||
? screen.options
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
// @ts-ignore
|
||||
navigation,
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
...options[route.key],
|
||||
};
|
||||
|
||||
acc[route.key] = {
|
||||
navigation,
|
||||
render() {
|
||||
@@ -128,31 +150,12 @@ export default function useDescriptors<
|
||||
screen={screen}
|
||||
getState={getState}
|
||||
setState={setState}
|
||||
options={routeOptions}
|
||||
/>
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
},
|
||||
options: {
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...(typeof screenOptions === 'object' || screenOptions == null
|
||||
? screenOptions
|
||||
: screenOptions({
|
||||
// @ts-ignore
|
||||
route,
|
||||
navigation,
|
||||
})),
|
||||
// The `options` prop passed to `Screen` elements
|
||||
...(typeof screen.options === 'object' || screen.options == null
|
||||
? screen.options
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
// @ts-ignore
|
||||
navigation,
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
...options[route.key],
|
||||
},
|
||||
options: routeOptions,
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
||||
@@ -9,16 +9,13 @@ import useNavigation from './useNavigation';
|
||||
*/
|
||||
export default function useIsFocused(): boolean {
|
||||
const navigation = useNavigation();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const getCurrentValue = React.useCallback(navigation.isFocused, [navigation]);
|
||||
const subscribe = React.useCallback(
|
||||
(callback: (value: boolean) => void) => {
|
||||
const unsubscribeFocus = navigation.addListener('focus', () =>
|
||||
callback(true)
|
||||
);
|
||||
(callback: () => void) => {
|
||||
const unsubscribeFocus = navigation.addListener('focus', callback);
|
||||
|
||||
const unsubscribeBlur = navigation.addListener('blur', () =>
|
||||
callback(false)
|
||||
);
|
||||
const unsubscribeBlur = navigation.addListener('blur', callback);
|
||||
|
||||
return () => {
|
||||
unsubscribeFocus();
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
NavigationAction,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import Screen from './Screen';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
@@ -33,6 +33,8 @@ import {
|
||||
import useStateGetters from './useStateGetters';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
|
||||
// This is to make TypeScript compiler happy
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
@@ -47,13 +49,6 @@ type NavigatorRoute = {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two arrays with primitive values as the content.
|
||||
* We need to make sure that both values and order match.
|
||||
*/
|
||||
const isArrayEqual = (a: any[], b: any[]) =>
|
||||
a.length === b.length && a.every((it, index) => it === b[index]);
|
||||
|
||||
/**
|
||||
* Extract route config object from React children elements.
|
||||
*
|
||||
@@ -264,41 +259,36 @@ export default function useNavigationBuilder<
|
||||
getKey,
|
||||
} = React.useContext(NavigationStateContext);
|
||||
|
||||
const previousStateRef = React.useRef<
|
||||
NavigationState | PartialState<NavigationState> | undefined
|
||||
>();
|
||||
const initializedStateRef = React.useRef<State>();
|
||||
|
||||
let isFirstStateInitialization = false;
|
||||
|
||||
if (
|
||||
initializedStateRef.current === undefined ||
|
||||
currentState !== previousStateRef.current
|
||||
) {
|
||||
const [initializedState, isFirstStateInitialization] = React.useMemo(() => {
|
||||
// If the current state isn't initialized on first render, we initialize it
|
||||
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
|
||||
// Otherwise assume that the state was provided as initial state
|
||||
// So we need to rehydrate it to make it usable
|
||||
if (currentState === undefined || !isStateValid(currentState)) {
|
||||
isFirstStateInitialization = true;
|
||||
initializedStateRef.current = router.getInitialState({
|
||||
routeNames,
|
||||
routeParamList,
|
||||
});
|
||||
} else {
|
||||
initializedStateRef.current = router.getRehydratedState(
|
||||
currentState as PartialState<State>,
|
||||
{
|
||||
return [
|
||||
router.getInitialState({
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}
|
||||
);
|
||||
}),
|
||||
true,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
router.getRehydratedState(currentState as PartialState<State>, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}),
|
||||
false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
previousStateRef.current = currentState;
|
||||
}, [currentState]);
|
||||
// We explicitly don't include routeNames/routeParamList in the dep list
|
||||
// below. We want to avoid forcing a new state to be calculated in cases
|
||||
// where routeConfigs change without affecting routeNames/routeParamList.
|
||||
// Instead, we handle changes to these in the nextState code below. Note
|
||||
// that some changes to routeConfigs are explicitly ignored, such as changes
|
||||
// to initialParams
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState, router, isStateValid]);
|
||||
|
||||
let state =
|
||||
// If the state isn't initialized, or stale, use the state we initialized instead
|
||||
@@ -306,7 +296,7 @@ export default function useNavigationBuilder<
|
||||
// So it'll be `undefined` or stale untill the first navigation event happens
|
||||
isStateInitialized(currentState)
|
||||
? (currentState as State)
|
||||
: (initializedStateRef.current as State);
|
||||
: (initializedState as State);
|
||||
|
||||
let nextState: State = state;
|
||||
|
||||
@@ -362,13 +352,24 @@ export default function useNavigationBuilder<
|
||||
|
||||
return () => {
|
||||
// We need to clean up state for this navigator on unmount
|
||||
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
|
||||
setState(undefined);
|
||||
}
|
||||
// We do it in a timeout because we need to detect if another navigator mounted in the meantime
|
||||
// For example, if another navigator has started rendering, we should skip cleanup
|
||||
// Otherwise, our cleanup step will cleanup state for the other navigator and re-initialize it
|
||||
setTimeout(() => {
|
||||
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
|
||||
setState(undefined);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// We initialize this ref here to avoid a new getState getting initialized
|
||||
// whenever initializedState changes. We want getState to have access to the
|
||||
// latest initializedState, but don't need it to change when that happens
|
||||
const initializedStateRef = React.useRef<State>();
|
||||
initializedStateRef.current = initializedState;
|
||||
|
||||
const getState = React.useCallback((): State => {
|
||||
const currentState = getCurrentState();
|
||||
|
||||
@@ -492,6 +493,12 @@ export default function useNavigationBuilder<
|
||||
emitter,
|
||||
});
|
||||
|
||||
useCurrentRender({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
navigation,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
import { NavigationHelpers, NavigationProp } from './types';
|
||||
|
||||
@@ -49,12 +48,10 @@ export default function useNavigationCache<
|
||||
// Cache object which holds navigation objects for each screen
|
||||
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
||||
// In reality, these deps will rarely change, if ever
|
||||
const parentNavigation = React.useContext(NavigationContext);
|
||||
|
||||
const cache = React.useMemo(
|
||||
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[getState, navigation, setOptions, router, emitter, parentNavigation]
|
||||
[getState, navigation, setOptions, router, emitter]
|
||||
);
|
||||
|
||||
const actions = {
|
||||
@@ -99,8 +96,6 @@ export default function useNavigationCache<
|
||||
...rest,
|
||||
...helpers,
|
||||
...emitter.create(route.key),
|
||||
dangerouslyGetParent: () => parentNavigation as any,
|
||||
dangerouslyGetState: getState,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions((o) => ({
|
||||
|
||||
@@ -112,6 +112,8 @@ export default function useNavigationHelpers<
|
||||
false
|
||||
);
|
||||
},
|
||||
dangerouslyGetParent: () => parentNavigationHelpers as any,
|
||||
dangerouslyGetState: getState,
|
||||
} as NavigationHelpers<ParamListBase, EventMap> &
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
|
||||
}, [router, getState, parentNavigationHelpers, emitter.emit, onAction]);
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
|
||||
export default function useOnGetState({
|
||||
getStateForRoute,
|
||||
@@ -16,13 +17,23 @@ export default function useOnGetState({
|
||||
|
||||
const getRehydratedState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.map((route) => ({
|
||||
...route,
|
||||
state: getStateForRoute(route.key),
|
||||
})),
|
||||
};
|
||||
|
||||
// Avoid returning new route objects if we don't need to
|
||||
const routes = state.routes.map((route) => {
|
||||
const childState = getStateForRoute(route.key);
|
||||
|
||||
if (route.state === childState) {
|
||||
return route;
|
||||
}
|
||||
|
||||
return { ...route, state: childState };
|
||||
});
|
||||
|
||||
if (isArrayEqual(state.routes, routes)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, routes };
|
||||
}, [getState, getStateForRoute]);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
70
packages/core/src/useOptionsGetters.tsx
Normal file
70
packages/core/src/useOptionsGetters.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
import { NavigationState } from '@react-navigation/routers';
|
||||
|
||||
export default function useOptionsGetters({
|
||||
key,
|
||||
getOptions,
|
||||
getState,
|
||||
}: {
|
||||
key?: string;
|
||||
getOptions?: () => object | undefined;
|
||||
getState?: () => NavigationState;
|
||||
}) {
|
||||
const optionsGettersFromChild = React.useRef<
|
||||
Record<string, (() => object | undefined | null) | undefined>
|
||||
>({});
|
||||
|
||||
const { addOptionsGetter: parentAddOptionsGetter } = React.useContext(
|
||||
NavigationStateContext
|
||||
);
|
||||
|
||||
const getOptionsFromListener = React.useCallback(() => {
|
||||
for (let key in optionsGettersFromChild.current) {
|
||||
if (optionsGettersFromChild.current.hasOwnProperty(key)) {
|
||||
const result = optionsGettersFromChild.current[key]?.();
|
||||
// null means unfocused route
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
const getCurrentOptions = React.useCallback(() => {
|
||||
if (getState) {
|
||||
const state = getState();
|
||||
if (state.routes[state.index].key !== key) {
|
||||
// null means unfocused route
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const optionsFromListener = getOptionsFromListener();
|
||||
if (optionsFromListener !== null) {
|
||||
return optionsFromListener;
|
||||
}
|
||||
return getOptions?.() ?? undefined;
|
||||
}, [getState, getOptionsFromListener, getOptions, key]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return parentAddOptionsGetter?.(key!, getCurrentOptions);
|
||||
}, [getCurrentOptions, parentAddOptionsGetter, key]);
|
||||
|
||||
const addOptionsGetter = React.useCallback(
|
||||
(key: string, getter: () => object | undefined | null) => {
|
||||
optionsGettersFromChild.current[key] = getter;
|
||||
|
||||
return () => {
|
||||
optionsGettersFromChild.current[key] = undefined;
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
addOptionsGetter,
|
||||
getCurrentOptions,
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,15 @@ const UNINTIALIZED_STATE = {};
|
||||
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
||||
const isSchedulingRef = React.useRef(false);
|
||||
const isMountedRef = React.useRef(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (stateRef.current === UNINTIALIZED_STATE) {
|
||||
stateRef.current =
|
||||
@@ -20,7 +29,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||
const getState = React.useCallback(() => stateRef.current, []);
|
||||
|
||||
const setState = React.useCallback((state: T) => {
|
||||
if (state === stateRef.current) {
|
||||
if (state === stateRef.current || !isMountedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +51,10 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||
}, []);
|
||||
|
||||
const flushUpdates = React.useCallback(() => {
|
||||
if (!isMountedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that the tracking state is up-to-date.
|
||||
// We call it unconditionally, but React should skip the update if state is unchanged.
|
||||
setTrackingState(stateRef.current);
|
||||
|
||||
@@ -3,6 +3,164 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.8.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.8.1...@react-navigation/drawer@5.8.2) (2020-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* typo on drawerPosition default props ([#8357](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8357)) ([762cc44](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/762cc4457842182189eeac84aedbb88169452e1e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.8.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.8.0...@react-navigation/drawer@5.8.1) (2020-05-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.8.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.7...@react-navigation/drawer@5.8.0) (2020-05-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update react-native-safe-area-context to 1.0.0 ([#8182](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8182)) ([d62fbfe](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/d62fbfe255140f16b182e8b54b276a7c96f2aec6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.7](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.6...@react-navigation/drawer@5.7.7) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.5...@react-navigation/drawer@5.7.6) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.4...@react-navigation/drawer@5.7.5) (2020-05-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.3...@react-navigation/drawer@5.7.4) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.2...@react-navigation/drawer@5.7.3) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.0...@react-navigation/drawer@5.7.1) (2020-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.4...@react-navigation/drawer@5.7.0) (2020-05-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7971)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.3...@react-navigation/drawer@5.6.4) (2020-05-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.2...@react-navigation/drawer@5.6.3) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.1...@react-navigation/drawer@5.6.2) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.0...@react-navigation/drawer@5.6.1) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.1...@react-navigation/drawer@5.6.0) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix closing drawer on web with tap on overlay ([70be3f6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/70be3f6d863c56211e2f90bdf743bd8526338248))
|
||||
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.0...@react-navigation/drawer@5.5.1) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.1...@react-navigation/drawer@5.5.0) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.5.0",
|
||||
"version": "5.8.2",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -20,11 +20,13 @@
|
||||
"homepage": "https://reactnavigation.org/docs/drawer-navigator.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -39,17 +41,17 @@
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@react-navigation/native": "^5.5.1",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-native": "^0.62.7",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-reanimated": "^1.8.0",
|
||||
"react-native-safe-area-context": "^1.0.0",
|
||||
"react-native-screens": "^2.7.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -25,6 +25,7 @@ export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
||||
export type {
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationProp,
|
||||
DrawerScreenProps,
|
||||
DrawerContentOptions,
|
||||
DrawerContentComponentProps,
|
||||
} from './types';
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
NavigationHelpers,
|
||||
DrawerNavigationState,
|
||||
DrawerActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
|
||||
|
||||
export type Scene = {
|
||||
route: Route<string>;
|
||||
@@ -32,6 +33,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
edgeWidth?: number;
|
||||
/**
|
||||
@@ -58,8 +60,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
statusBarAnimation?: 'slide' | 'none' | 'fade';
|
||||
/**
|
||||
* Props to pass to the underlying pan gesture handler.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
|
||||
gestureHandlerProps?: PanGestureHandlerProperties;
|
||||
/**
|
||||
* Whether the screens should render the first time they are accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render all screens on initial render.
|
||||
@@ -113,13 +116,15 @@ export type DrawerNavigationOptions = {
|
||||
* Whether you can use gestures to open or close the drawer.
|
||||
* Setting this to `false` disables swipe gestures as well as tap on overlay to close.
|
||||
* See `swipeEnabled` to disable only the swipe gesture.
|
||||
* Defaults to `true`
|
||||
* Defaults to `true`.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* Whether you can use swipe gestures to open or close the drawer.
|
||||
* Defaults to `true`
|
||||
* Defaults to `true`.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
swipeEnabled?: boolean;
|
||||
|
||||
@@ -204,6 +209,14 @@ export type DrawerNavigationProp<
|
||||
> &
|
||||
DrawerActionHelpers<ParamList>;
|
||||
|
||||
export type DrawerScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
> = {
|
||||
navigation: DrawerNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
};
|
||||
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
|
||||
@@ -10,14 +10,14 @@ import {
|
||||
StyleProp,
|
||||
View,
|
||||
InteractionManager,
|
||||
TouchableWithoutFeedback,
|
||||
} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
TapGestureHandler,
|
||||
State as GestureState,
|
||||
TapGestureHandlerStateChangeEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
import Animated from 'react-native-reanimated';
|
||||
GestureState,
|
||||
} from './GestureHandler';
|
||||
import Overlay from './Overlay';
|
||||
|
||||
const {
|
||||
@@ -79,7 +79,6 @@ type Props = {
|
||||
open: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||
gestureEnabled: boolean;
|
||||
swipeEnabled: boolean;
|
||||
drawerPosition: 'left' | 'right';
|
||||
@@ -101,7 +100,7 @@ type Props = {
|
||||
|
||||
export default class DrawerView extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
|
||||
drawerPosition: I18nManager.isRTL ? 'left' : 'right',
|
||||
drawerType: 'front',
|
||||
gestureEnabled: true,
|
||||
swipeEnabled: Platform.OS !== 'web',
|
||||
@@ -511,28 +510,17 @@ export default class DrawerView extends React.Component<Props> {
|
||||
},
|
||||
]);
|
||||
|
||||
private handleTapStateChange =
|
||||
Platform.OS === 'web'
|
||||
? // FIXME: Drawer doesn't close on Web with the same code that we use for native
|
||||
({ nativeEvent }: TapGestureHandlerStateChangeEvent) => {
|
||||
if (
|
||||
nativeEvent.state === GestureState.END &&
|
||||
nativeEvent.oldState === GestureState.ACTIVE
|
||||
) {
|
||||
this.toggleDrawer(false);
|
||||
}
|
||||
}
|
||||
: event([
|
||||
{
|
||||
nativeEvent: {
|
||||
oldState: (s: Animated.Value<number>) =>
|
||||
cond(
|
||||
eq(s, GestureState.ACTIVE),
|
||||
set(this.manuallyTriggerSpring, TRUE)
|
||||
),
|
||||
},
|
||||
},
|
||||
]);
|
||||
private handleTapStateChange = event([
|
||||
{
|
||||
nativeEvent: {
|
||||
oldState: (s: Animated.Value<number>) =>
|
||||
cond(
|
||||
eq(s, GestureState.ACTIVE),
|
||||
set(this.manuallyTriggerSpring, TRUE)
|
||||
),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
private handleContainerLayout = (e: LayoutChangeEvent) =>
|
||||
this.containerWidth.setValue(e.nativeEvent.layout.width);
|
||||
@@ -579,7 +567,6 @@ export default class DrawerView extends React.Component<Props> {
|
||||
sceneContainerStyle,
|
||||
drawerStyle,
|
||||
overlayStyle,
|
||||
onGestureRef,
|
||||
renderDrawerContent,
|
||||
renderSceneContent,
|
||||
gestureHandlerProps,
|
||||
@@ -624,7 +611,6 @@ export default class DrawerView extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<PanGestureHandler
|
||||
ref={onGestureRef}
|
||||
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
@@ -663,12 +649,20 @@ export default class DrawerView extends React.Component<Props> {
|
||||
</View>
|
||||
{
|
||||
// Disable overlay if sidebar is permanent
|
||||
drawerType === 'permanent' ? null : (
|
||||
drawerType === 'permanent' ? null : Platform.OS === 'web' ? (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={
|
||||
gestureEnabled ? () => this.toggleDrawer(false) : undefined
|
||||
}
|
||||
>
|
||||
<Overlay progress={progress} style={overlayStyle as any} />
|
||||
</TouchableWithoutFeedback>
|
||||
) : (
|
||||
<TapGestureHandler
|
||||
enabled={gestureEnabled}
|
||||
onHandlerStateChange={this.handleTapStateChange}
|
||||
>
|
||||
<Overlay progress={progress} style={overlayStyle} />
|
||||
<Overlay progress={progress} style={overlayStyle as any} />
|
||||
</TapGestureHandler>
|
||||
)
|
||||
}
|
||||
@@ -737,6 +731,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
main: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
...Platform.select({
|
||||
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
|
||||
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
|
||||
web: null,
|
||||
default: { overflow: 'hidden' },
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
Platform,
|
||||
TouchableWithoutFeedbackProps,
|
||||
} from 'react-native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import { Link, useTheme } from '@react-navigation/native';
|
||||
import Color from 'color';
|
||||
import TouchableItem from './TouchableItem';
|
||||
|
||||
@@ -26,6 +28,10 @@ type Props = {
|
||||
size: number;
|
||||
color: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* URL to use for the link to the tab.
|
||||
*/
|
||||
to?: string;
|
||||
/**
|
||||
* Whether to highlight the drawer item as active.
|
||||
*/
|
||||
@@ -60,6 +66,54 @@ type Props = {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
const Touchable = ({
|
||||
children,
|
||||
style,
|
||||
onPress,
|
||||
to,
|
||||
accessibilityRole,
|
||||
delayPressIn,
|
||||
...rest
|
||||
}: TouchableWithoutFeedbackProps & {
|
||||
to?: string;
|
||||
children: React.ReactNode;
|
||||
onPress?: () => void;
|
||||
}) => {
|
||||
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 (
|
||||
<TouchableItem
|
||||
{...rest}
|
||||
accessibilityRole={accessibilityRole}
|
||||
delayPressIn={delayPressIn}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A component used to show an action item with an icon and a label in a navigation drawer.
|
||||
*/
|
||||
@@ -70,6 +124,7 @@ export default function DrawerItem(props: Props) {
|
||||
icon,
|
||||
label,
|
||||
labelStyle,
|
||||
to,
|
||||
focused = false,
|
||||
activeTintColor = colors.primary,
|
||||
inactiveTintColor = Color(colors.text).alpha(0.68).rgb().string(),
|
||||
@@ -94,7 +149,7 @@ export default function DrawerItem(props: Props) {
|
||||
{...rest}
|
||||
style={[styles.container, { borderRadius, backgroundColor }, style]}
|
||||
>
|
||||
<TouchableItem
|
||||
<Touchable
|
||||
delayPressIn={0}
|
||||
onPress={onPress}
|
||||
style={[styles.wrapper, { borderRadius }]}
|
||||
@@ -102,6 +157,7 @@ export default function DrawerItem(props: Props) {
|
||||
accessibilityComponentType="button"
|
||||
accessibilityRole="button"
|
||||
accessibilityStates={focused ? ['selected'] : []}
|
||||
to={to}
|
||||
>
|
||||
<React.Fragment>
|
||||
{iconNode}
|
||||
@@ -129,7 +185,7 @@ export default function DrawerItem(props: Props) {
|
||||
)}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</TouchableItem>
|
||||
</Touchable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -148,4 +204,7 @@ const styles = StyleSheet.create({
|
||||
label: {
|
||||
marginRight: 32,
|
||||
},
|
||||
button: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CommonActions,
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import DrawerItem from './DrawerItem';
|
||||
import {
|
||||
@@ -31,6 +32,8 @@ export default function DrawerItemList({
|
||||
itemStyle,
|
||||
labelStyle,
|
||||
}: Props) {
|
||||
const buildLink = useLinkBuilder();
|
||||
|
||||
return (state.routes.map((route, i) => {
|
||||
const focused = i === state.index;
|
||||
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
|
||||
@@ -53,6 +56,7 @@ export default function DrawerItemList({
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
labelStyle={labelStyle}
|
||||
style={itemStyle}
|
||||
to={buildLink(route.name, route.params)}
|
||||
onPress={() => {
|
||||
navigation.dispatch({
|
||||
...(focused
|
||||
|
||||
@@ -12,16 +12,13 @@ import {
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
GestureHandlerRootView,
|
||||
} from 'react-native-gesture-handler';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
DrawerNavigationState,
|
||||
DrawerActions,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import DrawerGestureContext from '../utils/DrawerGestureContext';
|
||||
import { GestureHandlerRootView } from './GestureHandler';
|
||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import DrawerContent from './DrawerContent';
|
||||
@@ -93,8 +90,6 @@ export default function DrawerView({
|
||||
Dimensions.get('window')
|
||||
);
|
||||
|
||||
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
||||
@@ -201,9 +196,9 @@ export default function DrawerView({
|
||||
const { gestureEnabled, swipeEnabled } = descriptors[activeKey].options;
|
||||
|
||||
return (
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerGestureContext.Provider value={drawerGestureRef}>
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerOpenContext.Provider value={isDrawerOpen}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
@@ -211,10 +206,6 @@ export default function DrawerView({
|
||||
swipeEnabled={swipeEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={(ref) => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
@@ -247,13 +238,12 @@ export default function DrawerView({
|
||||
renderDrawerContent={renderNavigationView}
|
||||
renderSceneContent={renderContent}
|
||||
keyboardDismissMode={keyboardDismissMode}
|
||||
drawerPostion={drawerPosition}
|
||||
dimensions={dimensions}
|
||||
/>
|
||||
</DrawerOpenContext.Provider>
|
||||
</DrawerGestureContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
23
packages/drawer/src/views/GestureHandler.native.tsx
Normal file
23
packages/drawer/src/views/GestureHandler.native.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
PanGestureHandler as PanGestureHandlerNative,
|
||||
PanGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
import DrawerGestureContext from '../utils/DrawerGestureContext';
|
||||
|
||||
export function PanGestureHandler(props: PanGestureHandlerProperties) {
|
||||
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
|
||||
|
||||
return (
|
||||
<DrawerGestureContext.Provider value={gestureRef}>
|
||||
<PanGestureHandlerNative {...props} />
|
||||
</DrawerGestureContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
GestureHandlerRootView,
|
||||
TapGestureHandler,
|
||||
State as GestureState,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
31
packages/drawer/src/views/GestureHandler.tsx
Normal file
31
packages/drawer/src/views/GestureHandler.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type {
|
||||
PanGestureHandlerProperties,
|
||||
TapGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
|
||||
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
export const PanGestureHandler = Dummy as React.ComponentType<
|
||||
PanGestureHandlerProperties
|
||||
>;
|
||||
|
||||
export const TapGestureHandler = Dummy as React.ComponentType<
|
||||
TapGestureHandlerProperties
|
||||
>;
|
||||
|
||||
export const GestureHandlerRootView = View;
|
||||
|
||||
export const GestureState = {
|
||||
UNDETERMINED: 0,
|
||||
FAILED: 1,
|
||||
BEGAN: 2,
|
||||
CANCELLED: 3,
|
||||
ACTIVE: 4,
|
||||
END: 5,
|
||||
};
|
||||
|
||||
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
|
||||
@@ -29,7 +29,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
: { overflow: 'hidden' },
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
@@ -52,7 +52,6 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
attached: {
|
||||
flex: 1,
|
||||
|
||||
@@ -3,6 +3,152 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.9...@react-navigation/material-bottom-tabs@5.2.10) (2020-06-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.8...@react-navigation/material-bottom-tabs@5.2.9) (2020-05-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.7...@react-navigation/material-bottom-tabs@5.2.8) (2020-05-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.6...@react-navigation/material-bottom-tabs@5.2.7) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.5...@react-navigation/material-bottom-tabs@5.2.6) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.4...@react-navigation/material-bottom-tabs@5.2.5) (2020-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* center icons in material tab bar. fixes [#8248](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8248) ([51b4087](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/51b40879bdb9cea5462a2291955513a88eb87340))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.3...@react-navigation/material-bottom-tabs@5.2.4) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.2...@react-navigation/material-bottom-tabs@5.2.3) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.0...@react-navigation/material-bottom-tabs@5.2.1) (2020-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.15...@react-navigation/material-bottom-tabs@5.2.0) (2020-05-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/7971)
|
||||
* use links in bottom navigation tabs ([f384706](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/f384706741f7e2422c284b65da10425f7af680c0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.14...@react-navigation/material-bottom-tabs@5.1.15) (2020-05-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.13...@react-navigation/material-bottom-tabs@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.12...@react-navigation/material-bottom-tabs@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.11...@react-navigation/material-bottom-tabs@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.10...@react-navigation/material-bottom-tabs@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.9...@react-navigation/material-bottom-tabs@5.1.10) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.8...@react-navigation/material-bottom-tabs@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.1.9",
|
||||
"version": "5.2.10",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -20,11 +20,13 @@
|
||||
"homepage": "https://reactnavigation.org/docs/material-bottom-tab-navigator.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -35,15 +37,15 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@react-navigation/native": "^5.5.1",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-native": "^0.62.7",
|
||||
"@types/react-native-vector-icons": "^6.4.5",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-paper": "^3.7.0",
|
||||
"react-native-paper": "^3.10.1",
|
||||
"react-native-vector-icons": "^6.6.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
|
||||
@@ -14,4 +14,5 @@ export { default as MaterialBottomTabView } from './views/MaterialBottomTabView'
|
||||
export type {
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationProp,
|
||||
MaterialBottomTabScreenProps,
|
||||
} from './types';
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
NavigationHelpers,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export type MaterialBottomTabNavigationEventMap = {
|
||||
@@ -32,6 +33,14 @@ export type MaterialBottomTabNavigationProp<
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type MaterialBottomTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
> = {
|
||||
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
};
|
||||
|
||||
export type MaterialBottomTabNavigationOptions = {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
Route,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
Link,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import {
|
||||
@@ -23,13 +26,14 @@ type Props = MaterialBottomTabNavigationConfig & {
|
||||
|
||||
type Scene = { route: { key: string } };
|
||||
|
||||
export default function MaterialBottomTabView({
|
||||
function MaterialBottomTabViewInner({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { dark, colors } = useTheme();
|
||||
const buildLink = useLinkBuilder();
|
||||
|
||||
const theme = React.useMemo(() => {
|
||||
const t = dark ? DarkTheme : DefaultTheme;
|
||||
@@ -56,6 +60,39 @@ export default function MaterialBottomTabView({
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
renderTouchable={
|
||||
Platform.OS === 'web'
|
||||
? ({
|
||||
onPress,
|
||||
route,
|
||||
accessibilityRole: _0,
|
||||
borderless: _1,
|
||||
centered: _2,
|
||||
rippleColor: _3,
|
||||
style,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Link
|
||||
{...rest}
|
||||
// @ts-ignore
|
||||
to={buildLink(route.name, route.params)}
|
||||
accessibilityRole="link"
|
||||
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);
|
||||
}
|
||||
}}
|
||||
style={[styles.touchable, style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderIcon={({ route, focused, color }) => {
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
@@ -66,8 +103,6 @@ export default function MaterialBottomTabView({
|
||||
color={color}
|
||||
size={24}
|
||||
style={styles.icon}
|
||||
importantForAccessibility="no-hide-descendants"
|
||||
accessibilityElementsHidden
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -108,8 +143,20 @@ export default function MaterialBottomTabView({
|
||||
);
|
||||
}
|
||||
|
||||
export default function MaterialBottomTabView(props: Props) {
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={props.navigation}>
|
||||
<MaterialBottomTabViewInner {...props} />
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
touchable: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,148 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.9...@react-navigation/material-top-tabs@5.2.10) (2020-06-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.8...@react-navigation/material-top-tabs@5.2.9) (2020-05-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.7...@react-navigation/material-top-tabs@5.2.8) (2020-05-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.6...@react-navigation/material-top-tabs@5.2.7) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.5...@react-navigation/material-top-tabs@5.2.6) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.4...@react-navigation/material-top-tabs@5.2.5) (2020-05-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.3...@react-navigation/material-top-tabs@5.2.4) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.2...@react-navigation/material-top-tabs@5.2.3) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.0...@react-navigation/material-top-tabs@5.2.1) (2020-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.15...@react-navigation/material-top-tabs@5.2.0) (2020-05-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/7971)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.14...@react-navigation/material-top-tabs@5.1.15) (2020-05-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.13...@react-navigation/material-top-tabs@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.12...@react-navigation/material-top-tabs@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.11...@react-navigation/material-top-tabs@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.10...@react-navigation/material-top-tabs@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.9...@react-navigation/material-top-tabs@5.1.10) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.8...@react-navigation/material-top-tabs@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.1.9",
|
||||
"version": "5.2.10",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -20,11 +20,13 @@
|
||||
"homepage": "https://reactnavigation.org/docs/material-top-tab-navigator.html",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
"module": "lib/module/index.js",
|
||||
"types": "lib/typescript/src/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"lib"
|
||||
"lib",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -38,15 +40,15 @@
|
||||
"color": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@react-native-community/bob": "^0.14.3",
|
||||
"@react-navigation/native": "^5.5.1",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-native": "^0.62.7",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-reanimated": "^1.8.0",
|
||||
"react-native-tab-view": "^2.14.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user