mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
54 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f274058b90 | ||
|
|
976178d098 | ||
|
|
493956ef71 | ||
|
|
699ea0cc50 | ||
|
|
a63f9da8c1 | ||
|
|
cceaa6780d | ||
|
|
4b8155386b | ||
|
|
1a757fc30a | ||
|
|
7b353a4aea | ||
|
|
3728390b60 | ||
|
|
a8342aaf3d | ||
|
|
860adbfd8b | ||
|
|
38d680833e | ||
|
|
cae115fc17 | ||
|
|
87b51476d0 | ||
|
|
b1b211855f | ||
|
|
60fe0dbb0a | ||
|
|
bb294b16f9 | ||
|
|
4ca2d2d22b | ||
|
|
35747a6066 | ||
|
|
bae4019995 | ||
|
|
d3a9639060 | ||
|
|
d88cbcb52d | ||
|
|
dc7e876b6f | ||
|
|
1e215614d8 | ||
|
|
dd87fa49a4 | ||
|
|
09f0ebbb0f | ||
|
|
9633c4d35f | ||
|
|
28fac3e0b9 | ||
|
|
a8b8c27174 | ||
|
|
b19f76bfff | ||
|
|
365a2ad28c | ||
|
|
b26b90706f | ||
|
|
47f28558d6 | ||
|
|
26074a28f7 | ||
|
|
6fe1d70c6c | ||
|
|
77fa6fb683 | ||
|
|
2ad61a6735 | ||
|
|
c9a5d45324 | ||
|
|
3c874191ff | ||
|
|
2317633652 | ||
|
|
74d368eb4d | ||
|
|
d617ab82f9 | ||
|
|
f5fd0e5be4 | ||
|
|
7bef138e3d | ||
|
|
1406eb83ed | ||
|
|
3e069b718d | ||
|
|
7754eb450f | ||
|
|
95b2599877 | ||
|
|
efcfa7121f | ||
|
|
a8e27ef448 | ||
|
|
946d2923d7 | ||
|
|
794339eeed | ||
|
|
53141a6436 |
@@ -3,7 +3,7 @@ version: 2.1
|
||||
executors:
|
||||
default:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/project
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||
@@ -36,12 +36,10 @@ jobs:
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
paths: ~/.cache/yarn
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- .
|
||||
paths: .
|
||||
|
||||
lint-and-typecheck:
|
||||
executor: default
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"@react-navigation/core",
|
||||
"@react-navigation/native",
|
||||
"@react-navigation/routers",
|
||||
"@react-navigation/compat",
|
||||
"@react-navigation/stack",
|
||||
"@react-navigation/drawer",
|
||||
"@react-navigation/bottom-tabs",
|
||||
"@react-navigation/material-top-tabs",
|
||||
"@react-navigation/material-bottom-tabs",
|
||||
"@react-navigation/elements",
|
||||
"@react-navigation/devtools"
|
||||
]
|
||||
},
|
||||
|
||||
72
.github/workflows/check-repro.yml
vendored
72
.github/workflows/check-repro.yml
vendored
@@ -23,54 +23,26 @@ jobs:
|
||||
'gm'
|
||||
);
|
||||
|
||||
if (regex.test(body)) {
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['repro provided'],
|
||||
});
|
||||
|
||||
try {
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'needs repro',
|
||||
});
|
||||
} catch (error) {
|
||||
if (!/Label does not exist/.test(error.message)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (context.eventName !== 'issues') {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = "Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a [snack.expo.io](https://snack.expo.io) link or link to a GitHub repo under your username).\n\nCan you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.";
|
||||
|
||||
const comments = await github.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
if (comments.data.some(comment => comment.body === body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body,
|
||||
});
|
||||
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['needs repro'],
|
||||
});
|
||||
if (!regex.test(body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['repro provided'],
|
||||
});
|
||||
|
||||
try {
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'needs repro',
|
||||
});
|
||||
} catch (error) {
|
||||
if (!/Label does not exist/.test(error.message)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
8
.github/workflows/expo-preview.yml
vendored
8
.github/workflows/expo-preview.yml
vendored
@@ -13,12 +13,14 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 10.x
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@v5
|
||||
with:
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
expo-version: 3.x
|
||||
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
|
||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||
expo-cache: true
|
||||
|
||||
- name: Restore yarn cache
|
||||
@@ -34,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Publish Expo app
|
||||
working-directory: ./example
|
||||
run: yarn expo publish --release-channel=pr-${{ github.event.number }}
|
||||
run: expo publish --release-channel=pr-${{ github.event.number }}
|
||||
env:
|
||||
EXPO_USE_DEV_SERVER: true
|
||||
|
||||
|
||||
8
.github/workflows/expo.yml
vendored
8
.github/workflows/expo.yml
vendored
@@ -16,12 +16,14 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 10.x
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@v5
|
||||
with:
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
expo-version: 3.x
|
||||
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
|
||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||
expo-cache: true
|
||||
|
||||
- name: Restore yarn cache
|
||||
@@ -37,4 +39,4 @@ jobs:
|
||||
|
||||
- name: Publish Expo app
|
||||
working-directory: ./example
|
||||
run: yarn expo publish
|
||||
run: expo publish
|
||||
|
||||
10
.github/workflows/first-pull-request.yml
vendored
10
.github/workflows/first-pull-request.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: First pull request
|
||||
on: pull_request_target
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
@@ -12,13 +12,13 @@ jobs:
|
||||
// Get a list of all issues created by the PR opener
|
||||
// See: https://octokit.github.io/rest.js/#pagination
|
||||
const creator = context.payload.sender.login;
|
||||
const options = github.issues.listForRepo.endpoint.merge({
|
||||
const opts = github.issues.listForRepo.endpoint.merge({
|
||||
...context.issue,
|
||||
creator,
|
||||
state: 'all'
|
||||
});
|
||||
|
||||
const issues = await github.paginate(options);
|
||||
const issues = await github.paginate(opts);
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.number === context.issue.number) {
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
}
|
||||
|
||||
if (issue.pull_request) {
|
||||
return; // Creator is already a contributor.
|
||||
return ;// Creator is already a contributor.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@ jobs:
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Hey ${creator}! Thanks for opening your first pull request in this repo. If you haven't already, make sure to read our [contribution guidelines](https://github.com/react-navigation/react-navigation/blob/main/CONTRIBUTING.md).`
|
||||
body: "Hey ${creator}! Thanks for opening the pull request. If you haven't already, make sure to read our [contribution guidelines](https://github.com/react-navigation/react-navigation/blob/main/CONTRIBUTING.md)."
|
||||
});
|
||||
|
||||
15
.github/workflows/sponsor.yml
vendored
15
.github/workflows/sponsor.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Label sponsors
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: is-sponsor-label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JasonEtco/is-sponsor-label-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
21
.github/workflows/stale.yml
vendored
21
.github/workflows/stale.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Close stale issues and PRs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
any-of-labels: 'needs more info,needs repro,needs response'
|
||||
exempt-issue-labels: 'repro provided,keep open'
|
||||
exempt-pr-labels: 'keep open'
|
||||
stale-issue-label: 'stale'
|
||||
stale-pr-label: 'stale'
|
||||
stale-issue-message: 'Hello 👋, this issue has been open for more than a month without a repro or any activity. If the issue is still present in the latest version, please provide a repro or leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution or workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix it.'
|
||||
stale-pr-message: 'Hello 👋, this pull request has been open for more than a month with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.'
|
||||
2
.github/workflows/triage.yml
vendored
2
.github/workflows/triage.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.\n\nThe easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then please provide the repro in a GitHub repository."
|
||||
body: "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible.\n\nThe easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then please provide the repro in a GitHub repository."
|
||||
})
|
||||
|
||||
question:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,7 +26,6 @@ build
|
||||
|
||||
npm-debug.*
|
||||
|
||||
*.tsbuildinfo
|
||||
*.log
|
||||
*.jks
|
||||
*.p8
|
||||
@@ -34,5 +33,3 @@ npm-debug.*
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
|
||||
*.iml
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { Assets } from '@react-navigation/elements';
|
||||
import { Assets as StackAssets } from '@react-navigation/stack';
|
||||
|
||||
import App from './src/index';
|
||||
|
||||
Asset.loadAsync(Assets);
|
||||
Asset.loadAsync(StackAssets);
|
||||
|
||||
registerRootComponent(App);
|
||||
|
||||
@@ -2,6 +2,5 @@ module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,41 +4,41 @@ beforeEach(async () => {
|
||||
await page.click('[data-testid=LinkComponent]');
|
||||
});
|
||||
|
||||
const getPageInfo = async () => ({
|
||||
url: await page.evaluate(() => location.pathname + location.search),
|
||||
title: await page.evaluate(() => document.title),
|
||||
heading: (await page.accessibility.snapshot())?.children?.find(
|
||||
(it) => it.role === 'heading'
|
||||
)?.name,
|
||||
});
|
||||
|
||||
it('loads the article page', async () => {
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
|
||||
expect(url).toBe('/link-component/article/gandalf');
|
||||
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||
expect(heading).toBe('Article by Gandalf');
|
||||
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"]');
|
||||
|
||||
{
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/music'
|
||||
);
|
||||
|
||||
expect(url).toBe('/link-component/music');
|
||||
expect(title).toBe('Albums - React Navigation Example');
|
||||
expect(heading).toBe('Albums');
|
||||
}
|
||||
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();
|
||||
|
||||
{
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/article/gandalf'
|
||||
);
|
||||
|
||||
expect(url).toBe('/link-component/article/gandalf');
|
||||
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||
expect(heading).toBe('Article by Gandalf');
|
||||
}
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Article by Gandalf');
|
||||
});
|
||||
|
||||
@@ -21,8 +21,4 @@ beforeEach(async () => {
|
||||
await page.goto('http://localhost:3579');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
export { browser, context, page };
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -80,4 +80,13 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
transformer: {
|
||||
getTransformOptions: () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,54 +13,51 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-masked-view/masked-view": "~0.2.4",
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-async-storage/async-storage": "^1.13.1",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"color": "^3.1.3",
|
||||
"expo": "^41.0.1",
|
||||
"expo-asset": "~8.3.1",
|
||||
"expo-blur": "~9.0.3",
|
||||
"expo-linking": "~2.2.3",
|
||||
"expo-updates": "~0.5.4",
|
||||
"expo": "^39.0.0",
|
||||
"expo-asset": "~8.2.0",
|
||||
"expo-blur": "~8.2.0",
|
||||
"expo-linking": "^1.0.4",
|
||||
"expo-updates": "~0.3.5",
|
||||
"koa": "^2.13.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
|
||||
"react": "~16.13.1",
|
||||
"react-dom": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-appearance": "~0.3.3",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-pager-view": "~5.0.12",
|
||||
"react-native-paper": "^4.7.2",
|
||||
"react-native-reanimated": "~2.1.0",
|
||||
"react-native-safe-area-context": "~3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"react-native-tab-view": "^3.0.1",
|
||||
"react-native-vector-icons": "^8.1.0",
|
||||
"react-native-web": "~0.15.0"
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-paper": "^4.2.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"react-native-vector-icons": "^7.0.0",
|
||||
"react-native-web": "^0.13.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/node": "^7.13.13",
|
||||
"@expo/webpack-config": "~0.12.63",
|
||||
"@types/cheerio": "^0.22.28",
|
||||
"@babel/node": "^7.12.1",
|
||||
"@expo/webpack-config": "^0.12.40",
|
||||
"@types/cheerio": "^0.22.22",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/koa": "^2.13.1",
|
||||
"@types/mock-require": "^2.0.0",
|
||||
"@types/node-fetch": "^2.5.9",
|
||||
"@types/react": "~16.9.35",
|
||||
"@types/react-dom": "~16.9.8",
|
||||
"@types/react-native": "~0.63.2",
|
||||
"babel-loader": "^8.2.2",
|
||||
"@types/koa": "^2.11.6",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "~16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-native": "~0.63.30",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-preset-expo": "8.3.0",
|
||||
"babel-preset-expo": "^8.3.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"expo-cli": "^4.4.4",
|
||||
"jest": "^26.6.3",
|
||||
"expo-cli": "^3.28.2",
|
||||
"jest": "^26.6.1",
|
||||
"jest-dev-server": "^4.4.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"mock-require-assets": "^0.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^2.0.6",
|
||||
"playwright": "^1.10.0",
|
||||
"playwright": "^1.9.1",
|
||||
"serve": "^11.3.0",
|
||||
"typescript": "~4.2.3"
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ module.exports = {
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['..'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json'],
|
||||
alias: {
|
||||
'react-native': 'react-native-web',
|
||||
...alias,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'mock-require-assets';
|
||||
|
||||
import mock from 'mock-require';
|
||||
import Module from 'module';
|
||||
|
||||
// We need to make sure that .web.xx extensions are resolved before .xx
|
||||
@@ -11,14 +10,3 @@ Module._extensions = Object.fromEntries(
|
||||
return b[0].split('.').length - a[0].split('.').length;
|
||||
})
|
||||
);
|
||||
|
||||
// Set __DEV__ that expo needs
|
||||
// @ts-expect-error
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
// Reanimated doesn't support SSR :(
|
||||
mock(
|
||||
'react-native-reanimated',
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
{ ...require('react-native-reanimated/mock').default, call() {} }
|
||||
);
|
||||
|
||||
2
example/src/LinkingPrefixes.expo.ts
Normal file
2
example/src/LinkingPrefixes.expo.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import * as Linking from 'expo-linking';
|
||||
export default [Linking.makeUrl('/')];
|
||||
1
example/src/LinkingPrefixes.ts
Normal file
1
example/src/LinkingPrefixes.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default ['rne://127.0.0.1:19000/--/'];
|
||||
@@ -4,9 +4,9 @@ import { Title, Button } from 'react-native-paper';
|
||||
import { useTheme, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
HeaderBackButton,
|
||||
StackScreenProps,
|
||||
} from '@react-navigation/stack';
|
||||
import { HeaderBackButton } from '@react-navigation/elements';
|
||||
|
||||
type AuthStackParams = {
|
||||
Splash: undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StyleSheet } from 'react-native';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import {
|
||||
getFocusedRouteNameFromRoute,
|
||||
ParamListBase,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
import {
|
||||
createBottomTabNavigator,
|
||||
useBottomTabBarHeight,
|
||||
BottomTabScreenProps,
|
||||
} from '@react-navigation/bottom-tabs';
|
||||
import { HeaderBackButton } from '@react-navigation/elements';
|
||||
import TouchableBounce from '../Shared/TouchableBounce';
|
||||
import Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
import Chat from '../Shared/Chat';
|
||||
@@ -27,18 +27,36 @@ const getTabBarIcon = (name: string) => ({
|
||||
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
|
||||
|
||||
type BottomTabParams = {
|
||||
TabStack: NavigatorScreenParams<SimpleStackParams>;
|
||||
TabAlbums: undefined;
|
||||
TabContacts: undefined;
|
||||
TabChat: undefined;
|
||||
Article: NavigatorScreenParams<SimpleStackParams>;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const AlbumsScreen = () => {
|
||||
const tabBarHeight = useBottomTabBarHeight();
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: BottomTabScreenProps<BottomTabParams>) => {
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: tabBarHeight }}>
|
||||
<Albums scrollEnabled={false} />
|
||||
<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>
|
||||
);
|
||||
};
|
||||
@@ -53,7 +71,6 @@ export default function BottomTabsScreen({
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
title: routeName,
|
||||
});
|
||||
}, [navigation, routeName]);
|
||||
@@ -61,21 +78,28 @@ export default function BottomTabsScreen({
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
headerLeft: (props) => (
|
||||
<HeaderBackButton {...props} onPress={navigation.goBack} />
|
||||
),
|
||||
tabBarButton:
|
||||
Platform.OS === 'web'
|
||||
? undefined
|
||||
: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
name="TabStack"
|
||||
component={SimpleStackScreen}
|
||||
name="Article"
|
||||
options={{
|
||||
title: 'Article',
|
||||
tabBarIcon: getTabBarIcon('file-document'),
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{(props) => (
|
||||
<SimpleStackScreen
|
||||
{...props}
|
||||
screenOptions={{ headerShown: false }}
|
||||
/>
|
||||
)}
|
||||
</BottomTabs.Screen>
|
||||
<BottomTabs.Screen
|
||||
name="TabChat"
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -84,7 +108,7 @@ export default function BottomTabsScreen({
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="TabContacts"
|
||||
name="Contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
title: 'Contacts',
|
||||
@@ -92,21 +116,23 @@ export default function BottomTabsScreen({
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="TabAlbums"
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
tabBarIcon: getTabBarIcon('image-album'),
|
||||
tabBarStyle: { position: 'absolute' },
|
||||
tabBarBackground: () => (
|
||||
<BlurView
|
||||
tint="light"
|
||||
intensity={100}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
|
||||
164
example/src/Screens/CompatAPI.tsx
Normal file
164
example/src/Screens/CompatAPI.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import * as React from 'react';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import {
|
||||
createCompatNavigatorFactory,
|
||||
CompatScreenType,
|
||||
} from '@react-navigation/compat';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackNavigationProp,
|
||||
StackScreenProps,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
type CompatStackParams = {
|
||||
Albums: undefined;
|
||||
Nested: { author: string };
|
||||
};
|
||||
|
||||
type NestedStackParams = {
|
||||
Feed: undefined;
|
||||
Article: { author: string };
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push nested
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
NestedStackParams,
|
||||
'Article'
|
||||
>> = ({ navigation }) => {
|
||||
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>
|
||||
>(
|
||||
{
|
||||
Albums: AlbumsScreen,
|
||||
Nested: {
|
||||
screen: createCompatStackNavigator<
|
||||
StackNavigationProp<NestedStackParams>
|
||||
>(
|
||||
{
|
||||
Feed: { getScreen: () => FeedScreen },
|
||||
Article: { getScreen: () => ArticleScreen },
|
||||
},
|
||||
{ navigationOptions: { headerShown: false } }
|
||||
),
|
||||
params: {
|
||||
author: 'Gandalf',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
}
|
||||
);
|
||||
|
||||
export default function CompatStackScreen({
|
||||
navigation,
|
||||
}: StackScreenProps<{}>) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return <CompatStack />;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -24,8 +24,7 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
const LinkButton = ({
|
||||
to,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof Button> &
|
||||
Parameters<typeof useLinkProps>[0]) => {
|
||||
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
||||
const props = useLinkProps({ to });
|
||||
|
||||
return <Button {...props} {...rest} />;
|
||||
@@ -58,13 +57,6 @@ const ArticleScreen = ({
|
||||
>
|
||||
Go to /link-component/music
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
to={{ screen: 'Home' }}
|
||||
mode="contained"
|
||||
style={styles.button}
|
||||
>
|
||||
Go to /
|
||||
</LinkButton>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DrawerScreenProps,
|
||||
DrawerContent,
|
||||
DrawerContentComponentProps,
|
||||
DrawerContentOptions,
|
||||
} from '@react-navigation/drawer';
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
@@ -90,7 +91,9 @@ const AlbumsScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const CustomDrawerContent = (props: DrawerContentComponentProps) => {
|
||||
const CustomDrawerContent = (
|
||||
props: DrawerContentComponentProps<DrawerContentOptions>
|
||||
) => {
|
||||
const { colors } = useTheme();
|
||||
const navigation = useNavigation();
|
||||
|
||||
@@ -123,14 +126,10 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
openByDefault
|
||||
drawerType={isLargeScreen ? 'permanent' : 'back'}
|
||||
drawerStyle={isLargeScreen ? null : { width: '100%' }}
|
||||
overlayColor="transparent"
|
||||
drawerContent={(props) => <CustomDrawerContent {...props} />}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
drawerType: isLargeScreen ? 'permanent' : 'back',
|
||||
drawerStyle: isLargeScreen ? null : { width: '100%' },
|
||||
drawerContentContainerStyle: { paddingTop: 4 },
|
||||
overlayColor: 'transparent',
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Drawer.Screen name="Article" component={ArticleScreen} />
|
||||
|
||||
@@ -8,22 +8,24 @@ import Chat from '../Shared/Chat';
|
||||
import SimpleStackScreen, { SimpleStackParams } from './SimpleStack';
|
||||
|
||||
type MaterialBottomTabParams = {
|
||||
TabStack: NavigatorScreenParams<SimpleStackParams>;
|
||||
TabAlbums: undefined;
|
||||
TabContacts: undefined;
|
||||
TabChat: undefined;
|
||||
Article: NavigatorScreenParams<SimpleStackParams>;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const MaterialBottomTabs = createMaterialBottomTabNavigator<MaterialBottomTabParams>();
|
||||
const MaterialBottomTabs = createMaterialBottomTabNavigator<
|
||||
MaterialBottomTabParams
|
||||
>();
|
||||
|
||||
export default function MaterialBottomTabsScreen() {
|
||||
return (
|
||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="TabStack"
|
||||
name="Article"
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: 'file-document',
|
||||
tabBarIcon: 'file-document-box',
|
||||
tabBarColor: '#C9E7F8',
|
||||
}}
|
||||
>
|
||||
@@ -35,7 +37,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
)}
|
||||
</MaterialBottomTabs.Screen>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="TabChat"
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -45,7 +47,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="TabContacts"
|
||||
name="Contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
tabBarLabel: 'Contacts',
|
||||
@@ -54,7 +56,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="TabAlbums"
|
||||
name="Albums"
|
||||
component={Albums}
|
||||
options={{
|
||||
tabBarLabel: 'Albums',
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import type { ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
TransitionPresets,
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
export type SimpleStackParams = {
|
||||
Article: { author: string } | undefined;
|
||||
NewsFeed: { date: number };
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('NewsFeed', { date: Date.now() })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push feed
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: route.params?.author ?? 'Unknown' }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const NewsFeedScreen = ({
|
||||
route,
|
||||
navigation,
|
||||
}: StackScreenProps<SimpleStackParams, 'NewsFeed'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Navigate to album
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} date={route.params.date} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: StackScreenProps<SimpleStackParams, 'Albums'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
export default function SimpleStackScreen({
|
||||
navigation,
|
||||
}: StackScreenProps<ParamListBase>) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator
|
||||
screenOptions={{
|
||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||
}}
|
||||
>
|
||||
<SimpleStack.Group
|
||||
screenOptions={{
|
||||
...TransitionPresets.SlideFromRightIOS,
|
||||
headerMode: 'float',
|
||||
}}
|
||||
>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author ?? 'Unknown'}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="NewsFeed"
|
||||
component={NewsFeedScreen}
|
||||
options={{ title: 'Feed' }}
|
||||
/>
|
||||
</SimpleStack.Group>
|
||||
<SimpleStack.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
headerMode: 'screen',
|
||||
title: 'Albums',
|
||||
}}
|
||||
/>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,136 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import type { ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type MixedStackParams = {
|
||||
Article: { author: string };
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: StackScreenProps<MixedStackParams, 'Article'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<View>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Dalek' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({ navigation }: StackScreenProps<MixedStackParams>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<View>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'The Doctor' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const MixedStack = createStackNavigator<MixedStackParams>();
|
||||
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
|
||||
export default function MixedStackScreen({ navigation }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<MixedStack.Navigator>
|
||||
<MixedStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params.author}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<MixedStack.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
presentation: 'modal',
|
||||
}}
|
||||
/>
|
||||
</MixedStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -5,6 +5,8 @@ import type { ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
StackNavigationOptions,
|
||||
TransitionPresets,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
@@ -70,11 +72,13 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<ModalStackParams>) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ModalStack = createStackNavigator<ModalStackParams>();
|
||||
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
|
||||
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
type Props = StackScreenProps<ParamListBase> & {
|
||||
options?: StackNavigationOptions;
|
||||
};
|
||||
|
||||
export default function ModalStackScreen({ navigation }: Props) {
|
||||
export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -82,8 +86,22 @@ export default function ModalStackScreen({ navigation }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<ModalStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||
<ModalStack.Screen
|
||||
<ModalPresentationStack.Navigator
|
||||
mode="modal"
|
||||
screenOptions={({ route, navigation }) => ({
|
||||
...TransitionPresets.ModalPresentationIOS,
|
||||
cardOverlayEnabled: true,
|
||||
gestureEnabled: true,
|
||||
headerStatusBarHeight:
|
||||
navigation
|
||||
.dangerouslyGetState()
|
||||
.routes.findIndex((r: any) => r.key === route.key) > 0
|
||||
? 0
|
||||
: undefined,
|
||||
})}
|
||||
{...options}
|
||||
>
|
||||
<ModalPresentationStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
@@ -91,12 +109,12 @@ export default function ModalStackScreen({ navigation }: Props) {
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
<ModalPresentationStack.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</ModalStack.Navigator>
|
||||
</ModalPresentationStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Button } from 'react-native-paper';
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
|
||||
const NotFoundScreen = ({
|
||||
route,
|
||||
navigation,
|
||||
}: StackScreenProps<{ Home: undefined }>) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>404 Not Found ({route.path})</Text>
|
||||
<Text style={styles.title}>404 Not Found</Text>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
|
||||
@@ -13,10 +13,11 @@ import { useTheme, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
HeaderBackground,
|
||||
useHeaderHeight,
|
||||
Header,
|
||||
StackHeaderProps,
|
||||
} from '@react-navigation/stack';
|
||||
import { HeaderBackground, useHeaderHeight } from '@react-navigation/elements';
|
||||
import BlurView from '../Shared/BlurView';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
@@ -86,10 +87,11 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
|
||||
StackScreenProps<ParamListBase>;
|
||||
|
||||
function CustomHeader(props: StackHeaderProps) {
|
||||
const { current, next } = props.progress;
|
||||
const { current, next } = props.scene.progress;
|
||||
|
||||
const progress = Animated.add(current, next || 0);
|
||||
const opacity = progress.interpolate({
|
||||
@@ -107,7 +109,7 @@ function CustomHeader(props: StackHeaderProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function HeaderCustomizationScreen({ navigation }: Props) {
|
||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -115,20 +117,19 @@ export default function HeaderCustomizationScreen({ navigation }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
const { colors, dark } = useTheme();
|
||||
const [headerTitleCentered, setHeaderTitleCentered] = React.useState(true);
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator screenOptions={{ headerMode: 'float' }}>
|
||||
<SimpleStack.Navigator {...rest}>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author}`,
|
||||
header: (props) => <CustomHeader {...props} />,
|
||||
header: CustomHeader,
|
||||
headerTintColor: '#fff',
|
||||
headerStyle: { backgroundColor: '#ff005d' },
|
||||
headerBackTitleVisible: false,
|
||||
headerTitleAlign: headerTitleCentered ? 'center' : 'left',
|
||||
headerTitleAlign: 'center',
|
||||
headerBackImage: ({ tintColor }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="arrow-left-circle-outline"
|
||||
@@ -141,13 +142,12 @@ export default function HeaderCustomizationScreen({ navigation }: Props) {
|
||||
<Appbar.Action
|
||||
color={tintColor}
|
||||
icon="dots-horizontal-circle-outline"
|
||||
onPress={() => {
|
||||
setHeaderTitleCentered((centered) => !centered);
|
||||
onPress={() =>
|
||||
Alert.alert(
|
||||
'Never gonna give you up!',
|
||||
'Never gonna let you down! Never gonna run around and desert you!'
|
||||
);
|
||||
}}
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
|
||||
type TransparentStackParams = {
|
||||
type SimpleStackParams = {
|
||||
Article: { author: string };
|
||||
Dialog: undefined;
|
||||
};
|
||||
@@ -18,7 +18,7 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: StackScreenProps<TransparentStackParams, 'Article'>) => {
|
||||
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
@@ -45,9 +45,7 @@ const ArticleScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const DialogScreen = ({
|
||||
navigation,
|
||||
}: StackScreenProps<TransparentStackParams>) => {
|
||||
const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
@@ -72,11 +70,12 @@ const DialogScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const TransparentStack = createStackNavigator<TransparentStackParams>();
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
|
||||
StackScreenProps<ParamListBase>;
|
||||
|
||||
export default function TransparentStackScreen({ navigation }: Props) {
|
||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -84,13 +83,13 @@ export default function TransparentStackScreen({ navigation }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<TransparentStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||
<TransparentStack.Screen
|
||||
<SimpleStack.Navigator mode="modal" {...rest}>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<TransparentStack.Screen
|
||||
<SimpleStack.Screen
|
||||
name="Dialog"
|
||||
component={DialogScreen}
|
||||
options={{
|
||||
@@ -123,7 +122,7 @@ export default function TransparentStackScreen({ navigation }: Props) {
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</TransparentStack.Navigator>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
4
example/src/Shared/TouchableBounce.native.tsx
Normal file
4
example/src/Shared/TouchableBounce.native.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
// @ts-expect-error: there are no type definitions for deep imports
|
||||
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
|
||||
|
||||
export default TouchableBounce;
|
||||
3
example/src/Shared/TouchableBounce.tsx
Normal file
3
example/src/Shared/TouchableBounce.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
|
||||
export default TouchableOpacity;
|
||||
@@ -20,31 +20,28 @@ import {
|
||||
Divider,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import { createURL } from 'expo-linking';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {
|
||||
InitialState,
|
||||
NavigationContainer,
|
||||
DefaultTheme,
|
||||
DarkTheme,
|
||||
PathConfigMap,
|
||||
useNavigationContainerRef,
|
||||
NavigatorScreenParams,
|
||||
NavigationContainerRef,
|
||||
} from '@react-navigation/native';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
HeaderStyleInterpolators,
|
||||
StackNavigationProp,
|
||||
} from '@react-navigation/stack';
|
||||
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
import { restartApp } from './Restart';
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
import ModalStack from './Screens/ModalStack';
|
||||
import MixedStack from './Screens/MixedStack';
|
||||
import MixedHeaderMode from './Screens/MixedHeaderMode';
|
||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||
import StackTransparent from './Screens/StackTransparent';
|
||||
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||
import BottomTabs from './Screens/BottomTabs';
|
||||
@@ -55,6 +52,7 @@ import DynamicTabs from './Screens/DynamicTabs';
|
||||
import MasterDetail from './Screens/MasterDetail';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import PreventRemove from './Screens/PreventRemove';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import LinkComponent from './Screens/LinkComponent';
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
@@ -63,30 +61,15 @@ if (Platform.OS !== 'web') {
|
||||
|
||||
enableScreens();
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace ReactNavigation {
|
||||
interface RootParamList extends RootStackParamList {}
|
||||
}
|
||||
}
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Examples: undefined;
|
||||
};
|
||||
|
||||
const SCREENS = {
|
||||
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
|
||||
ModalStack: {
|
||||
title: 'Modal Stack',
|
||||
component: ModalStack,
|
||||
},
|
||||
MixedStack: {
|
||||
title: 'Regular + Modal Stack',
|
||||
component: MixedStack,
|
||||
},
|
||||
MixedHeaderMode: {
|
||||
title: 'Float + Screen Header Stack',
|
||||
component: MixedHeaderMode,
|
||||
ModalPresentationStack: {
|
||||
title: 'Modal Presentation Stack',
|
||||
component: ModalPresentationStack,
|
||||
},
|
||||
StackTransparent: {
|
||||
title: 'Transparent Stack',
|
||||
@@ -121,6 +104,10 @@ const SCREENS = {
|
||||
title: 'Prevent removing screen',
|
||||
component: PreventRemove,
|
||||
},
|
||||
CompatAPI: {
|
||||
title: 'Compat Layer',
|
||||
component: CompatAPI,
|
||||
},
|
||||
LinkComponent: {
|
||||
title: '<Link />',
|
||||
component: LinkComponent,
|
||||
@@ -128,7 +115,7 @@ const SCREENS = {
|
||||
};
|
||||
|
||||
type RootStackParamList = {
|
||||
Home: NavigatorScreenParams<RootDrawerParamList>;
|
||||
Home: undefined;
|
||||
NotFound: undefined;
|
||||
} & {
|
||||
[P in keyof typeof SCREENS]: undefined;
|
||||
@@ -154,7 +141,7 @@ export default function App() {
|
||||
const initialUrl = await Linking.getInitialURL();
|
||||
|
||||
if (Platform.OS !== 'web' || initialUrl === null) {
|
||||
const savedState = await AsyncStorage?.getItem(
|
||||
const savedState = await AsyncStorage.getItem(
|
||||
NAVIGATION_PERSISTENCE_KEY
|
||||
);
|
||||
|
||||
@@ -166,7 +153,7 @@ export default function App() {
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
const themeName = await AsyncStorage?.getItem(THEME_PERSISTENCE_KEY);
|
||||
const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY);
|
||||
|
||||
setTheme(themeName === 'dark' ? DarkTheme : DefaultTheme);
|
||||
} catch (e) {
|
||||
@@ -206,7 +193,7 @@ export default function App() {
|
||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||
}, []);
|
||||
|
||||
const navigationRef = useNavigationContainerRef();
|
||||
const navigationRef = React.useRef<NavigationContainerRef>(null);
|
||||
|
||||
useReduxDevToolsExtension(navigationRef);
|
||||
|
||||
@@ -225,7 +212,7 @@ export default function App() {
|
||||
ref={navigationRef}
|
||||
initialState={initialState}
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage?.setItem(
|
||||
AsyncStorage.setItem(
|
||||
NAVIGATION_PERSISTENCE_KEY,
|
||||
JSON.stringify(state)
|
||||
)
|
||||
@@ -238,12 +225,10 @@ export default function App() {
|
||||
// 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: [createURL('/')],
|
||||
prefixes: LinkingPrefixes,
|
||||
config: {
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<
|
||||
PathConfigMap<RootStackParamList>
|
||||
>(
|
||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
const path = name
|
||||
@@ -251,15 +236,13 @@ export default function App() {
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
// @ts-expect-error: these types aren't accurate
|
||||
// But we aren't too concerned for now
|
||||
acc[name] = {
|
||||
path,
|
||||
screens: {
|
||||
Article: {
|
||||
path: 'article/:author?',
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author: (author) =>
|
||||
author.charAt(0).toUpperCase() +
|
||||
author.slice(1).replace(/-/g, ' '),
|
||||
},
|
||||
@@ -308,9 +291,8 @@ export default function App() {
|
||||
>
|
||||
{() => (
|
||||
<Drawer.Navigator
|
||||
screenOptions={{
|
||||
drawerType: isLargeScreen ? 'permanent' : undefined,
|
||||
}}
|
||||
drawerType={isLargeScreen ? 'permanent' : undefined}
|
||||
screenOptions={{ headerShown: true }}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="Examples"
|
||||
@@ -321,11 +303,7 @@ export default function App() {
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: StackNavigationProp<RootStackParamList>;
|
||||
}) => (
|
||||
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
||||
<ScrollView
|
||||
style={{ backgroundColor: theme.colors.background }}
|
||||
>
|
||||
@@ -343,7 +321,7 @@ export default function App() {
|
||||
label="Dark theme"
|
||||
value={theme.dark}
|
||||
onValueChange={() => {
|
||||
AsyncStorage?.setItem(
|
||||
AsyncStorage.setItem(
|
||||
THEME_PERSISTENCE_KEY,
|
||||
theme.dark ? 'light' : 'dark'
|
||||
);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../packages/bottom-tabs" },
|
||||
{ "path": "../packages/core" },
|
||||
{ "path": "../packages/devtools" },
|
||||
{ "path": "../packages/drawer" },
|
||||
{ "path": "../packages/elements" },
|
||||
{ "path": "../packages/material-bottom-tabs" },
|
||||
{ "path": "../packages/material-top-tabs" },
|
||||
{ "path": "../packages/native" },
|
||||
{ "path": "../packages/routers" },
|
||||
{ "path": "../packages/stack" },
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Avoid expo-cli auto-generating a tsconfig
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,9 @@
|
||||
"version": "independent",
|
||||
"command": {
|
||||
"publish": {
|
||||
"allowBranch": "main",
|
||||
"allowBranch": "5.x",
|
||||
"conventionalCommits": true,
|
||||
"createRelease": "github",
|
||||
"distTag": "next",
|
||||
"message": "chore: publish",
|
||||
"ignoreChanges": [
|
||||
"**/__fixtures__/**",
|
||||
|
||||
28
package.json
28
package.json
@@ -8,7 +8,7 @@
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -25,19 +25,19 @@
|
||||
"example": "yarn --cwd example"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^12.1.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"babel-jest": "^26.6.3",
|
||||
"codecov": "^3.8.1",
|
||||
"commitlint": "^12.1.1",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint-config-satya164": "^3.1.10",
|
||||
"husky": "^4.3.6",
|
||||
"jest": "^26.6.3",
|
||||
"lerna": "^4.0.0",
|
||||
"metro-react-native-babel-preset": "^0.65.2",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "^4.2.3"
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"babel-jest": "^26.6.1",
|
||||
"codecov": "^3.8.0",
|
||||
"commitlint": "^11.0.0",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-config-satya164": "^3.1.8",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.1",
|
||||
"lerna": "^3.22.1",
|
||||
"metro-react-native-babel-preset": "^0.63.0",
|
||||
"prettier": "^2.1.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "~16.13.1",
|
||||
|
||||
@@ -3,39 +3,19 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.10...@react-navigation/bottom-tabs@6.0.0-next.11) (2021-05-16)
|
||||
## [5.11.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.8...@react-navigation/bottom-tabs@5.11.9) (2021-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix tab bar height including extra bottom inset ([7c722d2](https://github.com/react-navigation/react-navigation/commit/7c722d2028e914e8f143b9385ebf5e1c00131a01))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a tabBarBackground option to bottom tabs ([2f282f1](https://github.com/react-navigation/react-navigation/commit/2f282f107053a65b69f80edb5d9c858cfa569aa2))
|
||||
* check for screens enabled in ScreenContainer ([493956e](https://github.com/react-navigation/react-navigation/commit/493956ef717a03bd8c3533a2949434e83718c5e4))
|
||||
* don't pass accessibilityState to link. closes [#9418](https://github.com/react-navigation/react-navigation/issues/9418) ([699ea0c](https://github.com/react-navigation/react-navigation/commit/699ea0cc5052f190acc7ce8bc0328bb052d7cf26))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.9...@react-navigation/bottom-tabs@6.0.0-next.10) (2021-05-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add a deprecation warning for mode prop in stack ([a6e4981](https://github.com/react-navigation/react-navigation/commit/a6e498170f59648190fa5513e273ca523e56c5d5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* return a NavigationContent component from useNavigationBuilder ([1179d56](https://github.com/react-navigation/react-navigation/commit/1179d56c5008270753feef41acdc1dbd2191efcf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.8...@react-navigation/bottom-tabs@6.0.0-next.9) (2021-05-09)
|
||||
## [5.11.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.7...@react-navigation/bottom-tabs@5.11.8) (2021-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -43,7 +23,29 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.7...@react-navigation/bottom-tabs@6.0.0-next.8) (2021-05-09)
|
||||
## [5.11.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.6...@react-navigation/bottom-tabs@5.11.7) (2021-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix drawer screen content not being interactable on Android ([87b5147](https://github.com/react-navigation/react-navigation/commit/87b51476d0bce8f2dae793416c2976da30a1a5f7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.5...@react-navigation/bottom-tabs@5.11.6) (2021-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix pointerEvents in ResourceSavingScene ([60fe0db](https://github.com/react-navigation/react-navigation/commit/60fe0dbb0ae443fdb21016d368c919b933cb64e7)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.4...@react-navigation/bottom-tabs@5.11.5) (2021-01-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -51,19 +53,29 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.6...@react-navigation/bottom-tabs@6.0.0-next.7) (2021-05-09)
|
||||
## [5.11.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.3...@react-navigation/bottom-tabs@5.11.4) (2021-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enable screens only on supported platforms ([#9494](https://github.com/react-navigation/react-navigation/issues/9494)) ([8da4c58](https://github.com/react-navigation/react-navigation/commit/8da4c58065607d44e9dc1ad8943e09537598dcd7))
|
||||
* make sure disabling react-native-screens works ([a369ba3](https://github.com/react-navigation/react-navigation/commit/a369ba36451ddc2bb5b247e61b725bce1e3fb5e5))
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([d88cbcb](https://github.com/react-navigation/react-navigation/commit/d88cbcb52d46de26edaa9ce6bfb06badb1b1de64))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.5...@react-navigation/bottom-tabs@6.0.0-next.6) (2021-05-01)
|
||||
## [5.11.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.2...@react-navigation/bottom-tabs@5.11.3) (2021-01-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enable detachInactiveScreens by default on web for better a11y ([dd87fa4](https://github.com/react-navigation/react-navigation/commit/dd87fa49a43ad8db105a62418243339e4150fadf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@5.11.2) (2020-11-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -71,102 +83,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.4...@react-navigation/bottom-tabs@6.0.0-next.5) (2021-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update tab bar height correctly. fixes [#9296](https://github.com/react-navigation/react-navigation/issues/9296) ([338ed6f](https://github.com/react-navigation/react-navigation/commit/338ed6ff07bd2d6efa1abdb369612ea72f540502))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.3...@react-navigation/bottom-tabs@6.0.0-next.4) (2021-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove calls to removed Keyboard.removeListener in useIsKeyboardShown ([#9457](https://github.com/react-navigation/react-navigation/issues/9457)) ([d87857e](https://github.com/react-navigation/react-navigation/commit/d87857e5d93c19ebee2fd84eb4910e36001ec2a3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.2...@react-navigation/bottom-tabs@6.0.0-next.3) (2021-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use tab role on Android for accessibility ([de805a3](https://github.com/react-navigation/react-navigation/commit/de805a3ebf35db81cb7b7bcbf5cfd4a03e69c567))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a Background component ([cbaabc1](https://github.com/react-navigation/react-navigation/commit/cbaabc1288e780698e499a00b9ca06ab9746a0da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.1...@react-navigation/bottom-tabs@6.0.0-next.2) (2021-03-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0...@react-navigation/bottom-tabs@6.0.0-next.1) (2021-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix peer dep versions ([72f90b5](https://github.com/react-navigation/react-navigation/commit/72f90b50d27eda1315bb750beca8a36f26dafe17))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@6.0.0-next.0) (2021-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
|
||||
* drop usage of Dimensions in favor of metrics from safe-area-context ([12b893d](https://github.com/react-navigation/react-navigation/commit/12b893d7ca8cdb726b973972797658ac9c7d17d7))
|
||||
* enable detachInactiveScreens by default on web for better a11y ([4954d6a](https://github.com/react-navigation/react-navigation/commit/4954d6aae3cdbb5855d44ff17d80d16b81fb224e))
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([b735de1](https://github.com/react-navigation/react-navigation/commit/b735de153ca650240625dba6d8b5c8d16b913bac))
|
||||
* fix drawer screen content not being interactable on Android ([865d8b3](https://github.com/react-navigation/react-navigation/commit/865d8b3e51e117a01243966c160b7cd147d236ac))
|
||||
* fix initial metrics on server ([69d333f](https://github.com/react-navigation/react-navigation/commit/69d333f6c23e0c37eaf4d3f8b413e8f96d6827f8))
|
||||
* fix pointerEvents in ResourceSavingScene ([af53dd6](https://github.com/react-navigation/react-navigation/commit/af53dd6548630124f831446e0eee468da5d9bf5e)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
* show a missing icon symbol instead of empty area in bottom tab bar ([2bc4882](https://github.com/react-navigation/react-navigation/commit/2bc4882692be9f02d781639892e1f98b891811c4))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* don't use deprecated APIs from react-native-safe-area-context ([ddf27bf](https://github.com/react-navigation/react-navigation/commit/ddf27bf41a2efc5d1573aad0f8fe6c27a98c32b3))
|
||||
* drop support for tabBarVisible option ([a97a43a](https://github.com/react-navigation/react-navigation/commit/a97a43aa1d2a615074ade93f1addebcee1dbfb65))
|
||||
* move `tabBarOptions` to `options` for bottom tabs ([f7ff1ad](https://github.com/react-navigation/react-navigation/commit/f7ff1adee7654b2d624dee4ae8844be217f23026))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial implementation of @react-navigation/elements ([07ba7a9](https://github.com/react-navigation/react-navigation/commit/07ba7a96870efdb8acf99eb82ba0b1d3eac90bab))
|
||||
* move lazy to options for bottom-tabs and drawer ([068a9a4](https://github.com/react-navigation/react-navigation/commit/068a9a456c31a08104097f2a8434c66c30a5be99))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
|
||||
* This commit moves options from `tabBarOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the tab bar based on a per screen basis.
|
||||
* We now require newer versions of safe area context library.
|
||||
* We need to add support for specifying style for tab bar in options to support the use cases which need this.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.0...@react-navigation/bottom-tabs@5.11.1) (2020-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Bottom tab navigator for React Navigation following iOS design guidelines.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/6.x/bottom-tab-navigator/).
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/bottom-tab-navigator/).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "6.0.0-next.11",
|
||||
"version": "5.11.9",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,30 +36,29 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.10",
|
||||
"color": "^3.1.3",
|
||||
"warn-once": "^0.1.0"
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@react-navigation/native": "^5.9.4",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"react-native-safe-area-context": "~3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"typescript": "^4.2.3"
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0",
|
||||
"react-native-screens": ">= 3.0.0"
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
},
|
||||
"react-native-builder-bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -24,5 +24,6 @@ export type {
|
||||
BottomTabNavigationProp,
|
||||
BottomTabScreenProps,
|
||||
BottomTabBarProps,
|
||||
BottomTabBarOptions,
|
||||
BottomTabBarButtonProps,
|
||||
} from './types';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import warnOnce from 'warn-once';
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
@@ -27,55 +26,9 @@ function BottomTabNavigator({
|
||||
children,
|
||||
screenOptions,
|
||||
sceneContainerStyle,
|
||||
// @ts-expect-error: lazy is deprecated
|
||||
lazy,
|
||||
// @ts-expect-error: tabBarOptions is deprecated
|
||||
tabBarOptions,
|
||||
...rest
|
||||
}: Props) {
|
||||
let defaultScreenOptions: BottomTabNavigationOptions = {};
|
||||
|
||||
if (tabBarOptions) {
|
||||
Object.assign(defaultScreenOptions, {
|
||||
tabBarHideOnKeyboard: tabBarOptions.keyboardHidesTabBar,
|
||||
tabBarActiveTintColor: tabBarOptions.activeTintColor,
|
||||
tabBarInactiveTintColor: tabBarOptions.inactiveTintColor,
|
||||
tabBarActiveBackgroundColor: tabBarOptions.activeBackgroundColor,
|
||||
tabBarInactiveBackgroundColor: tabBarOptions.inactiveBackgroundColor,
|
||||
tabBarAllowFontScaling: tabBarOptions.allowFontScaling,
|
||||
tabBarShowLabel: tabBarOptions.showLabel,
|
||||
tabBarLabelStyle: tabBarOptions.labelStyle,
|
||||
tabBarIconStyle: tabBarOptions.iconStyle,
|
||||
tabBarItemStyle: tabBarOptions.tabStyle,
|
||||
tabBarLabelPosition: tabBarOptions.labelPosition,
|
||||
tabBarAdaptive: tabBarOptions.adaptive,
|
||||
});
|
||||
|
||||
warnOnce(
|
||||
tabBarOptions,
|
||||
`Bottom Tab Navigator: 'tabBarOptions' is deprecated. Migrate the options to 'screenOptions' instead.\n\nPlace the following in 'screenOptions' in your code to keep current behavior:\n\n${JSON.stringify(
|
||||
defaultScreenOptions,
|
||||
null,
|
||||
2
|
||||
)}\n\nSee https://reactnavigation.org/docs/6.x/bottom-tab-navigator#options for more details.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof lazy === 'boolean') {
|
||||
defaultScreenOptions.lazy = lazy;
|
||||
|
||||
warnOnce(
|
||||
true,
|
||||
`Bottom Tab Navigator: 'lazy' in props is deprecated. Move it to 'screenOptions' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
NavigationContent,
|
||||
} = useNavigationBuilder<
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
@@ -86,19 +39,16 @@ function BottomTabNavigator({
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
defaultScreenOptions,
|
||||
});
|
||||
|
||||
return (
|
||||
<NavigationContent>
|
||||
<BottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
/>
|
||||
</NavigationContent>
|
||||
<BottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ import type {
|
||||
TabActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { HeaderOptions } from '@react-navigation/elements';
|
||||
|
||||
export type Layout = { width: number; height: number };
|
||||
|
||||
export type BottomTabNavigationEventMap = {
|
||||
/**
|
||||
@@ -42,7 +38,7 @@ export type BottomTabNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type BottomTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
RouteName extends keyof ParamList = string
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -54,7 +50,7 @@ export type BottomTabNavigationProp<
|
||||
|
||||
export type BottomTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
RouteName extends keyof ParamList = string
|
||||
> = {
|
||||
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -80,34 +76,16 @@ export type TabBarVisibilityAnimationConfig =
|
||||
| TimingKeyboardAnimationConfig
|
||||
| SpringKeyboardAnimationConfig;
|
||||
|
||||
export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
export type BottomTabNavigationOptions = {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Whether this screens should render the first time it's accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render the screen on initial render.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
|
||||
/**
|
||||
* Function that given returns a React Element to display as a header.
|
||||
*/
|
||||
header?: (props: BottomTabHeaderProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether to show the header. Setting this to `false` hides the header.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
headerShown?: boolean;
|
||||
|
||||
/**
|
||||
* Title string of a tab displayed in the tab bar
|
||||
* or a function that given { focused: boolean, color: string, position: 'below-icon' | 'beside-icon' } returns a React.Node to display in tab bar.
|
||||
*
|
||||
* When undefined, scene title is used. Use `tabBarShowLabel` to hide the label.
|
||||
* When undefined, scene title is used. To hide, see tabBarOptions.showLabel in the previous section.
|
||||
*/
|
||||
tabBarLabel?:
|
||||
| string
|
||||
@@ -148,6 +126,11 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
*/
|
||||
tabBarTestID?: string;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether the tab bar is visible when this screen is active.
|
||||
*/
|
||||
tabBarVisible?: boolean;
|
||||
|
||||
/**
|
||||
* Animation config for showing and hiding the tab bar.
|
||||
*/
|
||||
@@ -162,82 +145,6 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
*/
|
||||
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the active tab.
|
||||
*/
|
||||
tabBarActiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the inactive tabs.
|
||||
*/
|
||||
tabBarInactiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Background color for the active tab.
|
||||
*/
|
||||
tabBarActiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* background color for the inactive tabs.
|
||||
*/
|
||||
tabBarInactiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Whether label font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
tabBarAllowFontScaling?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the tab label should be visible. Defaults to `true`.
|
||||
*/
|
||||
tabBarShowLabel?: boolean;
|
||||
|
||||
/**
|
||||
* Style object for the tab label.
|
||||
*/
|
||||
tabBarLabelStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the tab icon.
|
||||
*/
|
||||
tabBarIconStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the tab item container.
|
||||
*/
|
||||
tabBarItemStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Whether the label is rendered below the icon or beside the icon.
|
||||
* By default, the position is chosen automatically based on device width.
|
||||
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
|
||||
*/
|
||||
tabBarLabelPosition?: LabelPosition;
|
||||
|
||||
/**
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
tabBarAdaptive?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
|
||||
*/
|
||||
tabBarHideOnKeyboard?: boolean;
|
||||
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
|
||||
/**
|
||||
* Component to use as background for the tab bar.
|
||||
* You could render an image, a gradient, blur view etc.
|
||||
*
|
||||
* When using `BlurView`, make sure to set `position: 'absolute'` in `tabBarStyle` as well.
|
||||
* You'd also need to use `useBottomTabBarHeight()` to add a bottom padding to your content.
|
||||
*/
|
||||
tabBarBackground?: () => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether this screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
@@ -246,28 +153,30 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
};
|
||||
|
||||
export type BottomTabDescriptor = Descriptor<
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase>
|
||||
ParamListBase,
|
||||
string,
|
||||
TabNavigationState<ParamListBase>,
|
||||
BottomTabNavigationOptions
|
||||
>;
|
||||
|
||||
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
|
||||
export type BottomTabDescriptorMap = {
|
||||
[key: string]: BottomTabDescriptor;
|
||||
};
|
||||
|
||||
export type BottomTabNavigationConfig = {
|
||||
export type BottomTabNavigationConfig<T = BottomTabBarOptions> = {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
/**
|
||||
* Function that returns a React element to display as the tab bar.
|
||||
*/
|
||||
tabBar?: (props: BottomTabBarProps) => React.ReactNode;
|
||||
tabBar?: (props: BottomTabBarProps<T>) => React.ReactNode;
|
||||
/**
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
* Options for the tab bar which will be passed as props to the tab bar component.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
tabBarOptions?: T;
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
@@ -280,30 +189,77 @@ export type BottomTabNavigationConfig = {
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type BottomTabHeaderProps = {
|
||||
export type BottomTabBarOptions = {
|
||||
/**
|
||||
* Layout of the screen.
|
||||
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
|
||||
*/
|
||||
layout: Layout;
|
||||
keyboardHidesTabBar?: boolean;
|
||||
/**
|
||||
* Options for the current screen.
|
||||
* Color for the icon and label in the active tab.
|
||||
*/
|
||||
options: BottomTabNavigationOptions;
|
||||
activeTintColor?: string;
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
* Color for the icon and label in the inactive tabs.
|
||||
*/
|
||||
route: RouteProp<ParamListBase>;
|
||||
inactiveTintColor?: string;
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
* Background color for the active tab.
|
||||
*/
|
||||
navigation: BottomTabNavigationProp<ParamListBase>;
|
||||
activeBackgroundColor?: string;
|
||||
/**
|
||||
* background color for the inactive tabs.
|
||||
*/
|
||||
inactiveBackgroundColor?: string;
|
||||
/**
|
||||
* Whether label font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
allowFontScaling?: boolean;
|
||||
/**
|
||||
* Whether the tab label should be visible. Defaults to `true`.
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
/**
|
||||
* Style object for the tab label.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the tab icon.
|
||||
*/
|
||||
iconStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the tab container.
|
||||
*/
|
||||
tabStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Whether the label is rendered below the icon or beside the icon.
|
||||
* By default, the position is chosen automatically based on device width.
|
||||
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
|
||||
*/
|
||||
labelPosition?: LabelPosition;
|
||||
/**
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
adaptive?: boolean;
|
||||
/**
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
};
|
||||
|
||||
export type BottomTabBarProps = {
|
||||
export type BottomTabBarProps<T = BottomTabBarOptions> = T & {
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
insets: EdgeInsets;
|
||||
};
|
||||
|
||||
export type BottomTabBarButtonProps = Omit<
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Keyboard, Platform, EmitterSubscription } from 'react-native';
|
||||
import { Keyboard, Platform } from 'react-native';
|
||||
|
||||
export default function useIsKeyboardShown() {
|
||||
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
|
||||
@@ -8,22 +8,22 @@ export default function useIsKeyboardShown() {
|
||||
const handleKeyboardShow = () => setIsKeyboardShown(true);
|
||||
const handleKeyboardHide = () => setIsKeyboardShown(false);
|
||||
|
||||
let subscriptions: EmitterSubscription[];
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
subscriptions = [
|
||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow),
|
||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide),
|
||||
];
|
||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
|
||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
|
||||
} else {
|
||||
subscriptions = [
|
||||
Keyboard.addListener('keyboardDidShow', handleKeyboardShow),
|
||||
Keyboard.addListener('keyboardDidHide', handleKeyboardHide),
|
||||
];
|
||||
Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
|
||||
Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
|
||||
}
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach((s) => s.remove());
|
||||
if (Platform.OS === 'ios') {
|
||||
Keyboard.removeListener('keyboardWillShow', handleKeyboardShow);
|
||||
Keyboard.removeListener('keyboardWillHide', handleKeyboardHide);
|
||||
} else {
|
||||
Keyboard.removeListener('keyboardDidShow', handleKeyboardShow);
|
||||
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
37
packages/bottom-tabs/src/utils/useWindowDimensions.tsx
Normal file
37
packages/bottom-tabs/src/utils/useWindowDimensions.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { ScaledSize, Dimensions } from 'react-native';
|
||||
|
||||
// This is similar to the new useWindowDimensions hook in react-native
|
||||
// However, we have a custom implementation to support older RN versions
|
||||
export default function useWindowDimensions() {
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
// `height` and `width` maybe undefined during SSR, so we initialize them
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const onChange = ({ window }: { window: ScaledSize }) => {
|
||||
const { width, height } = window;
|
||||
|
||||
setDimensions((d) => {
|
||||
if (width === d.width && height === d.height) {
|
||||
return d;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
});
|
||||
};
|
||||
|
||||
// We might have missed an update before the listener was added
|
||||
// So make sure to update the dimensions
|
||||
onChange({ window: Dimensions.get('window') });
|
||||
|
||||
Dimensions.addEventListener('change', onChange);
|
||||
|
||||
return () => Dimensions.removeEventListener('change', onChange);
|
||||
}, []);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
@@ -17,16 +17,17 @@ import {
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import { MissingIcon } from '@react-navigation/elements';
|
||||
import { EdgeInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { useSafeArea, EdgeInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import BottomTabItem from './BottomTabItem';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
|
||||
import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
|
||||
import type { BottomTabBarProps, LabelPosition } from '../types';
|
||||
|
||||
type Props = BottomTabBarProps & {
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
activeTintColor?: string;
|
||||
inactiveTintColor?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_TABBAR_HEIGHT = 49;
|
||||
@@ -37,47 +38,44 @@ const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
type Options = {
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
layout: { height: number; width: number };
|
||||
dimensions: { height: number; width: number };
|
||||
tabStyle: StyleProp<ViewStyle>;
|
||||
labelPosition: LabelPosition | undefined;
|
||||
adaptive: boolean | undefined;
|
||||
};
|
||||
|
||||
const shouldUseHorizontalLabels = ({
|
||||
state,
|
||||
descriptors,
|
||||
layout,
|
||||
dimensions,
|
||||
adaptive = true,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
}: Options) => {
|
||||
const { tabBarLabelPosition, tabBarAdaptive = true } = descriptors[
|
||||
state.routes[state.index].key
|
||||
].options;
|
||||
|
||||
if (tabBarLabelPosition) {
|
||||
return tabBarLabelPosition === 'beside-icon';
|
||||
if (labelPosition) {
|
||||
return labelPosition === 'beside-icon';
|
||||
}
|
||||
|
||||
if (!tabBarAdaptive) {
|
||||
if (!adaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout.width >= 768) {
|
||||
// Screen size matches a tablet
|
||||
const maxTabWidth = state.routes.reduce((acc, route) => {
|
||||
const { tabBarItemStyle } = descriptors[route.key].options;
|
||||
const flattenedStyle = StyleSheet.flatten(tabBarItemStyle);
|
||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
return acc + flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
return acc + flattenedStyle.maxWidth;
|
||||
}
|
||||
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
||||
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return acc + DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
}, 0);
|
||||
|
||||
return maxTabWidth <= layout.width;
|
||||
return state.routes.length * maxTabItemWidth <= layout.width;
|
||||
} else {
|
||||
return dimensions.width > dimensions.height;
|
||||
}
|
||||
@@ -87,15 +85,13 @@ const getPaddingBottom = (insets: EdgeInsets) =>
|
||||
Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);
|
||||
|
||||
export const getTabBarHeight = ({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
insets,
|
||||
style,
|
||||
...rest
|
||||
}: Options & {
|
||||
insets: EdgeInsets;
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>> | undefined;
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
}) => {
|
||||
// @ts-ignore
|
||||
const customHeight = StyleSheet.flatten(style)?.height;
|
||||
@@ -105,12 +101,7 @@ export const getTabBarHeight = ({
|
||||
}
|
||||
|
||||
const isLandscape = dimensions.width > dimensions.height;
|
||||
const horizontalLabels = shouldUseHorizontalLabels({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
...rest,
|
||||
});
|
||||
const horizontalLabels = shouldUseHorizontalLabels({ dimensions, ...rest });
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
|
||||
if (
|
||||
@@ -129,8 +120,20 @@ export default function BottomTabBar({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
insets,
|
||||
activeBackgroundColor,
|
||||
activeTintColor,
|
||||
adaptive,
|
||||
allowFontScaling,
|
||||
inactiveBackgroundColor,
|
||||
inactiveTintColor,
|
||||
keyboardHidesTabBar = false,
|
||||
labelPosition,
|
||||
labelStyle,
|
||||
iconStyle,
|
||||
safeAreaInsets,
|
||||
showLabel,
|
||||
style,
|
||||
tabStyle,
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
const buildLink = useLinkBuilder();
|
||||
@@ -139,27 +142,22 @@ export default function BottomTabBar({
|
||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||
const focusedOptions = focusedDescriptor.options;
|
||||
|
||||
const {
|
||||
tabBarShowLabel,
|
||||
tabBarHideOnKeyboard = false,
|
||||
tabBarVisibilityAnimationConfig,
|
||||
tabBarStyle,
|
||||
tabBarBackground,
|
||||
} = focusedOptions;
|
||||
|
||||
const dimensions = useSafeAreaFrame();
|
||||
const dimensions = useWindowDimensions();
|
||||
const isKeyboardShown = useIsKeyboardShown();
|
||||
|
||||
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
||||
|
||||
const shouldShowTabBar = !(tabBarHideOnKeyboard && isKeyboardShown);
|
||||
const shouldShowTabBar =
|
||||
focusedOptions.tabBarVisible !== false &&
|
||||
!(keyboardHidesTabBar && isKeyboardShown);
|
||||
|
||||
const visibilityAnimationConfigRef = React.useRef(
|
||||
tabBarVisibilityAnimationConfig
|
||||
focusedOptions.tabBarVisibilityAnimationConfig
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
visibilityAnimationConfigRef.current = tabBarVisibilityAnimationConfig;
|
||||
visibilityAnimationConfigRef.current =
|
||||
focusedOptions.tabBarVisibilityAnimationConfig;
|
||||
});
|
||||
|
||||
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
|
||||
@@ -212,7 +210,15 @@ export default function BottomTabBar({
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
onHeightChange?.(height);
|
||||
const topBorderWidth =
|
||||
// @ts-ignore
|
||||
StyleSheet.flatten([styles.tabBar, style])?.borderTopWidth;
|
||||
|
||||
onHeightChange?.(
|
||||
height +
|
||||
paddingBottom +
|
||||
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
|
||||
);
|
||||
|
||||
setLayout((layout) => {
|
||||
if (height === layout.height && width === layout.width) {
|
||||
@@ -228,32 +234,42 @@ export default function BottomTabBar({
|
||||
|
||||
const { routes } = state;
|
||||
|
||||
const defaultInsets = useSafeArea();
|
||||
|
||||
const insets = {
|
||||
top: safeAreaInsets?.top ?? defaultInsets.top,
|
||||
right: safeAreaInsets?.right ?? defaultInsets.right,
|
||||
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||
};
|
||||
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
state,
|
||||
descriptors,
|
||||
insets,
|
||||
dimensions,
|
||||
layout,
|
||||
style: [tabBarStyle, style],
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
style,
|
||||
});
|
||||
|
||||
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
layout,
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
});
|
||||
|
||||
const tabBarBackgroundElement = tabBarBackground?.();
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor:
|
||||
tabBarBackgroundElement != null ? 'transparent' : colors.card,
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
{
|
||||
@@ -277,15 +293,11 @@ export default function BottomTabBar({
|
||||
paddingBottom,
|
||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||
},
|
||||
tabBarStyle,
|
||||
style,
|
||||
]}
|
||||
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||
onLayout={handleLayout}
|
||||
>
|
||||
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
||||
{tabBarBackgroundElement}
|
||||
</View>
|
||||
<View accessibilityRole="tablist" style={styles.content}>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
@@ -322,7 +334,7 @@ export default function BottomTabBar({
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string' && Platform.OS === 'ios'
|
||||
: typeof label === 'string'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
|
||||
@@ -341,27 +353,20 @@ export default function BottomTabBar({
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
to={buildLink(route.name, route.params)}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={options.tabBarAllowFontScaling}
|
||||
activeTintColor={options.tabBarActiveTintColor}
|
||||
inactiveTintColor={options.tabBarInactiveTintColor}
|
||||
activeBackgroundColor={options.tabBarActiveBackgroundColor}
|
||||
inactiveBackgroundColor={
|
||||
options.tabBarInactiveBackgroundColor
|
||||
}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
button={options.tabBarButton}
|
||||
icon={
|
||||
options.tabBarIcon ??
|
||||
(({ color, size }) => (
|
||||
<MissingIcon color={color} size={size} />
|
||||
))
|
||||
}
|
||||
icon={options.tabBarIcon}
|
||||
badge={options.tabBarBadge}
|
||||
badgeStyle={options.tabBarBadgeStyle}
|
||||
label={label}
|
||||
showLabel={tabBarShowLabel}
|
||||
labelStyle={options.tabBarLabelStyle}
|
||||
iconStyle={options.tabBarIconStyle}
|
||||
style={options.tabBarItemStyle}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
iconStyle={iconStyle}
|
||||
style={tabStyle}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Pressable,
|
||||
TouchableWithoutFeedback,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
StyleProp,
|
||||
@@ -37,7 +38,7 @@ type Props = {
|
||||
/**
|
||||
* Icon to display for the tab.
|
||||
*/
|
||||
icon: (props: {
|
||||
icon?: (props: {
|
||||
focused: boolean;
|
||||
size: number;
|
||||
color: string;
|
||||
@@ -133,6 +134,7 @@ export default function BottomTabBarItem({
|
||||
onPress,
|
||||
to,
|
||||
accessibilityRole,
|
||||
accessibilityState,
|
||||
...rest
|
||||
}: BottomTabBarButtonProps) => {
|
||||
if (Platform.OS === 'web' && to) {
|
||||
@@ -158,14 +160,14 @@ export default function BottomTabBarItem({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Pressable
|
||||
<TouchableWithoutFeedback
|
||||
{...rest}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityState={accessibilityState}
|
||||
onPress={onPress}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -263,8 +265,7 @@ export default function BottomTabBarItem({
|
||||
onLongPress,
|
||||
testID,
|
||||
accessibilityLabel,
|
||||
// FIXME: accessibilityRole: 'tab' doesn't seem to work as expected on iOS
|
||||
accessibilityRole: Platform.select({ ios: 'button', default: 'tab' }),
|
||||
accessibilityRole: 'button',
|
||||
accessibilityState: { selected: focused },
|
||||
// @ts-expect-error: keep for compatibility with older React Native versions
|
||||
accessibilityStates: focused ? ['selected'] : [],
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
||||
import type {
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
ParamListBase,
|
||||
TabNavigationState,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
Header,
|
||||
Screen,
|
||||
SafeAreaProviderCompat,
|
||||
getHeaderTitle,
|
||||
} from '@react-navigation/elements';
|
||||
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
|
||||
import { ScreenContainer, screensEnabled } from 'react-native-screens';
|
||||
|
||||
import SafeAreaProviderCompat, {
|
||||
initialSafeAreaInsets,
|
||||
} from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||
@@ -20,8 +27,6 @@ import type {
|
||||
BottomTabDescriptorMap,
|
||||
BottomTabNavigationHelpers,
|
||||
BottomTabBarProps,
|
||||
BottomTabHeaderProps,
|
||||
BottomTabNavigationProp,
|
||||
} from '../types';
|
||||
|
||||
type Props = BottomTabNavigationConfig & {
|
||||
@@ -30,134 +35,169 @@ type Props = BottomTabNavigationConfig & {
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
};
|
||||
|
||||
export default function BottomTabView(props: Props) {
|
||||
const {
|
||||
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
safeAreaInsets,
|
||||
detachInactiveScreens = Platform.OS === 'web' ||
|
||||
Platform.OS === 'android' ||
|
||||
Platform.OS === 'ios',
|
||||
sceneContainerStyle,
|
||||
} = props;
|
||||
type State = {
|
||||
loaded: string[];
|
||||
tabBarHeight: number;
|
||||
};
|
||||
|
||||
const focusedRouteKey = state.routes[state.index].key;
|
||||
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
|
||||
|
||||
if (!loaded.includes(focusedRouteKey)) {
|
||||
setLoaded([...loaded, focusedRouteKey]);
|
||||
}
|
||||
|
||||
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
|
||||
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
|
||||
getTabBarHeight({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
layout: { width: dimensions.width, height: 0 },
|
||||
insets: {
|
||||
...SafeAreaProviderCompat.initialMetrics.insets,
|
||||
...props.safeAreaInsets,
|
||||
},
|
||||
style: descriptors[state.routes[state.index].key].options.tabBarStyle,
|
||||
})
|
||||
);
|
||||
|
||||
const renderTabBar = () => {
|
||||
return (
|
||||
<SafeAreaInsetsContext.Consumer>
|
||||
{(insets) =>
|
||||
tabBar({
|
||||
state: state,
|
||||
descriptors: descriptors,
|
||||
navigation: navigation,
|
||||
insets: {
|
||||
top: safeAreaInsets?.top ?? insets?.top ?? 0,
|
||||
right: safeAreaInsets?.right ?? insets?.right ?? 0,
|
||||
bottom: safeAreaInsets?.bottom ?? insets?.bottom ?? 0,
|
||||
left: safeAreaInsets?.left ?? insets?.left ?? 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
</SafeAreaInsetsContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
const { routes } = state;
|
||||
function SceneContent({
|
||||
isFocused,
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
isFocused: boolean;
|
||||
children: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<SafeAreaProviderCompat>
|
||||
<MaybeScreenContainer
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a lazy screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
header = ({ layout, options }: BottomTabHeaderProps) => (
|
||||
<Header
|
||||
{...options}
|
||||
layout={layout}
|
||||
title={getHeaderTitle(options, route.name)}
|
||||
/>
|
||||
),
|
||||
} = descriptor.options;
|
||||
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
visible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
<Screen
|
||||
focused={isFocused}
|
||||
route={descriptor.route}
|
||||
navigation={descriptor.navigation}
|
||||
headerShown={descriptor.options.headerShown}
|
||||
headerStatusBarHeight={
|
||||
descriptor.options.headerStatusBarHeight
|
||||
}
|
||||
header={header({
|
||||
layout: dimensions,
|
||||
route: descriptor.route,
|
||||
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
||||
options: descriptor.options,
|
||||
})}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</Screen>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
||||
{renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
<View
|
||||
accessibilityElementsHidden={!isFocused}
|
||||
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
|
||||
style={[styles.content, { backgroundColor: colors.background }, style]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default class BottomTabView extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
lazy: true,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const focusedRouteKey = nextProps.state.routes[nextProps.state.index].key;
|
||||
|
||||
return {
|
||||
// Set the current tab to be loaded if it was not loaded before
|
||||
loaded: prevState.loaded.includes(focusedRouteKey)
|
||||
? prevState.loaded
|
||||
: [...prevState.loaded, focusedRouteKey],
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { state, tabBarOptions } = this.props;
|
||||
|
||||
const dimensions = Dimensions.get('window');
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
state,
|
||||
dimensions,
|
||||
layout: { width: dimensions.width, height: 0 },
|
||||
insets: initialSafeAreaInsets,
|
||||
adaptive: tabBarOptions?.adaptive,
|
||||
labelPosition: tabBarOptions?.labelPosition,
|
||||
tabStyle: tabBarOptions?.tabStyle,
|
||||
style: tabBarOptions?.style,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
loaded: [state.routes[state.index].key],
|
||||
tabBarHeight: tabBarHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private renderTabBar = () => {
|
||||
const {
|
||||
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
||||
tabBarOptions,
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
} = this.props;
|
||||
return tabBar({
|
||||
...tabBarOptions,
|
||||
state: state,
|
||||
descriptors: descriptors,
|
||||
navigation: navigation,
|
||||
});
|
||||
};
|
||||
|
||||
private handleTabBarHeightChange = (height: number) => {
|
||||
this.setState((state) => {
|
||||
if (state.tabBarHeight !== height) {
|
||||
return { tabBarHeight: height };
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
lazy,
|
||||
detachInactiveScreens = true,
|
||||
sceneContainerStyle,
|
||||
} = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded, tabBarHeight } = this.state;
|
||||
const isScreensEnabled = screensEnabled?.() && detachInactiveScreens;
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<ScreenContainer
|
||||
// @ts-ignore
|
||||
enabled={isScreensEnabled}
|
||||
style={styles.container}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(route.key) && !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}
|
||||
enabled={isScreensEnabled}
|
||||
>
|
||||
<SceneContent
|
||||
isFocused={isFocused}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
{descriptor.render()}
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider
|
||||
value={this.handleTabBarHeightChange}
|
||||
>
|
||||
{this.renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
96
packages/bottom-tabs/src/views/ResourceSavingScene.tsx
Normal file
96
packages/bottom-tabs/src/views/ResourceSavingScene.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default function ResourceSavingScene({
|
||||
isVisible,
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Props) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen activityState={isVisible ? 2 : 0} style={style} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen active={isVisible ? 1 : 0} style={style} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
return (
|
||||
<View
|
||||
// @ts-expect-error: hidden exists on web, but not in React Native
|
||||
hidden={!isVisible}
|
||||
style={[
|
||||
{ display: isVisible ? 'flex' : 'none' },
|
||||
styles.container,
|
||||
style,
|
||||
]}
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style]}
|
||||
// box-none doesn't seem to work properly on Android
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
>
|
||||
<View
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
// This is an workaround for a bug where the clipped view never re-appears
|
||||
Platform.OS === 'ios' ? !isVisible : true
|
||||
}
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
style={isVisible ? styles.attached : styles.detached}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
attached: {
|
||||
flex: 1,
|
||||
},
|
||||
detached: {
|
||||
flex: 1,
|
||||
top: FAR_FAR_AWAY,
|
||||
},
|
||||
});
|
||||
49
packages/bottom-tabs/src/views/SafeAreaProviderCompat.tsx
Normal file
49
packages/bottom-tabs/src/views/SafeAreaProviderCompat.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaConsumer,
|
||||
initialWindowSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context';
|
||||
import {
|
||||
getStatusBarHeight,
|
||||
getBottomSpace,
|
||||
} from 'react-native-iphone-x-helper';
|
||||
|
||||
// The provider component for safe area initializes asynchornously
|
||||
// Until the insets are available, there'll be blank screen
|
||||
// To avoid the blank screen, we specify some initial values
|
||||
export const initialSafeAreaInsets = {
|
||||
// Approximate values which are good enough for most cases
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
right: 0,
|
||||
left: 0,
|
||||
// If we are on a newer version of the library, we can get the correct window insets
|
||||
// The component might not be filling the window, but this is good enough for most cases
|
||||
...initialWindowSafeAreaInsets,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
// https://github.com/react-navigation/react-navigation/issues/174
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}}
|
||||
</SafeAreaConsumer>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
||||
import { ResourceSavingView } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
let Screens: typeof import('react-native-screens') | undefined;
|
||||
|
||||
try {
|
||||
Screens = require('react-native-screens');
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
export function MaybeScreen({ visible, children, ...rest }: Props) {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return (
|
||||
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
|
||||
{children}
|
||||
</Screens.Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSavingView visible={visible} {...rest}>
|
||||
{children}
|
||||
</ResourceSavingView>
|
||||
);
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" },
|
||||
{ "path": "../elements" }
|
||||
{ "path": "../native" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
|
||||
826
packages/compat/CHANGELOG.md
Normal file
826
packages/compat/CHANGELOG.md
Normal file
@@ -0,0 +1,826 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.15](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.14...@react-navigation/compat@5.3.15) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.14](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.13...@react-navigation/compat@5.3.14) (2021-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.13](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.12...@react-navigation/compat@5.3.13) (2021-01-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.12](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.11...@react-navigation/compat@5.3.12) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.10...@react-navigation/compat@5.3.11) (2021-01-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.9...@react-navigation/compat@5.3.10) (2020-11-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.8...@react-navigation/compat@5.3.9) (2020-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.7...@react-navigation/compat@5.3.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.6...@react-navigation/compat@5.3.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.5...@react-navigation/compat@5.3.6) (2020-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.4...@react-navigation/compat@5.3.5) (2020-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.3...@react-navigation/compat@5.3.4) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.2...@react-navigation/compat@5.3.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.1...@react-navigation/compat@5.3.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.0...@react-navigation/compat@5.3.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.8...@react-navigation/compat@5.3.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.7...@react-navigation/compat@5.2.8) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.6...@react-navigation/compat@5.2.7) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.5...@react-navigation/compat@5.2.6) (2020-09-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.4...@react-navigation/compat@5.2.5) (2020-08-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.3...@react-navigation/compat@5.2.4) (2020-07-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.3) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix false warning due to change in Object.assign in metro preset ([5e358b3](https://github.com/react-navigation/react-navigation/commit/5e358b3aadac7bb186521872d515fff2e571a940)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.2) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix false warning due to change in Object.assign in metro preset ([240a706](https://github.com/react-navigation/react-navigation/commit/240a706a56220b63d603a52407a738c2872349dd)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.0...@react-navigation/compat@5.2.1) (2020-07-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.28...@react-navigation/compat@5.2.0) (2020-07-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a getComponent prop to lazily specify components ([f418029](https://github.com/react-navigation/react-navigation/commit/f4180295bf22e32c65f6a7ab7089523cb2de58fb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.28](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.27...@react-navigation/compat@5.1.28) (2020-06-25)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.27](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.26...@react-navigation/compat@5.1.27) (2020-06-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* more improvements to types ([d244488](https://github.com/react-navigation/react-navigation/commit/d2444887be227bbbdcfcb13a7f26a8ebb344043e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.26](https://github.com/react-navigation/react-navigation/tree/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
|
||||
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6785)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.5...@react-navigation/compat@5.0.6) (2020-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add NavigationEvents ([d69b0db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/d69b0db60455b8789276822ba73f5349db8842d7)), closes [/github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/issuecomment-588268512)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.4...@react-navigation/compat@5.0.5) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.3...@react-navigation/compat@5.0.4) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.2...@react-navigation/compat@5.0.3) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.1...@react-navigation/compat@5.0.2) (2020-02-11)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.34...@react-navigation/compat@5.0.1) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.34](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.33...@react-navigation/compat@5.0.0-alpha.34) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.33](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.32...@react-navigation/compat@5.0.0-alpha.33) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.32](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.31...@react-navigation/compat@5.0.0-alpha.32) (2020-02-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.31](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.31) (2020-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.29) (2020-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.28](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.27...@react-navigation/compat@5.0.0-alpha.28) (2020-01-24)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.27](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.26...@react-navigation/compat@5.0.0-alpha.27) (2020-01-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensure re-render on isFirstRouteInParent change in compat layer ([14ae373](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/14ae3738cf46088e082bd1c60b9dcc6dacacd1bf))
|
||||
* improvements to the compat layer ([2a76dc4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/2a76dc4d3c4cc0365a3afcff6ac321145efed026))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.26](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.25](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.24...@react-navigation/compat@5.0.0-alpha.25) (2020-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/265)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.24](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.23...@react-navigation/compat@5.0.0-alpha.24) (2020-01-13)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.23](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.23) (2020-01-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.22](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.22) (2020-01-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.17...@react-navigation/compat@5.0.0-alpha.18) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.15...@react-navigation/compat@5.0.0-alpha.16) (2019-12-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.14...@react-navigation/compat@5.0.0-alpha.15) (2019-11-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.13...@react-navigation/compat@5.0.0-alpha.14) (2019-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.12...@react-navigation/compat@5.0.0-alpha.13) (2019-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.11...@react-navigation/compat@5.0.0-alpha.12) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.10...@react-navigation/compat@5.0.0-alpha.11) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop isFirstRouteInParent method ([#145](https://github.com/react-navigation/navigation-ex/issues/145)) ([3a77107](https://github.com/react-navigation/navigation-ex/commit/3a77107))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.9...@react-navigation/compat@5.0.0-alpha.10) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.8...@react-navigation/compat@5.0.0-alpha.9) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.7...@react-navigation/compat@5.0.0-alpha.8) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.2...@react-navigation/compat@5.0.0-alpha.3) (2019-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw when wrapping a compat navigatofor compat ([8920da6](https://github.com/react-navigation/navigation-ex/commit/8920da6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.1...@react-navigation/compat@5.0.0-alpha.2) (2019-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add createSwitchNavigator ([ff50282](https://github.com/react-navigation/navigation-ex/commit/ff50282))
|
||||
* add withNavigation and withNavigationFocus ([114a5dc](https://github.com/react-navigation/navigation-ex/commit/114a5dc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 5.0.0-alpha.1 (2019-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix dispatching compat actions ([88a560a](https://github.com/react-navigation/navigation-ex/commit/88a560a))
|
||||
* support legacy goBack method ([d83d3de](https://github.com/react-navigation/navigation-ex/commit/d83d3de))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))
|
||||
5
packages/compat/README.md
Normal file
5
packages/compat/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# `@react-navigation/compat`
|
||||
|
||||
Compatibility layer to write navigator definitions in static configuration format.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/compatibility/).
|
||||
@@ -1,23 +1,17 @@
|
||||
{
|
||||
"name": "@react-navigation/elements",
|
||||
"description": "UI Components for React Navigation",
|
||||
"version": "1.0.0-next.10",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.3.15",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/react-navigation/react-navigation.git",
|
||||
"directory": "packages/elements"
|
||||
"directory": "packages/compat"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-navigation/react-navigation/issues"
|
||||
},
|
||||
"homepage": "https://reactnavigation.org",
|
||||
"homepage": "https://reactnavigation.org/docs/compatibility/",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "src/index.tsx",
|
||||
"source": "src/index.tsx",
|
||||
@@ -37,22 +31,15 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-masked-view/masked-view": "^0.2.3",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@react-navigation/native": "^5.9.4",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"typescript": "^4.2.3"
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0"
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*"
|
||||
},
|
||||
"react-native-builder-bob": {
|
||||
"source": "src",
|
||||
17
packages/compat/src/CompatScreen.tsx
Normal file
17
packages/compat/src/CompatScreen.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import useCompatNavigation from './useCompatNavigation';
|
||||
|
||||
type Props = {
|
||||
getComponent: () => React.ComponentType<any>;
|
||||
};
|
||||
|
||||
function CompatScreen({ getComponent }: Props) {
|
||||
const navigation = useCompatNavigation();
|
||||
const screenProps = React.useContext(ScreenPropsContext);
|
||||
const ScreenComponent = getComponent();
|
||||
|
||||
return <ScreenComponent navigation={navigation} screenProps={screenProps} />;
|
||||
}
|
||||
|
||||
export default React.memo(CompatScreen);
|
||||
13
packages/compat/src/DrawerActions.tsx
Normal file
13
packages/compat/src/DrawerActions.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DrawerActions, DrawerActionType } from '@react-navigation/native';
|
||||
|
||||
export function openDrawer(): DrawerActionType {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer(): DrawerActionType {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer(): DrawerActionType {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
48
packages/compat/src/NavigationActions.tsx
Normal file
48
packages/compat/src/NavigationActions.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { CommonActions, NavigationState } from '@react-navigation/native';
|
||||
|
||||
export function navigate({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
action?: never;
|
||||
}): CommonActions.Action {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `navigate`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return CommonActions.navigate({
|
||||
name: routeName,
|
||||
key: key,
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function back(options?: { key?: null | string }) {
|
||||
return options?.key != null
|
||||
? (state: NavigationState) => ({
|
||||
...CommonActions.goBack(),
|
||||
source: options.key,
|
||||
target: state.key,
|
||||
})
|
||||
: CommonActions.goBack();
|
||||
}
|
||||
|
||||
export function setParams({
|
||||
params,
|
||||
key,
|
||||
}: {
|
||||
params: object;
|
||||
key?: string;
|
||||
}): CommonActions.Action {
|
||||
return {
|
||||
...CommonActions.setParams(params),
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
45
packages/compat/src/NavigationEvents.tsx
Normal file
45
packages/compat/src/NavigationEvents.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
type Props = {
|
||||
onWillFocus?: () => void;
|
||||
onDidFocus?: () => void;
|
||||
onWillBlur?: () => void;
|
||||
onDidBlur?: () => void;
|
||||
};
|
||||
|
||||
export default function NavigationEvents(props: Props) {
|
||||
const navigation = useNavigation();
|
||||
const propsRef = React.useRef(props);
|
||||
|
||||
React.useEffect(() => {
|
||||
propsRef.current = props;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubFocus = navigation.addListener('focus', () => {
|
||||
propsRef.current.onWillFocus?.();
|
||||
});
|
||||
|
||||
const unsubBlur = navigation.addListener('blur', () => {
|
||||
propsRef.current.onWillBlur?.();
|
||||
});
|
||||
|
||||
// @ts-expect-error: transitionEnd may not exist on this navigator
|
||||
const unsubTransitionEnd = navigation.addListener('transitionEnd', () => {
|
||||
if (navigation.isFocused()) {
|
||||
propsRef.current.onDidFocus?.();
|
||||
} else {
|
||||
propsRef.current.onDidBlur?.();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubFocus();
|
||||
unsubBlur();
|
||||
unsubTransitionEnd();
|
||||
};
|
||||
}, [navigation]);
|
||||
|
||||
return null;
|
||||
}
|
||||
3
packages/compat/src/ScreenPropsContext.tsx
Normal file
3
packages/compat/src/ScreenPropsContext.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<unknown>(undefined);
|
||||
71
packages/compat/src/StackActions.tsx
Normal file
71
packages/compat/src/StackActions.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
CommonActions,
|
||||
StackActions,
|
||||
StackActionType,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export function reset(): CommonActions.Action {
|
||||
throw new Error(
|
||||
'The legacy `reset` action is not supported. Use the new reset API by accessing the original navigation object at `navigation.original`.'
|
||||
);
|
||||
}
|
||||
|
||||
export function replace({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
newKey,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
newKey?: string;
|
||||
action?: never;
|
||||
}): StackActionType {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'REPLACE',
|
||||
payload: {
|
||||
name: routeName,
|
||||
key: newKey,
|
||||
params,
|
||||
},
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
|
||||
export function push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
action?: never;
|
||||
}): StackActionType {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `push`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return StackActions.push(routeName, params);
|
||||
}
|
||||
|
||||
export function pop({ n = 1 }: { n: number }): StackActionType {
|
||||
return StackActions.pop(n);
|
||||
}
|
||||
|
||||
export function popToTop(): StackActionType {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss(): CommonActions.Action {
|
||||
throw new Error('The legacy `dismiss` action is not supported.');
|
||||
}
|
||||
18
packages/compat/src/SwitchActions.tsx
Normal file
18
packages/compat/src/SwitchActions.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TabActions, TabActionType } from '@react-navigation/native';
|
||||
|
||||
export function jumpTo({
|
||||
routeName,
|
||||
key,
|
||||
}: {
|
||||
routeName: string;
|
||||
key?: string;
|
||||
}): TabActionType {
|
||||
if (key === undefined) {
|
||||
return TabActions.jumpTo(routeName);
|
||||
} else {
|
||||
return {
|
||||
...TabActions.jumpTo(routeName),
|
||||
target: key,
|
||||
};
|
||||
}
|
||||
}
|
||||
220
packages/compat/src/createCompatNavigationProp.tsx
Normal file
220
packages/compat/src/createCompatNavigationProp.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import type {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import * as helpers from './helpers';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
type EventName =
|
||||
| 'action'
|
||||
| 'willFocus'
|
||||
| 'willBlur'
|
||||
| 'didFocus'
|
||||
| 'didBlur'
|
||||
| 'refocus';
|
||||
|
||||
export default function createCompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
>(
|
||||
navigation: NavigationPropType,
|
||||
state:
|
||||
| (RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
})
|
||||
| NavigationState
|
||||
| PartialState<NavigationState>,
|
||||
context: Record<string, any>,
|
||||
isFirstRouteInParent?: boolean
|
||||
): CompatNavigationProp<NavigationPropType> {
|
||||
context.parent = context.parent || {};
|
||||
context.subscriptions = context.subscriptions || {
|
||||
didFocus: new Map<() => void, () => void>(),
|
||||
didBlur: new Map<() => void, () => void>(),
|
||||
refocus: new Map<() => void, () => void>(),
|
||||
};
|
||||
|
||||
return {
|
||||
...navigation,
|
||||
...Object.entries(helpers).reduce<{
|
||||
[key: string]: (...args: any[]) => void;
|
||||
}>((acc, [name, method]: [string, Function]) => {
|
||||
if (name in navigation) {
|
||||
acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
original: navigation,
|
||||
addListener(type: EventName, callback: () => void) {
|
||||
let unsubscribe: () => void;
|
||||
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
unsubscribe = navigation.addListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
unsubscribe = navigation.addListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
context.subscriptions.didFocus.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const listener = () => {
|
||||
if (!navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
context.subscriptions.didBlur.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('tabPress', listener);
|
||||
context.subscriptions.refocus.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'action':
|
||||
throw new Error("Listening to 'action' events is not supported.");
|
||||
default:
|
||||
unsubscribe = navigation.addListener(type, callback);
|
||||
}
|
||||
|
||||
const subscription = () => unsubscribe();
|
||||
|
||||
subscription.remove = unsubscribe;
|
||||
|
||||
return subscription;
|
||||
},
|
||||
removeListener(type: EventName, callback: () => void) {
|
||||
context.subscriptions = context.subscriptions || {};
|
||||
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
navigation.removeListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
navigation.removeListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const unsubscribe = context.subscriptions.didFocus.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const unsubscribe = context.subscriptions.didBlur.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const unsubscribe = context.subscriptions.refocus.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'action':
|
||||
throw new Error("Listening to 'action' events is not supported.");
|
||||
default:
|
||||
navigation.removeListener(type, callback);
|
||||
}
|
||||
},
|
||||
state: {
|
||||
key: state.key,
|
||||
// @ts-expect-error
|
||||
routeName: state.name,
|
||||
// @ts-expect-error
|
||||
params: state.params ?? {},
|
||||
get index() {
|
||||
// @ts-expect-error
|
||||
if (state.index !== undefined) {
|
||||
// @ts-expect-error
|
||||
return state.index;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Looks like you are using 'navigation.state.index' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'index' property in the child navigation state."
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return state.state?.index;
|
||||
},
|
||||
get routes() {
|
||||
// @ts-expect-error
|
||||
if (state.routes !== undefined) {
|
||||
// @ts-expect-error
|
||||
return state.routes;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Looks like you are using 'navigation.state.routes' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'routes' property in the child navigation state."
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return state.state?.routes;
|
||||
},
|
||||
},
|
||||
getParam<T extends keyof ParamList>(
|
||||
paramName: T,
|
||||
defaultValue: ParamList[T]
|
||||
): ParamList[T] {
|
||||
// @ts-expect-error
|
||||
const params = state.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
isFirstRouteInParent(): boolean {
|
||||
if (typeof isFirstRouteInParent === 'boolean') {
|
||||
return isFirstRouteInParent;
|
||||
}
|
||||
|
||||
const { routes } = navigation.dangerouslyGetState();
|
||||
|
||||
return routes[0].key === state.key;
|
||||
},
|
||||
dangerouslyGetParent() {
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
if (parent) {
|
||||
return createCompatNavigationProp(
|
||||
parent,
|
||||
navigation.dangerouslyGetState(),
|
||||
context.parent
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
194
packages/compat/src/createCompatNavigatorFactory.tsx
Normal file
194
packages/compat/src/createCompatNavigatorFactory.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
TypedNavigator,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
NavigationRouteContext,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import type { CompatScreenType, CompatRouteConfig } from './types';
|
||||
|
||||
export default function createCompatNavigatorFactory<
|
||||
CreateNavigator extends () => TypedNavigator<
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
{},
|
||||
any,
|
||||
React.ComponentType<any>
|
||||
>
|
||||
>(createNavigator: CreateNavigator) {
|
||||
// @ts-expect-error: isCompat may or may not exist
|
||||
if (createNavigator.isCompat) {
|
||||
throw new Error(
|
||||
`The navigator is already in compat mode. You don't need to wrap it in 'createCompatNavigatorFactory'.`
|
||||
);
|
||||
}
|
||||
|
||||
const createCompatNavigator = <
|
||||
NavigationPropType extends NavigationProp<any, any, any, any, any>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {},
|
||||
NavigationConfig extends {} = React.ComponentProps<
|
||||
ReturnType<CreateNavigator>['Navigator']
|
||||
>
|
||||
>(
|
||||
routeConfig: CompatRouteConfig<NavigationPropType>,
|
||||
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
|
||||
order?: Extract<keyof ParamList, string>[];
|
||||
defaultNavigationOptions?: ScreenOptions;
|
||||
navigationOptions?: Record<string, any>;
|
||||
} = {}
|
||||
) => {
|
||||
const Pair = createNavigator();
|
||||
|
||||
const {
|
||||
order,
|
||||
defaultNavigationOptions,
|
||||
navigationOptions: parentNavigationOptions,
|
||||
...restConfig
|
||||
} = navigationConfig;
|
||||
|
||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const parentRouteParams = React.useContext(NavigationRouteContext)
|
||||
?.params;
|
||||
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map((name) => {
|
||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||
|
||||
let initialParams;
|
||||
|
||||
const routeConfigItem = routeConfig[name];
|
||||
|
||||
if ('getScreen' in routeConfigItem) {
|
||||
getScreenComponent = routeConfigItem.getScreen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else if ('screen' in routeConfigItem) {
|
||||
getScreenComponent = () => routeConfigItem.screen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else {
|
||||
getScreenComponent = () => routeConfigItem;
|
||||
}
|
||||
|
||||
const screenOptions = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: NavigationPropType;
|
||||
route: RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
}) => {
|
||||
// @ts-expect-error: navigationOptions may exists on the component, but TS is dumb
|
||||
const routeNavigationOptions = routeConfigItem.navigationOptions;
|
||||
const screenNavigationOptions = getScreenComponent()
|
||||
.navigationOptions;
|
||||
|
||||
if (
|
||||
routeNavigationOptions == null &&
|
||||
screenNavigationOptions == null
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options =
|
||||
typeof routeNavigationOptions === 'function' ||
|
||||
typeof screenNavigationOptions === 'function'
|
||||
? {
|
||||
navigation: createCompatNavigationProp<
|
||||
NavigationPropType,
|
||||
ParamList
|
||||
>(navigation, route, {}),
|
||||
navigationOptions: defaultNavigationOptions || {},
|
||||
screenProps,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...(typeof routeNavigationOptions === 'function'
|
||||
? routeNavigationOptions(options)
|
||||
: routeNavigationOptions),
|
||||
...(typeof screenNavigationOptions === 'function'
|
||||
? (screenNavigationOptions as (o: any) => ScreenOptions)(
|
||||
options
|
||||
)
|
||||
: screenNavigationOptions),
|
||||
} as ScreenOptions;
|
||||
};
|
||||
|
||||
return (
|
||||
<Pair.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
initialParams={{ ...parentRouteParams, ...initialParams }}
|
||||
options={screenOptions}
|
||||
>
|
||||
{() => <CompatScreen getComponent={getScreenComponent} />}
|
||||
</Pair.Screen>
|
||||
);
|
||||
}),
|
||||
[parentRouteParams, screenProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenPropsContext.Provider value={screenProps}>
|
||||
<Pair.Navigator
|
||||
{...(restConfig as NavigationConfig)}
|
||||
screenOptions={defaultNavigationOptions}
|
||||
>
|
||||
{screens}
|
||||
</Pair.Navigator>
|
||||
</ScreenPropsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.navigationOptions = parentNavigationOptions;
|
||||
|
||||
return Navigator;
|
||||
};
|
||||
|
||||
Object.defineProperties(createCompatNavigator, {
|
||||
isCompat: {
|
||||
get() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
router: {
|
||||
get() {
|
||||
throw new Error(
|
||||
"It's no longer possible to access the router with the 'router' property."
|
||||
);
|
||||
},
|
||||
set() {
|
||||
throw new Error(
|
||||
"It's no longer possible to override the router by assigning the 'router' property."
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return createCompatNavigator;
|
||||
}
|
||||
34
packages/compat/src/createSwitchNavigator.tsx
Normal file
34
packages/compat/src/createSwitchNavigator.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
|
||||
|
||||
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
|
||||
|
||||
function SwitchNavigator(props: Props) {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
{},
|
||||
{},
|
||||
TabActionHelpers<ParamListBase>
|
||||
>(TabRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigatorFactory<
|
||||
TabNavigationState<ParamListBase>,
|
||||
{},
|
||||
{},
|
||||
typeof SwitchNavigator
|
||||
>(SwitchNavigator)
|
||||
);
|
||||
88
packages/compat/src/helpers.tsx
Normal file
88
packages/compat/src/helpers.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
|
||||
type NavigateActionPayload = Parameters<typeof NavigationActions.navigate>['0'];
|
||||
|
||||
type NavigateActionType = ReturnType<typeof NavigationActions.navigate>;
|
||||
|
||||
export function navigate(
|
||||
routeName: string,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function navigate(options: NavigateActionPayload): NavigateActionType;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function navigate(
|
||||
options: string | NavigateActionPayload,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType {
|
||||
if (typeof options === 'string') {
|
||||
return NavigationActions.navigate({
|
||||
routeName: options,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
return NavigationActions.navigate(options);
|
||||
}
|
||||
|
||||
export function goBack(fromKey?: null | string) {
|
||||
return NavigationActions.back({ key: fromKey });
|
||||
}
|
||||
|
||||
export function setParams(params: object) {
|
||||
return NavigationActions.setParams({ params });
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
return StackActions.reset();
|
||||
}
|
||||
|
||||
export function replace(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.replace({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function push(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function pop(n: number = 1) {
|
||||
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
||||
}
|
||||
|
||||
export function popToTop() {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss() {
|
||||
return StackActions.dismiss();
|
||||
}
|
||||
|
||||
export function jumpTo(routeName: string) {
|
||||
return SwitchActions.jumpTo({ routeName });
|
||||
}
|
||||
|
||||
export function openDrawer() {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer() {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
19
packages/compat/src/index.tsx
Normal file
19
packages/compat/src/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
|
||||
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
|
||||
|
||||
export { default as createCompatNavigatorFactory } from './createCompatNavigatorFactory';
|
||||
|
||||
export { default as createCompatNavigationProp } from './createCompatNavigationProp';
|
||||
|
||||
export { default as createSwitchNavigator } from './createSwitchNavigator';
|
||||
|
||||
export { default as withNavigation } from './withNavigation';
|
||||
export { default as withNavigationFocus } from './withNavigationFocus';
|
||||
|
||||
export { default as NavigationEvents } from './NavigationEvents';
|
||||
|
||||
export * from './types';
|
||||
92
packages/compat/src/types.tsx
Normal file
92
packages/compat/src/types.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
ParamListBase,
|
||||
NavigationProp,
|
||||
Route,
|
||||
} from '@react-navigation/native';
|
||||
import type * as helpers from './helpers';
|
||||
|
||||
export type CompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
RouteName extends Extract<keyof ParamList, string> = Extract<
|
||||
NavigationPropType extends NavigationProp<any, infer R> ? R : string,
|
||||
string
|
||||
>
|
||||
> = Omit<NavigationPropType, keyof typeof helpers> &
|
||||
{
|
||||
[method in Extract<keyof NavigationPropType, keyof typeof helpers>]: (
|
||||
...args: Parameters<typeof helpers[method]>
|
||||
) => void;
|
||||
} & {
|
||||
state: Route<RouteName> & {
|
||||
routeName: RouteName;
|
||||
};
|
||||
getParam<T extends keyof ParamList[RouteName]>(
|
||||
paramName: T,
|
||||
defaultValue?: ParamList[RouteName][T]
|
||||
): ParamList[RouteName][T];
|
||||
isFirstRouteInParent(): boolean;
|
||||
dangerouslyGetParent<
|
||||
T = NavigationProp<ParamListBase> | undefined
|
||||
>(): T extends NavigationProp<ParamListBase>
|
||||
? CompatNavigationProp<T>
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export type CompatNavigationOptions<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {}
|
||||
> =
|
||||
| ((options: {
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
navigationOptions: Partial<ScreenOptions>;
|
||||
screenProps: unknown;
|
||||
}) => ScreenOptions)
|
||||
| ScreenOptions;
|
||||
|
||||
export type CompatScreenType<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>
|
||||
> = React.ComponentType<{
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
screenProps: unknown;
|
||||
}> & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
};
|
||||
|
||||
export type CompatRouteConfig<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
> = {
|
||||
[RouteName in keyof ParamList]:
|
||||
| React.ComponentType<any>
|
||||
| ((
|
||||
| { screen: React.ComponentType<any> }
|
||||
| { getScreen(): React.ComponentType<any> }
|
||||
) & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
params?: ParamList[RouteName];
|
||||
});
|
||||
};
|
||||
34
packages/compat/src/useCompatNavigation.tsx
Normal file
34
packages/compat/src/useCompatNavigation.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
useNavigation,
|
||||
useRoute,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
useNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
export default function useCompatNavigation<
|
||||
T extends NavigationProp<ParamListBase>
|
||||
>() {
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
(state) => state.routes[0].key === route.key
|
||||
);
|
||||
|
||||
const context = React.useRef<Record<string, any>>({});
|
||||
|
||||
return React.useMemo(
|
||||
() =>
|
||||
createCompatNavigationProp(
|
||||
navigation,
|
||||
route as any,
|
||||
context.current,
|
||||
isFirstRouteInParent
|
||||
) as CompatNavigationProp<T>,
|
||||
[isFirstRouteInParent, navigation, route]
|
||||
);
|
||||
}
|
||||
34
packages/compat/src/withNavigation.tsx
Normal file
34
packages/compat/src/withNavigation.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import type { NavigationProp, ParamListBase } from '@react-navigation/native';
|
||||
import useCompatNavigation from './useCompatNavigation';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
type InjectedProps<T extends NavigationProp<ParamListBase>> = {
|
||||
navigation: CompatNavigationProp<T>;
|
||||
};
|
||||
|
||||
export default function withNavigation<
|
||||
T extends NavigationProp<ParamListBase>,
|
||||
P extends InjectedProps<T>,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps<T>> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const navigation = useCompatNavigation<T>();
|
||||
|
||||
// @ts-expect-error: type checking HOC is hard
|
||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigation(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
31
packages/compat/src/withNavigationFocus.tsx
Normal file
31
packages/compat/src/withNavigationFocus.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
type InjectedProps = {
|
||||
isFocused: boolean;
|
||||
};
|
||||
|
||||
export default function withNavigationFocus<
|
||||
P extends InjectedProps,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
// @ts-expect-error: type checking HOC is hard
|
||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigationFocus(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
@@ -3,91 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.7...@react-navigation/core@6.0.0-next.8) (2021-05-16)
|
||||
## [5.15.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.15.2...@react-navigation/core@5.15.3) (2021-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix type error when passing unannotated navigation ref ([dc4ffc0](https://github.com/react-navigation/react-navigation/commit/dc4ffc0171b4535fe1b6e839b9d54350121bcf55))
|
||||
* properly resolve initialRouteNames ([976178d](https://github.com/react-navigation/react-navigation/commit/976178d0986a90697931ab9cc2c297eb7938e28b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.6...@react-navigation/core@6.0.0-next.7) (2021-05-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* return a NavigationContent component from useNavigationBuilder ([1179d56](https://github.com/react-navigation/react-navigation/commit/1179d56c5008270753feef41acdc1dbd2191efcf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.5...@react-navigation/core@6.0.0-next.6) (2021-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix type annotations for useNavigation (again) ([929c3e3](https://github.com/react-navigation/react-navigation/commit/929c3e3a3b3eb32d197ef1f887dc4cbdce48eaff))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.4...@react-navigation/core@6.0.0-next.5) (2021-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix type annotations for useNavigation ([7da45e1](https://github.com/react-navigation/react-navigation/commit/7da45e1e8951ff46e09db4ebc2c88085c67ab8e9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.3...@react-navigation/core@6.0.0-next.4) (2021-05-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a new component to group multiple screens with common options ([1a6aebe](https://github.com/react-navigation/react-navigation/commit/1a6aebefcb77ea708687475c55742407d69808ce))
|
||||
* add ability to specify root param list ([b28bfdd](https://github.com/react-navigation/react-navigation/commit/b28bfddc17cbf3996fac04a34b2a7085ecf88be5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.2...@react-navigation/core@6.0.0-next.3) (2021-05-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a CompositeScreenProps type ([def7c03](https://github.com/react-navigation/react-navigation/commit/def7c03d7d7b42cf322f4e277f8f76858717654e))
|
||||
* add helper and hook for container ref ([0ecd112](https://github.com/react-navigation/react-navigation/commit/0ecd112ec9786a26261ada3d33ef44dc1ec84da0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.1...@react-navigation/core@6.0.0-next.2) (2021-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly resolve initialRouteNames ([c38906a](https://github.com/react-navigation/react-navigation/commit/c38906a7a09b997f37ce56734ea823c75ea744db))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve useNavigationState typing ([#9464](https://github.com/react-navigation/react-navigation/issues/9464)) ([84020a0](https://github.com/react-navigation/react-navigation/commit/84020a0b27ebae50d3037438a51d95eb31b02424))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0...@react-navigation/core@6.0.0-next.1) (2021-03-10)
|
||||
## [5.15.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.15.1...@react-navigation/core@5.15.2) (2021-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
@@ -95,50 +22,36 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.3...@react-navigation/core@6.0.0-next.0) (2021-03-09)
|
||||
## [5.15.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.15.0...@react-navigation/core@5.15.1) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.15.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.4...@react-navigation/core@5.15.0) (2021-01-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
|
||||
* check duplicate names only for immediate nested screens ([36a9b4f](https://github.com/react-navigation/react-navigation/commit/36a9b4f866c49d6f7350405c54b86bd77e374eb5))
|
||||
* don't merge params on navigation ([366d018](https://github.com/react-navigation/react-navigation/commit/366d0181dc02597b8d11e343f37fc77bee164c70))
|
||||
* drop dangerously prefix from getState and getParent ([227f133](https://github.com/react-navigation/react-navigation/commit/227f133536af85dc5ff85eeb269b76ed80cd3f05))
|
||||
* drop support for legacy linking config ([0e13e8d](https://github.com/react-navigation/react-navigation/commit/0e13e8d23cc2ea74f3b0fce9334ee5c8be2484f4))
|
||||
* fix default screen options not being respected ([03ba1f2](https://github.com/react-navigation/react-navigation/commit/03ba1f2930cb731266e6bc3044c67fb267837ed1))
|
||||
* fix incorrect state change events in independent nested container ([b82a912](https://github.com/react-navigation/react-navigation/commit/b82a9126bb91a84e21473723ce40da0cce732a39)), closes [#9080](https://github.com/react-navigation/react-navigation/issues/9080)
|
||||
* print an error when passing a second argument to useFocusEffect ([c361795](https://github.com/react-navigation/react-navigation/commit/c361795d97eb20150913ddc3fbf139e095d25830))
|
||||
* remove the state property from route prop ([ebab518](https://github.com/react-navigation/react-navigation/commit/ebab5183522f5ae03f50f88289c0e7acc208dc02))
|
||||
* show redbox instead of crash if navigation isn't initialized ([13d8553](https://github.com/react-navigation/react-navigation/commit/13d85530ae684eb956d3c4df25919572caf0ec1a))
|
||||
* print an error when passing a second argument to useFocusEffect ([2317633](https://github.com/react-navigation/react-navigation/commit/23176336528f98924d19f321d41cb70f13300edd))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a way to specify an unique ID for screens ([15b8bb3](https://github.com/react-navigation/react-navigation/commit/15b8bb34584db3cb166f6aafd45f0b95f14fde62))
|
||||
* add an option to specify default options for the navigator ([c85f2ff](https://github.com/react-navigation/react-navigation/commit/c85f2ff47a2b3d403a3cbe993b46d04914358ba5))
|
||||
* allow returning null or undefined to skip actions with dispatch ([d6466b7](https://github.com/react-navigation/react-navigation/commit/d6466b7a4b5d3087981dbd50a5f5f56a6092edb3))
|
||||
* associate path with the route it opens when deep linking ([#9384](https://github.com/react-navigation/react-navigation/issues/9384)) ([86e64fd](https://github.com/react-navigation/react-navigation/commit/86e64fdcd81a57cf3f3bdab4c9035b52984e7009)), closes [#9102](https://github.com/react-navigation/react-navigation/issues/9102)
|
||||
* warn on duplicate screen names across navigators ([02a031e](https://github.com/react-navigation/react-navigation/commit/02a031e46eac432fc3e26c5de30c7bdf0a81ce49))
|
||||
* add a way to specify an unique ID for screens ([b19f76b](https://github.com/react-navigation/react-navigation/commit/b19f76bfffe623759e67d925bfd067c753a453bf))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit drops support for legacy linking config which allowed screens to be specified without the screens property in the config.
|
||||
* Previous versions of React Navigation merged params on navigation which caused confusion. This commit changes params not to be merged.
|
||||
|
||||
The old behaviour can still be achieved by passing `merge: true` explicitly:
|
||||
|
||||
```js
|
||||
CommonActions.navigate({
|
||||
name: 'bar',
|
||||
params: { fruit: 'orange' },
|
||||
merge: true,
|
||||
})
|
||||
```
|
||||
`initialParams` specified for the screen are always merged.
|
||||
* any code which relies on `route.state` will break.
|
||||
## [5.14.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.3...@react-navigation/core@5.14.4) (2020-11-20)
|
||||
|
||||
Previous versions printed a warning on accessing `route.state`. This commit removes the property entirely. Accessing this property isn't safe since child navigator state isn't gurranteed to be in sync with parent navigator state and cause subtle bugs in apps.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix incorrect state change events in independent nested container ([95b2599](https://github.com/react-navigation/react-navigation/commit/95b2599877f5ceedf753e399e0586bb4af54cb87)), closes [#9080](https://github.com/react-navigation/react-navigation/issues/9080)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "6.0.0-next.8",
|
||||
"version": "5.15.3",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -35,22 +35,21 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^6.0.0-next.2",
|
||||
"@react-navigation/routers": "^5.7.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.22",
|
||||
"query-string": "^7.0.0",
|
||||
"nanoid": "^3.1.15",
|
||||
"query-string": "^6.13.6",
|
||||
"react-is": "^16.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"del-cli": "^3.0.1",
|
||||
"immer": "^9.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"react-test-renderer": "~16.13.1",
|
||||
"typescript": "^4.2.3"
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
InitialState,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import UnhandledActionContext from './UnhandledActionContext';
|
||||
@@ -21,9 +20,6 @@ import useOptionsGetters from './useOptionsGetters';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useSyncState from './useSyncState';
|
||||
import checkSerializable from './checkSerializable';
|
||||
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
||||
import type {
|
||||
NavigationContainerEventMap,
|
||||
NavigationContainerRef,
|
||||
@@ -32,8 +28,10 @@ import type {
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
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.";
|
||||
|
||||
const serializableWarnings: string[] = [];
|
||||
const duplicateNameWarnings: string[] = [];
|
||||
|
||||
try {
|
||||
/**
|
||||
@@ -102,13 +100,13 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
independent,
|
||||
children,
|
||||
}: NavigationContainerProps,
|
||||
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
|
||||
ref?: React.Ref<NavigationContainerRef>
|
||||
) {
|
||||
const parent = React.useContext(NavigationStateContext);
|
||||
|
||||
if (!parent.isDefault && !independent) {
|
||||
throw new Error(
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,10 +138,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationAction)
|
||||
) => {
|
||||
if (listeners.focus[0] == null) {
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
listeners.focus[0]((navigation) => navigation.dispatch(action));
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
|
||||
listeners.focus[0]((navigation) => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
@@ -167,15 +165,15 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const target = state?.key ?? keyedListeners.getState.root?.().key;
|
||||
|
||||
if (target == null) {
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
listeners.focus[0]((navigation) =>
|
||||
navigation.dispatch({
|
||||
...CommonActions.reset(state),
|
||||
target,
|
||||
})
|
||||
);
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
|
||||
listeners.focus[0]((navigation) =>
|
||||
navigation.dispatch({
|
||||
...CommonActions.reset(state),
|
||||
target,
|
||||
})
|
||||
);
|
||||
},
|
||||
[keyedListeners.getState, listeners.focus]
|
||||
);
|
||||
@@ -185,15 +183,14 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
}, [keyedListeners.getState]);
|
||||
|
||||
const getCurrentRoute = React.useCallback(() => {
|
||||
const state = getRootState();
|
||||
|
||||
if (state == null) {
|
||||
let state = getRootState();
|
||||
if (state === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const route = findFocusedRoute(state);
|
||||
|
||||
return route as Route<string> | 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<NavigationContainerEventMap>();
|
||||
@@ -201,10 +198,16 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...Object.keys(CommonActions).reduce<any>((acc, name) => {
|
||||
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
||||
any
|
||||
>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
// @ts-expect-error: this is ok
|
||||
dispatch(CommonActions[name](...args));
|
||||
dispatch(
|
||||
CommonActions[name](
|
||||
// @ts-expect-error: we can't know the type statically
|
||||
...args
|
||||
)
|
||||
);
|
||||
return acc;
|
||||
}, {}),
|
||||
...emitter.create('root'),
|
||||
@@ -212,11 +215,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
dispatch,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
getState: () => state,
|
||||
getParent: () => undefined,
|
||||
dangerouslyGetState: () => state,
|
||||
dangerouslyGetParent: () => undefined,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
isReady: () => listeners.focus[0] != null,
|
||||
}));
|
||||
|
||||
const onDispatchAction = React.useCallback(
|
||||
@@ -292,17 +294,15 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const hydratedState = getRootState();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (hydratedState !== undefined) {
|
||||
const serializableResult = checkSerializable(hydratedState);
|
||||
if (state !== undefined) {
|
||||
const result = checkSerializable(state);
|
||||
|
||||
if (!serializableResult.serializable) {
|
||||
const { location, reason } = serializableResult;
|
||||
if (!result.serializable) {
|
||||
const { location, reason } = result;
|
||||
|
||||
let path = '';
|
||||
let pointer: Record<any, any> = hydratedState;
|
||||
let pointer: Record<any, any> = state;
|
||||
let params = false;
|
||||
|
||||
for (let i = 0; i < location.length; i++) {
|
||||
@@ -344,28 +344,13 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateRouteNamesResult = checkDuplicateRouteNames(
|
||||
hydratedState
|
||||
);
|
||||
|
||||
if (duplicateRouteNamesResult.length) {
|
||||
const message = `Found screens with the same name nested inside one another. Check:\n${duplicateRouteNamesResult.map(
|
||||
(locations) => `\n${locations.join(', ')}`
|
||||
)}\n\nThis can cause confusing behavior during navigation. Consider using unique names for each screen instead.`;
|
||||
|
||||
if (!duplicateNameWarnings.includes(message)) {
|
||||
duplicateNameWarnings.push(message);
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitter.emit({ type: 'state', data: { state } });
|
||||
|
||||
if (!isFirstMountRef.current && onStateChangeRef.current) {
|
||||
onStateChangeRef.current(hydratedState);
|
||||
onStateChangeRef.current(getRootState());
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { ParamListBase } from '@react-navigation/routers';
|
||||
import type { RouteGroupConfig } from './types';
|
||||
|
||||
/**
|
||||
* Empty component used for grouping screen configs.
|
||||
*/
|
||||
export default function Group<
|
||||
ParamList extends ParamListBase,
|
||||
ScreenOptions extends {}
|
||||
>(_: RouteGroupConfig<ParamList, ScreenOptions>) {
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import type { NavigationProp } from './types';
|
||||
* Context which holds the navigation prop for a screen.
|
||||
*/
|
||||
const NavigationContext = React.createContext<
|
||||
NavigationProp<ParamListBase> | undefined
|
||||
NavigationProp<ParamListBase, string, any, any> | undefined
|
||||
>(undefined);
|
||||
|
||||
export default NavigationContext;
|
||||
|
||||
@@ -9,10 +9,14 @@ import NavigationStateContext from './NavigationStateContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import useOptionsGetters from './useOptionsGetters';
|
||||
import type { NavigationProp, RouteConfigComponent } from './types';
|
||||
import type { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||
|
||||
type Props<State extends NavigationState, ScreenOptions extends {}> = {
|
||||
screen: RouteConfigComponent<ParamListBase, string> & { name: string };
|
||||
type Props<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
|
||||
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||
route: Route<string>;
|
||||
routeState: NavigationState | PartialState<NavigationState> | undefined;
|
||||
@@ -27,7 +31,8 @@ type Props<State extends NavigationState, ScreenOptions extends {}> = {
|
||||
*/
|
||||
export default function SceneView<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {}
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
screen,
|
||||
route,
|
||||
@@ -36,7 +41,7 @@ export default function SceneView<
|
||||
getState,
|
||||
setState,
|
||||
options,
|
||||
}: Props<State, ScreenOptions>) {
|
||||
}: Props<State, ScreenOptions, EventMap>) {
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||
|
||||
|
||||
@@ -3,17 +3,16 @@ import { act, render } from '@testing-library/react-native';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
} from '@react-navigation/routers';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import NavigationStateContext from '../NavigationStateContext';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import Screen from '../Screen';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
it('throws when getState is accessed without a container', () => {
|
||||
expect.assertions(1);
|
||||
@@ -129,7 +128,7 @@ it('handle dispatching with ref', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
@@ -142,10 +141,10 @@ it('handle dispatching with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -164,8 +163,8 @@ it('handle dispatching with ref', () => {
|
||||
<Screen name="foo2">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux1">{() => null}</Screen>
|
||||
<Screen name="lex1">{() => null}</Screen>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -173,8 +172,8 @@ it('handle dispatching with ref', () => {
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux2">{() => null}</Screen>
|
||||
<Screen name="lex2">{() => null}</Screen>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -204,10 +203,10 @@ it('handle dispatching with ref', () => {
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
{ key: 'qux', name: 'qux' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -227,7 +226,7 @@ it('handle resetting state with ref', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
@@ -238,8 +237,8 @@ it('handle resetting state with ref', () => {
|
||||
<Screen name="foo2">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux1">{() => null}</Screen>
|
||||
<Screen name="lex1">{() => null}</Screen>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -247,8 +246,8 @@ it('handle resetting state with ref', () => {
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux2">{() => null}</Screen>
|
||||
<Screen name="lex2">{() => null}</Screen>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -267,10 +266,10 @@ it('handle resetting state with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -294,10 +293,10 @@ it('handle resetting state with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '6',
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routeNames: ['qux', 'lex'],
|
||||
routes: [
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
@@ -317,7 +316,7 @@ it('handles getRootState', () => {
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -379,7 +378,7 @@ it('emits state events when the state changes', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -449,7 +448,7 @@ it('emits state events when new navigator mounts', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const NestedNavigator = () => {
|
||||
const [isRendered, setIsRendered] = React.useState(false);
|
||||
@@ -538,7 +537,7 @@ it('emits option events when options change with tab router', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -612,7 +611,7 @@ it('emits option events when options change with stack router', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -678,27 +677,23 @@ it('emits option events when options change with stack router', () => {
|
||||
it('throws if there is no navigator rendered', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = <BaseNavigationContainer ref={ref} children={null} />;
|
||||
|
||||
render(element);
|
||||
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
ref.current?.dispatch({ type: 'WHATEVER' });
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
act(() => {
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("throws if the ref hasn't finished initializing", () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -708,15 +703,9 @@ it("throws if the ref hasn't finished initializing", () => {
|
||||
|
||||
const TestScreen = () => {
|
||||
React.useEffect(() => {
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
ref.current?.dispatch({ type: 'WHATEVER' });
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
@@ -734,7 +723,7 @@ it("throws if the ref hasn't finished initializing", () => {
|
||||
});
|
||||
|
||||
it('invokes the unhandled action listener with the unhandled action', () => {
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const fn = jest.fn();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
@@ -780,7 +769,7 @@ it('works with state change events in independent nested container', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
@@ -832,85 +821,3 @@ it('works with state change events in independent nested container', () => {
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('warns for duplicate route names nested inside each other', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{descriptors[state.routes[state.index].key].render()}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = () => <></>;
|
||||
|
||||
const spy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
'Found screens with the same name nested inside one another.'
|
||||
);
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="qux">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy.mock.calls[1][0]).toMatch(
|
||||
'Found screens with the same name nested inside one another.'
|
||||
);
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator initialRouteName="bar">
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ it('gets navigate action from state', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -36,7 +35,6 @@ it('gets navigate action from state', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
path: '/foo/bar',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
@@ -53,7 +51,6 @@ it('gets navigate action from state for top-level screen', () => {
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -62,7 +59,6 @@ it('gets navigate action from state for top-level screen', () => {
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -84,7 +80,6 @@ it('gets reset action from state with 1 route with key at root', () => {
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -107,12 +102,7 @@ it('gets reset action from state with 1 route with key at root', () => {
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ key: 'test', name: 'qux', params: { author: 'jane' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -135,7 +125,6 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -150,7 +139,6 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -209,7 +197,6 @@ it('gets reset action from state for top-level screen with 2 screens with config
|
||||
name: 'bar',
|
||||
key: 'test',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -232,7 +219,6 @@ it('gets reset action from state for top-level screen with 2 screens with config
|
||||
name: 'bar',
|
||||
key: 'test',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -250,7 +236,6 @@ it('gets navigate action from state for top-level screen with 2 screens with con
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -266,7 +251,6 @@ it('gets navigate action from state for top-level screen with 2 screens with con
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -283,7 +267,6 @@ it('gets navigate action from state for top-level screen with more than 2 screen
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'baz' },
|
||||
],
|
||||
@@ -300,7 +283,6 @@ it('gets navigate action from state for top-level screen with more than 2 screen
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -321,7 +303,7 @@ it('gets navigate action from state with 2 screens', () => {
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -346,7 +328,7 @@ it('gets navigate action from state with 2 screens', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -371,7 +353,6 @@ it('gets navigate action from state with 2 screens with lower index', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
@@ -395,7 +376,6 @@ it('gets navigate action from state with 2 screens with lower index', () => {
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
path: '/foo/bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -470,7 +450,6 @@ it('gets navigate action from state with config', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -504,7 +483,6 @@ it('gets navigate action from state with config', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
path: '/foo/bar',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
@@ -521,7 +499,6 @@ it('gets navigate action from state for top-level screen with config', () => {
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -542,7 +519,6 @@ it('gets navigate action from state for top-level screen with config', () => {
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -563,7 +539,7 @@ it('gets navigate action from state with 2 screens including initial route and w
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -595,7 +571,6 @@ it('gets navigate action from state with 2 screens including initial route and w
|
||||
params: {
|
||||
screen: 'quz',
|
||||
initial: false,
|
||||
path: '/foo/bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -618,7 +593,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -656,7 +631,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -881,7 +856,6 @@ it('gets navigate action from state with more than 2 screens with lower index',
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
@@ -915,7 +889,6 @@ it('gets navigate action from state with more than 2 screens with lower index',
|
||||
params: {
|
||||
screen: 'qux',
|
||||
initial: false,
|
||||
path: '/foo/bar',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import getFocusedRouteNameFromRoute from '../getFocusedRouteNameFromRoute';
|
||||
import { CHILD_STATE } from '../useRouteCache';
|
||||
|
||||
it('gets undefined if there is no nested state', () => {
|
||||
expect(getFocusedRouteNameFromRoute({ name: 'Home' })).toBe(undefined);
|
||||
@@ -9,7 +8,6 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
routes: [{ name: 'Article' }],
|
||||
},
|
||||
@@ -19,7 +17,6 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
|
||||
@@ -30,7 +27,6 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
@@ -40,7 +36,6 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
type: 'tab',
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
@@ -49,50 +44,6 @@ it('gets focused route name from nested state', () => {
|
||||
).toBe('Article');
|
||||
});
|
||||
|
||||
it('gets focused route name from nested state with symbol', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
routes: [{ name: 'Article' }],
|
||||
},
|
||||
})
|
||||
).toBe('Article');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
index: 1,
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
|
||||
},
|
||||
})
|
||||
).toBe('Chat');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
})
|
||||
).toBe('Chat');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
type: 'tab',
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
})
|
||||
).toBe('Article');
|
||||
});
|
||||
|
||||
it('gets nested screen in params if present', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from '@testing-library/react-native';
|
||||
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import Group from '../Group';
|
||||
import type { NavigationState } from '@react-navigation/routers';
|
||||
import Screen from '../Screen';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import useNavigation from '../useNavigation';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -249,53 +248,6 @@ it('initializes state for nested screens in React.Fragment', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('initializes state for nested screens in Group', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestScreen = (props: any) => {
|
||||
React.useEffect(() => {
|
||||
props.navigation.dispatch({ type: 'UPDATE' });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer onStateChange={onStateChange}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Group>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
<Screen name="baz" component={jest.fn()} />
|
||||
</Group>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element).update(element);
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(1);
|
||||
expect(onStateChange).toBeCalledWith({
|
||||
stale: false,
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar', 'baz'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'baz', name: 'baz' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('initializes state for nested navigator on navigation', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -598,7 +550,7 @@ it('updates route params with setParams applied to parent', () => {
|
||||
let setParams: (params: object) => void = () => undefined;
|
||||
|
||||
const FooScreen = (props: any) => {
|
||||
const parent = props.navigation.getParent();
|
||||
const parent = props.navigation.dangerouslyGetParent();
|
||||
if (parent) {
|
||||
setParams = parent.setParams;
|
||||
}
|
||||
@@ -727,7 +679,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -763,7 +715,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -774,7 +726,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
);
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
screen: 'bar-a',
|
||||
params: { whoa: 'test' },
|
||||
})
|
||||
@@ -784,15 +736,15 @@ it('navigates to nested child in a navigator', () => {
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
act(() => navigation.goBack());
|
||||
act(() => navigation.current?.goBack());
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42,\\"whoa\\":\\"test\\"}]"`
|
||||
@@ -847,7 +799,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -881,7 +833,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -914,7 +866,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -924,7 +876,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||
);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -992,7 +944,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
);
|
||||
|
||||
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1019,7 +971,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
initial: false,
|
||||
@@ -1028,7 +980,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1119,7 +1071,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '11',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1167,7 +1119,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -1198,7 +1150,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1231,7 +1183,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
@@ -1240,7 +1192,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1279,7 +1231,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.navigate('bar', {
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
@@ -1293,7 +1245,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1354,7 +1306,7 @@ it('gives access to internal state', () => {
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
state = navigation.getState();
|
||||
state = navigation.dangerouslyGetState();
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1384,7 +1336,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1398,7 +1350,11 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.getRootState().routeNames).toEqual(['foo', 'bar', 'baz']);
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves order of screens in state with numeric names', () => {
|
||||
@@ -1407,7 +1363,7 @@ it('preserves order of screens in state with numeric names', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1421,7 +1377,11 @@ it('preserves order of screens in state with numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.getRootState().routeNames).toEqual(['4', '7', '1']);
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'4',
|
||||
'7',
|
||||
'1',
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws if navigator doesn't have any screens", () => {
|
||||
@@ -1498,7 +1458,7 @@ it('throws when Screen is not the direct children', () => {
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Bar')"
|
||||
"A navigator can only contain 'Screen' components as its direct children (found 'Bar')"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1523,7 +1483,7 @@ it('throws when undefined component is a direct children', () => {
|
||||
spy.mockRestore();
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'undefined' for the screen 'foo')"
|
||||
"A navigator can only contain 'Screen' components as its direct children (found 'undefined' for the screen 'foo')"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1543,7 +1503,7 @@ it('throws when a tag is a direct children', () => {
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'screen' for the screen 'foo')"
|
||||
"A navigator can only contain 'Screen' components as its direct children (found 'screen' for the screen 'foo')"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1563,7 +1523,7 @@ it('throws when a React Element is not the direct children', () => {
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Hello world')"
|
||||
"A navigator can only contain 'Screen' components as its direct children (found 'Hello world')"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1841,7 +1801,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1864,7 +1824,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentRoute()).toEqual({
|
||||
expect(navigation.current?.getCurrentRoute()).toEqual({
|
||||
key: 'bar-a',
|
||||
name: 'bar-a',
|
||||
});
|
||||
@@ -1879,55 +1839,7 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample2: '2' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="xux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using screenOptions", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1936,17 +1848,17 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: '2' })}
|
||||
screenOptions={() => ({ sample2: 'data' })}
|
||||
>
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
options={{ sample: 'data' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: '3' }}
|
||||
options={{ sample3: 'data' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
@@ -1958,72 +1870,16 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
sample2: '2',
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
sample3: '3',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using Group", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: '2' })}
|
||||
>
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Group screenOptions={{ sample4: '4' }}>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: '3' }}
|
||||
/>
|
||||
</Group>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="xux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
sample2: '2',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
sample3: '3',
|
||||
sample4: '4',
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2036,55 +1892,7 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample2: '2' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="xux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when all screens are rendered with screenOptions", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -2093,17 +1901,17 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: '2' })}
|
||||
screenOptions={() => ({ sample2: 'data' })}
|
||||
>
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
options={{ sample: 'data' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: '3' }}
|
||||
options={{ sample3: 'data' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
@@ -2115,72 +1923,16 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
sample2: '2',
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
sample3: '3',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when all screens are rendered with Group", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: '2' })}
|
||||
>
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Group screenOptions={{ sample4: '4' }}>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: '3' }}
|
||||
/>
|
||||
</Group>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="xux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
sample2: '2',
|
||||
});
|
||||
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
sample3: '3',
|
||||
sample4: '4',
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2193,7 +1945,7 @@ it('does not throw if while getting current options with no options defined', ()
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -2216,11 +1968,11 @@ it('does not throw if while getting current options with no options defined', ()
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual({});
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({});
|
||||
});
|
||||
|
||||
it('does not throw if while getting current options with empty container', () => {
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation} children={null} />
|
||||
@@ -2228,5 +1980,5 @@ it('does not throw if while getting current options with empty container', () =>
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.getCurrentOptions()).toEqual(undefined);
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
|
||||
});
|
||||
|
||||
@@ -600,14 +600,14 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
||||
const root = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
<Screen name="qux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="baz">
|
||||
<Screen name="qux">
|
||||
{() => (
|
||||
<OverrodeNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
|
||||
@@ -44,7 +44,7 @@ it("gets navigation's parent from context", () => {
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
expect(navigation.getParent()).toBeDefined();
|
||||
expect(navigation.dangerouslyGetParent()).toBeDefined();
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -75,7 +75,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
const parent = navigation.getParent();
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
if (parent !== undefined) {
|
||||
@@ -91,7 +91,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="bar">
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="quo" component={Test} />
|
||||
|
||||
@@ -5,16 +5,15 @@ import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
StackRouter,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => {
|
||||
const m = { nanoid: () => String(++m.__key), __key: 0 };
|
||||
@@ -572,7 +571,7 @@ it("prevents removing a screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -707,7 +706,7 @@ it("prevents removing a child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -868,7 +867,7 @@ it("prevents removing a grand child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -1066,7 +1065,7 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -1218,7 +1217,7 @@ it("prevents removing a child screen with 'beforeRemove' event with 'resetRoot'"
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { NavigationState, PartialState } from '@react-navigation/routers';
|
||||
|
||||
export default function checkDuplicateRouteNames(state: NavigationState) {
|
||||
const duplicates: string[][] = [];
|
||||
|
||||
const getRouteNames = (
|
||||
location: string,
|
||||
state: NavigationState | PartialState<NavigationState>
|
||||
) => {
|
||||
state.routes.forEach((route: typeof state.routes[0]) => {
|
||||
const currentLocation = location
|
||||
? `${location} > ${route.name}`
|
||||
: route.name;
|
||||
|
||||
route.state?.routeNames?.forEach((routeName) => {
|
||||
if (routeName === route.name) {
|
||||
duplicates.push([
|
||||
currentLocation,
|
||||
`${currentLocation} > ${route.name}`,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if (route.state) {
|
||||
getRouteNames(currentLocation, route.state);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getRouteNames('', state);
|
||||
|
||||
return duplicates;
|
||||
}
|
||||
36
packages/core/src/checkLegacyPathConfig.tsx
Normal file
36
packages/core/src/checkLegacyPathConfig.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { PathConfigMap } from './types';
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap;
|
||||
};
|
||||
|
||||
export default function checkLegacyPathConfig(
|
||||
config?: Options
|
||||
): [boolean, Options | undefined] {
|
||||
let legacy = false;
|
||||
|
||||
if (config) {
|
||||
// Assume legacy configuration if config has any other keys except `screens` and `initialRouteName`
|
||||
legacy = Object.keys(config).some(
|
||||
(key) => key !== 'screens' && key !== 'initialRouteName'
|
||||
);
|
||||
|
||||
if (
|
||||
legacy &&
|
||||
(config.hasOwnProperty('screens') ||
|
||||
config.hasOwnProperty('initialRouteName'))
|
||||
) {
|
||||
throw new Error(
|
||||
'Found invalid keys in the configuration object. See https://reactnavigation.org/docs/configuring-links/ for more details on the shape of the configuration object.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (legacy) {
|
||||
// @ts-expect-error: we have incorrect type for config since we don't type legacy config
|
||||
return [legacy, { screens: config }];
|
||||
}
|
||||
|
||||
return [legacy, config];
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { CommonActions } from '@react-navigation/routers';
|
||||
import type { NavigationContainerRefWithCurrent } from './types';
|
||||
|
||||
export 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 default function createNavigationContainerRef<
|
||||
ParamList extends {} = ReactNavigation.RootParamList
|
||||
>(): NavigationContainerRefWithCurrent<ParamList> {
|
||||
const methods = [
|
||||
...Object.keys(CommonActions),
|
||||
'addListener',
|
||||
'removeListener',
|
||||
'resetRoot',
|
||||
'dispatch',
|
||||
'canGoBack',
|
||||
'getRootState',
|
||||
'getState',
|
||||
'getParent',
|
||||
'getCurrentRoute',
|
||||
'getCurrentOptions',
|
||||
] as const;
|
||||
|
||||
const ref: NavigationContainerRefWithCurrent<ParamList> = {
|
||||
...methods.reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) => {
|
||||
if (ref.current == null) {
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
// @ts-expect-error: this is ok
|
||||
return ref.current[name](...args);
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
isReady: () => {
|
||||
if (ref.current == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ref.current.isReady();
|
||||
},
|
||||
current: null,
|
||||
};
|
||||
|
||||
return ref;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import type * as React from 'react';
|
||||
import type { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import Group from './Group';
|
||||
import Screen from './Screen';
|
||||
import type { TypedNavigator, EventMapBase } from './types';
|
||||
|
||||
@@ -32,7 +31,6 @@ export default function createNavigatorFactory<
|
||||
|
||||
return {
|
||||
Navigator,
|
||||
Group,
|
||||
Screen,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { InitialState } from '@react-navigation/routers';
|
||||
|
||||
export default function findFocusedRoute(state: InitialState) {
|
||||
let current: InitialState | undefined = state;
|
||||
|
||||
while (current?.routes[current.index ?? 0].state != null) {
|
||||
current = current.routes[current.index ?? 0].state;
|
||||
}
|
||||
|
||||
const route = current?.routes[current?.index ?? 0];
|
||||
|
||||
return route;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Object.fromEntries is not available in older iOS versions
|
||||
export default function fromEntries<K extends string, V>(
|
||||
entries: (readonly [K, V])[]
|
||||
) {
|
||||
return entries.reduce((acc, [k, v]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
}
|
||||
@@ -13,17 +13,13 @@ type ConfigItem = {
|
||||
screens?: Record<string, ConfigItem>;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap<object>;
|
||||
};
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
|
||||
type NavigateAction<State extends NavigationState> = {
|
||||
type: 'NAVIGATE';
|
||||
payload: {
|
||||
name: string;
|
||||
params?: NavigatorScreenParams<State>;
|
||||
path?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -32,9 +28,7 @@ export default function getActionFromState(
|
||||
options?: Options
|
||||
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const normalizedConfig = options
|
||||
? createNormalizedConfigItem(options as PathConfig<object> | string)
|
||||
: {};
|
||||
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
|
||||
|
||||
const routes =
|
||||
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
|
||||
@@ -67,9 +61,7 @@ export default function getActionFromState(
|
||||
NavigationState
|
||||
>;
|
||||
|
||||
let payload = route
|
||||
? { name: route.name, path: route.path, params }
|
||||
: undefined;
|
||||
let payload = route ? { name: route.name, params } : undefined;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 0) {
|
||||
@@ -115,7 +107,6 @@ export default function getActionFromState(
|
||||
NavigationState
|
||||
>;
|
||||
} else {
|
||||
params.path = route.path;
|
||||
params.params = route.params;
|
||||
}
|
||||
|
||||
@@ -135,7 +126,7 @@ export default function getActionFromState(
|
||||
};
|
||||
}
|
||||
|
||||
const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
|
||||
const createNormalizedConfigItem = (config: PathConfig | string) =>
|
||||
typeof config === 'object' && config != null
|
||||
? {
|
||||
initialRouteName: config.initialRouteName,
|
||||
@@ -146,7 +137,7 @@ const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
|
||||
}
|
||||
: {};
|
||||
|
||||
const createNormalizedConfigs = (options: PathConfigMap<object>) =>
|
||||
const createNormalizedConfigs = (options: PathConfigMap) =>
|
||||
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
||||
acc[k] = createNormalizedConfigItem(v);
|
||||
return acc;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import type { Route } from '@react-navigation/routers';
|
||||
import { CHILD_STATE } from './useRouteCache';
|
||||
import type {
|
||||
Route,
|
||||
PartialState,
|
||||
NavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
import { SUPPRESS_STATE_ACCESS_WARNING } from './useRouteCache';
|
||||
|
||||
export default function getFocusedRouteNameFromRoute(
|
||||
route: Partial<Route<string>>
|
||||
route: Partial<Route<string>> & { state?: PartialState<NavigationState> }
|
||||
): string | undefined {
|
||||
// @ts-expect-error: this isn't in type definitions coz we want this private
|
||||
const state = route[CHILD_STATE] ?? route.state;
|
||||
SUPPRESS_STATE_ACCESS_WARNING.value = true;
|
||||
|
||||
const state = route.state;
|
||||
|
||||
SUPPRESS_STATE_ACCESS_WARNING.value = false;
|
||||
|
||||
const params = route.params as { screen?: unknown } | undefined;
|
||||
|
||||
const routeName = state
|
||||
|
||||
@@ -4,13 +4,10 @@ import type {
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import fromEntries from './fromEntries';
|
||||
import checkLegacyPathConfig from './checkLegacyPathConfig';
|
||||
import type { PathConfig, PathConfigMap } from './types';
|
||||
|
||||
type Options<ParamList> = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap<ParamList>;
|
||||
};
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
|
||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||
|
||||
@@ -64,9 +61,9 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
|
||||
* @param options Extra options to fine-tune how to serialize the path.
|
||||
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
||||
*/
|
||||
export default function getPathFromState<ParamList extends {}>(
|
||||
export default function getPathFromState(
|
||||
state: State,
|
||||
options?: Options<ParamList>
|
||||
options?: Options
|
||||
): string {
|
||||
if (state == null) {
|
||||
throw Error(
|
||||
@@ -74,9 +71,11 @@ export default function getPathFromState<ParamList extends {}>(
|
||||
);
|
||||
}
|
||||
|
||||
const [legacy, compatOptions] = checkLegacyPathConfig(options);
|
||||
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const configs: Record<string, ConfigItem> = options?.screens
|
||||
? createNormalizedConfigs(options?.screens)
|
||||
const configs: Record<string, ConfigItem> = compatOptions
|
||||
? createNormalizedConfigs(legacy, compatOptions.screens)
|
||||
: {};
|
||||
|
||||
let path = '/';
|
||||
@@ -178,6 +177,12 @@ export default function getPathFromState<ParamList extends {}>(
|
||||
// Showing the route name seems ok, though whatever we show here will be incorrect
|
||||
// Since the page doesn't actually exist
|
||||
if (p === '*') {
|
||||
if (legacy) {
|
||||
throw new Error(
|
||||
"Please update your config to the new format to use wildcard pattern ('*'). https://reactnavigation.org/docs/configuring-links/#updating-config"
|
||||
);
|
||||
}
|
||||
|
||||
return route.name;
|
||||
}
|
||||
|
||||
@@ -214,7 +219,7 @@ export default function getPathFromState<ParamList extends {}>(
|
||||
}
|
||||
}
|
||||
|
||||
const query = queryString.stringify(focusedParams, { sort: false });
|
||||
const query = queryString.stringify(focusedParams);
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
@@ -231,6 +236,17 @@ export default function getPathFromState<ParamList extends {}>(
|
||||
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]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
|
||||
const getParamName = (pattern: string) =>
|
||||
pattern.replace(/^:/, '').replace(/\?$/, '');
|
||||
|
||||
@@ -241,7 +257,8 @@ const joinPaths = (...paths: string[]): string =>
|
||||
.join('/');
|
||||
|
||||
const createConfigItem = (
|
||||
config: PathConfig<object> | string,
|
||||
legacy: boolean,
|
||||
config: PathConfig | string,
|
||||
parentPattern?: string
|
||||
): ConfigItem => {
|
||||
if (typeof config === 'string') {
|
||||
@@ -255,19 +272,26 @@ const createConfigItem = (
|
||||
// It can have `path` property and `screens` prop which has nested configs
|
||||
let pattern: string | undefined;
|
||||
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
if (legacy) {
|
||||
pattern =
|
||||
config.exact !== true && parentPattern && config.path
|
||||
? joinPaths(parentPattern, config.path)
|
||||
: config.path;
|
||||
} else {
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
|
||||
const screens = config.screens
|
||||
? createNormalizedConfigs(config.screens, pattern)
|
||||
? createNormalizedConfigs(legacy, config.screens, pattern)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
@@ -279,12 +303,13 @@ const createConfigItem = (
|
||||
};
|
||||
|
||||
const createNormalizedConfigs = (
|
||||
options: PathConfigMap<object>,
|
||||
legacy: boolean,
|
||||
options: PathConfigMap,
|
||||
pattern?: string
|
||||
): Record<string, ConfigItem> =>
|
||||
fromEntries(
|
||||
Object.entries(options).map(([name, c]) => {
|
||||
const result = createConfigItem(c, pattern);
|
||||
const result = createConfigItem(legacy, c, pattern);
|
||||
|
||||
return [name, result];
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user