mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-29 22:53:52 +08:00
Compare commits
46 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65b6a3d864 | ||
|
|
211f1f2c0e | ||
|
|
22b7c3f6c1 | ||
|
|
6ebe0824df | ||
|
|
82900cceff | ||
|
|
dc4ffc0171 | ||
|
|
9c30c42c0b | ||
|
|
ea8ea20127 | ||
|
|
2f282f1070 | ||
|
|
5165eb76aa | ||
|
|
7c722d2028 | ||
|
|
7f015130df | ||
|
|
7580efce89 | ||
|
|
1179d56c50 | ||
|
|
a6e498170f | ||
|
|
4af9d10298 | ||
|
|
08e74af545 | ||
|
|
1e05895b24 | ||
|
|
929c3e3a3b | ||
|
|
b5d539a11b | ||
|
|
7da45e1e89 | ||
|
|
47134d7052 | ||
|
|
a369ba3645 | ||
|
|
4294318210 | ||
|
|
8da4c58065 | ||
|
|
7809bc0650 | ||
|
|
8f2b95ca97 | ||
|
|
6866ad2cda | ||
|
|
459fd27050 | ||
|
|
14786594c0 | ||
|
|
b28bfddc17 | ||
|
|
1a6aebefcb | ||
|
|
33b2dbb85c | ||
|
|
4bb0b43f1a | ||
|
|
260da9b103 | ||
|
|
9ac709ea1e | ||
|
|
60fa3b9be9 | ||
|
|
cf6a9e614d | ||
|
|
0ecd112ec9 | ||
|
|
def7c03d7d | ||
|
|
83242a7bef | ||
|
|
f48303f036 | ||
|
|
dc779b8d82 | ||
|
|
d7401b0200 | ||
|
|
372d5921b8 | ||
|
|
f940153d02 |
@@ -3,7 +3,7 @@ version: 2.1
|
|||||||
executors:
|
executors:
|
||||||
default:
|
default:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10
|
- image: circleci/node:14
|
||||||
working_directory: ~/project
|
working_directory: ~/project
|
||||||
environment:
|
environment:
|
||||||
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||||
@@ -36,10 +36,12 @@ jobs:
|
|||||||
command: yarn install --frozen-lockfile
|
command: yarn install --frozen-lockfile
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||||
paths: ~/.cache/yarn
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: .
|
root: .
|
||||||
paths: .
|
paths:
|
||||||
|
- .
|
||||||
|
|
||||||
lint-and-typecheck:
|
lint-and-typecheck:
|
||||||
executor: default
|
executor: default
|
||||||
|
|||||||
2
.github/workflows/expo-preview.yml
vendored
2
.github/workflows/expo-preview.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Setup Expo
|
- name: Setup Expo
|
||||||
uses: expo/expo-github-action@v5
|
uses: expo/expo-github-action@v5
|
||||||
|
|||||||
2
.github/workflows/expo.yml
vendored
2
.github/workflows/expo.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Setup Expo
|
- name: Setup Expo
|
||||||
uses: expo/expo-github-action@v5
|
uses: expo/expo-github-action@v5
|
||||||
|
|||||||
10
.github/workflows/first-pull-request.yml
vendored
10
.github/workflows/first-pull-request.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: First pull request
|
name: First pull request
|
||||||
on: pull_request
|
on: pull_request_target
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
welcome:
|
welcome:
|
||||||
@@ -12,13 +12,13 @@ jobs:
|
|||||||
// Get a list of all issues created by the PR opener
|
// Get a list of all issues created by the PR opener
|
||||||
// See: https://octokit.github.io/rest.js/#pagination
|
// See: https://octokit.github.io/rest.js/#pagination
|
||||||
const creator = context.payload.sender.login;
|
const creator = context.payload.sender.login;
|
||||||
const opts = github.issues.listForRepo.endpoint.merge({
|
const options = github.issues.listForRepo.endpoint.merge({
|
||||||
...context.issue,
|
...context.issue,
|
||||||
creator,
|
creator,
|
||||||
state: 'all'
|
state: 'all'
|
||||||
});
|
});
|
||||||
|
|
||||||
const issues = await github.paginate(opts);
|
const issues = await github.paginate(options);
|
||||||
|
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
if (issue.number === context.issue.number) {
|
if (issue.number === context.issue.number) {
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (issue.pull_request) {
|
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,
|
issue_number: context.issue.number,
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
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)."
|
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).`
|
||||||
});
|
});
|
||||||
|
|||||||
15
.github/workflows/sponsor.yml
vendored
Normal file
15
.github/workflows/sponsor.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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
Normal file
21
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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.'
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,7 @@ build
|
|||||||
|
|
||||||
npm-debug.*
|
npm-debug.*
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
*.log
|
*.log
|
||||||
*.jks
|
*.jks
|
||||||
*.p8
|
*.p8
|
||||||
@@ -33,3 +34,5 @@ npm-debug.*
|
|||||||
*.key
|
*.key
|
||||||
*.mobileprovision
|
*.mobileprovision
|
||||||
*.orig.*
|
*.orig.*
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
|||||||
@@ -4,42 +4,41 @@ beforeEach(async () => {
|
|||||||
await page.click('[data-testid=LinkComponent]');
|
await page.click('[data-testid=LinkComponent]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads the article page', async () => {
|
const getPageInfo = async () => ({
|
||||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
url: await page.evaluate(() => location.pathname + location.search),
|
||||||
'/link-component/article/gandalf'
|
title: await page.evaluate(() => document.title),
|
||||||
);
|
heading: (await page.accessibility.snapshot())?.children?.find(
|
||||||
|
(it) => it.role === 'heading'
|
||||||
|
)?.name,
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
it('loads the article page', async () => {
|
||||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
const { url, title, heading } = await getPageInfo();
|
||||||
(it: any) => it.role === 'heading'
|
|
||||||
)?.name
|
expect(url).toBe('/link-component/article/gandalf');
|
||||||
).toBe('Article by Gandalf');
|
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||||
|
expect(heading).toBe('Article by Gandalf');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes to the album page and goes back', async () => {
|
it('goes to the album page and goes back', async () => {
|
||||||
await page.click('[href="/link-component/music"]');
|
await page.click('[href="/link-component/music"]');
|
||||||
|
|
||||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
{
|
||||||
'/link-component/music'
|
const { url, title, heading } = await getPageInfo();
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(url).toBe('/link-component/music');
|
||||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
expect(title).toBe('Albums - React Navigation Example');
|
||||||
(it: any) => it.role === 'heading'
|
expect(heading).toBe('Albums');
|
||||||
)?.name
|
}
|
||||||
).toBe('Albums');
|
|
||||||
|
|
||||||
await page.click('[aria-label="Article by Gandalf, back"]');
|
await page.click('[aria-label="Article by Gandalf, back"]');
|
||||||
|
|
||||||
await page.waitForNavigation();
|
await page.waitForNavigation();
|
||||||
|
|
||||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
{
|
||||||
'/link-component/article/gandalf'
|
const { url, title, heading } = await getPageInfo();
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(url).toBe('/link-component/article/gandalf');
|
||||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||||
(it: any) => it.role === 'heading'
|
expect(heading).toBe('Article by Gandalf');
|
||||||
)?.name
|
}
|
||||||
).toBe('Article by Gandalf');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^12.0.0",
|
"@expo/vector-icons": "^12.0.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||||
"@react-native-masked-view/masked-view": "0.2.3",
|
"@react-native-masked-view/masked-view": "~0.2.4",
|
||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"expo": "^41.0.0-beta.2",
|
"expo": "^41.0.1",
|
||||||
"expo-asset": "~8.3.1",
|
"expo-asset": "~8.3.1",
|
||||||
"expo-blur": "~9.0.3",
|
"expo-blur": "~9.0.3",
|
||||||
"expo-linking": "~2.2.1",
|
"expo-linking": "~2.2.3",
|
||||||
"expo-updates": "~0.5.3",
|
"expo-updates": "~0.5.4",
|
||||||
"koa": "^2.13.0",
|
"koa": "^2.13.0",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
"@types/cheerio": "^0.22.28",
|
"@types/cheerio": "^0.22.28",
|
||||||
"@types/jest-dev-server": "^4.2.0",
|
"@types/jest-dev-server": "^4.2.0",
|
||||||
"@types/koa": "^2.13.1",
|
"@types/koa": "^2.13.1",
|
||||||
|
"@types/mock-require": "^2.0.0",
|
||||||
"@types/node-fetch": "^2.5.9",
|
"@types/node-fetch": "^2.5.9",
|
||||||
"@types/react": "~16.9.35",
|
"@types/react": "~16.9.35",
|
||||||
"@types/react-dom": "~16.9.8",
|
"@types/react-dom": "~16.9.8",
|
||||||
@@ -51,9 +52,10 @@
|
|||||||
"babel-plugin-module-resolver": "^4.0.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",
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"expo-cli": "^4.3.4",
|
"expo-cli": "^4.4.4",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-dev-server": "^4.4.0",
|
"jest-dev-server": "^4.4.0",
|
||||||
|
"mock-require": "^3.0.3",
|
||||||
"mock-require-assets": "^0.0.1",
|
"mock-require-assets": "^0.0.1",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
"nodemon": "^2.0.6",
|
"nodemon": "^2.0.6",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'mock-require-assets';
|
import 'mock-require-assets';
|
||||||
|
|
||||||
|
import mock from 'mock-require';
|
||||||
import Module from 'module';
|
import Module from 'module';
|
||||||
|
|
||||||
// We need to make sure that .web.xx extensions are resolved before .xx
|
// We need to make sure that .web.xx extensions are resolved before .xx
|
||||||
@@ -10,3 +11,14 @@ Module._extensions = Object.fromEntries(
|
|||||||
return b[0].split('.').length - a[0].split('.').length;
|
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() {} }
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform } from 'react-native';
|
import { ScrollView, StyleSheet } from 'react-native';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
import { BlurView } from 'expo-blur';
|
||||||
import {
|
import {
|
||||||
getFocusedRouteNameFromRoute,
|
getFocusedRouteNameFromRoute,
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
NavigatorScreenParams,
|
NavigatorScreenParams,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import type { StackScreenProps } from '@react-navigation/stack';
|
import type { StackScreenProps } from '@react-navigation/stack';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import {
|
||||||
|
createBottomTabNavigator,
|
||||||
|
useBottomTabBarHeight,
|
||||||
|
} from '@react-navigation/bottom-tabs';
|
||||||
import { HeaderBackButton } from '@react-navigation/elements';
|
import { HeaderBackButton } from '@react-navigation/elements';
|
||||||
import TouchableBounce from '../Shared/TouchableBounce';
|
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
import Contacts from '../Shared/Contacts';
|
import Contacts from '../Shared/Contacts';
|
||||||
import Chat from '../Shared/Chat';
|
import Chat from '../Shared/Chat';
|
||||||
@@ -30,6 +33,16 @@ type BottomTabParams = {
|
|||||||
TabChat: undefined;
|
TabChat: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AlbumsScreen = () => {
|
||||||
|
const tabBarHeight = useBottomTabBarHeight();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={{ paddingBottom: tabBarHeight }}>
|
||||||
|
<Albums scrollEnabled={false} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||||
|
|
||||||
export default function BottomTabsScreen({
|
export default function BottomTabsScreen({
|
||||||
@@ -51,10 +64,6 @@ export default function BottomTabsScreen({
|
|||||||
headerLeft: (props) => (
|
headerLeft: (props) => (
|
||||||
<HeaderBackButton {...props} onPress={navigation.goBack} />
|
<HeaderBackButton {...props} onPress={navigation.goBack} />
|
||||||
),
|
),
|
||||||
tabBarButton:
|
|
||||||
Platform.OS === 'web'
|
|
||||||
? undefined
|
|
||||||
: (props) => <TouchableBounce {...props} />,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
@@ -84,10 +93,18 @@ export default function BottomTabsScreen({
|
|||||||
/>
|
/>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="TabAlbums"
|
name="TabAlbums"
|
||||||
component={Albums}
|
component={AlbumsScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Albums',
|
title: 'Albums',
|
||||||
tabBarIcon: getTabBarIcon('image-album'),
|
tabBarIcon: getTabBarIcon('image-album'),
|
||||||
|
tabBarStyle: { position: 'absolute' },
|
||||||
|
tabBarBackground: () => (
|
||||||
|
<BlurView
|
||||||
|
tint="light"
|
||||||
|
intensity={100}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
/>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BottomTabs.Navigator>
|
</BottomTabs.Navigator>
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
|||||||
const LinkButton = ({
|
const LinkButton = ({
|
||||||
to,
|
to,
|
||||||
...rest
|
...rest
|
||||||
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
}: React.ComponentProps<typeof Button> &
|
||||||
|
Parameters<typeof useLinkProps>[0]) => {
|
||||||
const props = useLinkProps({ to });
|
const props = useLinkProps({ to });
|
||||||
|
|
||||||
return <Button {...props} {...rest} />;
|
return <Button {...props} {...rest} />;
|
||||||
@@ -57,6 +58,13 @@ const ArticleScreen = ({
|
|||||||
>
|
>
|
||||||
Go to /link-component/music
|
Go to /link-component/music
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
<LinkButton
|
||||||
|
to={{ screen: 'Home' }}
|
||||||
|
mode="contained"
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go to /
|
||||||
|
</LinkButton>
|
||||||
<Button
|
<Button
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
|
|||||||
@@ -117,24 +117,29 @@ export default function SimpleStackScreen({
|
|||||||
return (
|
return (
|
||||||
<SimpleStack.Navigator
|
<SimpleStack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
...TransitionPresets.SlideFromRightIOS,
|
|
||||||
headerMode: 'float',
|
|
||||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Group
|
||||||
name="Article"
|
screenOptions={{
|
||||||
component={ArticleScreen}
|
...TransitionPresets.SlideFromRightIOS,
|
||||||
options={({ route }) => ({
|
headerMode: 'float',
|
||||||
title: `Article by ${route.params?.author ?? 'Unknown'}`,
|
}}
|
||||||
})}
|
>
|
||||||
initialParams={{ author: 'Gandalf' }}
|
<SimpleStack.Screen
|
||||||
/>
|
name="Article"
|
||||||
<SimpleStack.Screen
|
component={ArticleScreen}
|
||||||
name="NewsFeed"
|
options={({ route }) => ({
|
||||||
component={NewsFeedScreen}
|
title: `Article by ${route.params?.author ?? 'Unknown'}`,
|
||||||
options={{ title: 'Feed' }}
|
})}
|
||||||
/>
|
initialParams={{ author: 'Gandalf' }}
|
||||||
|
/>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="NewsFeed"
|
||||||
|
component={NewsFeedScreen}
|
||||||
|
options={{ title: 'Feed' }}
|
||||||
|
/>
|
||||||
|
</SimpleStack.Group>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Albums"
|
name="Albums"
|
||||||
component={AlbumsScreen}
|
component={AlbumsScreen}
|
||||||
|
|||||||
136
example/src/Screens/MixedStack.tsx
Normal file
136
example/src/Screens/MixedStack.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -82,7 +82,7 @@ export default function ModalStackScreen({ navigation }: Props) {
|
|||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalStack.Navigator mode="modal">
|
<ModalStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||||
<ModalStack.Screen
|
<ModalStack.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={ArticleScreen}
|
component={ArticleScreen}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
|
|
||||||
type SimpleStackParams = {
|
type TransparentStackParams = {
|
||||||
Article: { author: string };
|
Article: { author: string };
|
||||||
Dialog: undefined;
|
Dialog: undefined;
|
||||||
};
|
};
|
||||||
@@ -18,7 +18,7 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
|||||||
const ArticleScreen = ({
|
const ArticleScreen = ({
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
|
}: StackScreenProps<TransparentStackParams, 'Article'>) => {
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
@@ -45,7 +45,9 @@ const ArticleScreen = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
const DialogScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: StackScreenProps<TransparentStackParams>) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,12 +72,11 @@ const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
const TransparentStack = createStackNavigator<TransparentStackParams>();
|
||||||
|
|
||||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
|
type Props = StackScreenProps<ParamListBase>;
|
||||||
StackScreenProps<ParamListBase>;
|
|
||||||
|
|
||||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
export default function TransparentStackScreen({ navigation }: Props) {
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
@@ -83,13 +84,13 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleStack.Navigator mode="modal" {...rest}>
|
<TransparentStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||||
<SimpleStack.Screen
|
<TransparentStack.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={ArticleScreen}
|
component={ArticleScreen}
|
||||||
initialParams={{ author: 'Gandalf' }}
|
initialParams={{ author: 'Gandalf' }}
|
||||||
/>
|
/>
|
||||||
<SimpleStack.Screen
|
<TransparentStack.Screen
|
||||||
name="Dialog"
|
name="Dialog"
|
||||||
component={DialogScreen}
|
component={DialogScreen}
|
||||||
options={{
|
options={{
|
||||||
@@ -122,7 +123,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SimpleStack.Navigator>
|
</TransparentStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
// @ts-expect-error: there are no type definitions for deep imports
|
|
||||||
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
|
|
||||||
|
|
||||||
export default TouchableBounce;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { TouchableOpacity } from 'react-native';
|
|
||||||
|
|
||||||
export default TouchableOpacity;
|
|
||||||
@@ -28,13 +28,14 @@ import {
|
|||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
PathConfigMap,
|
PathConfigMap,
|
||||||
NavigationContainerRef,
|
useNavigationContainerRef,
|
||||||
|
NavigatorScreenParams,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackScreenProps,
|
|
||||||
HeaderStyleInterpolators,
|
HeaderStyleInterpolators,
|
||||||
|
StackNavigationProp,
|
||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ import { restartApp } from './Restart';
|
|||||||
import SettingsItem from './Shared/SettingsItem';
|
import SettingsItem from './Shared/SettingsItem';
|
||||||
import SimpleStack from './Screens/SimpleStack';
|
import SimpleStack from './Screens/SimpleStack';
|
||||||
import ModalStack from './Screens/ModalStack';
|
import ModalStack from './Screens/ModalStack';
|
||||||
|
import MixedStack from './Screens/MixedStack';
|
||||||
import MixedHeaderMode from './Screens/MixedHeaderMode';
|
import MixedHeaderMode from './Screens/MixedHeaderMode';
|
||||||
import StackTransparent from './Screens/StackTransparent';
|
import StackTransparent from './Screens/StackTransparent';
|
||||||
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||||
@@ -61,6 +63,13 @@ if (Platform.OS !== 'web') {
|
|||||||
|
|
||||||
enableScreens();
|
enableScreens();
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace ReactNavigation {
|
||||||
|
interface RootParamList extends RootStackParamList {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type RootDrawerParamList = {
|
type RootDrawerParamList = {
|
||||||
Examples: undefined;
|
Examples: undefined;
|
||||||
};
|
};
|
||||||
@@ -71,6 +80,10 @@ const SCREENS = {
|
|||||||
title: 'Modal Stack',
|
title: 'Modal Stack',
|
||||||
component: ModalStack,
|
component: ModalStack,
|
||||||
},
|
},
|
||||||
|
MixedStack: {
|
||||||
|
title: 'Regular + Modal Stack',
|
||||||
|
component: MixedStack,
|
||||||
|
},
|
||||||
MixedHeaderMode: {
|
MixedHeaderMode: {
|
||||||
title: 'Float + Screen Header Stack',
|
title: 'Float + Screen Header Stack',
|
||||||
component: MixedHeaderMode,
|
component: MixedHeaderMode,
|
||||||
@@ -115,7 +128,7 @@ const SCREENS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type RootStackParamList = {
|
type RootStackParamList = {
|
||||||
Home: undefined;
|
Home: NavigatorScreenParams<RootDrawerParamList>;
|
||||||
NotFound: undefined;
|
NotFound: undefined;
|
||||||
} & {
|
} & {
|
||||||
[P in keyof typeof SCREENS]: undefined;
|
[P in keyof typeof SCREENS]: undefined;
|
||||||
@@ -193,7 +206,7 @@ export default function App() {
|
|||||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const navigationRef = React.useRef<NavigationContainerRef>(null);
|
const navigationRef = useNavigationContainerRef();
|
||||||
|
|
||||||
useReduxDevToolsExtension(navigationRef);
|
useReduxDevToolsExtension(navigationRef);
|
||||||
|
|
||||||
@@ -228,7 +241,9 @@ export default function App() {
|
|||||||
prefixes: [createURL('/')],
|
prefixes: [createURL('/')],
|
||||||
config: {
|
config: {
|
||||||
initialRouteName: 'Home',
|
initialRouteName: 'Home',
|
||||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
screens: Object.keys(SCREENS).reduce<
|
||||||
|
PathConfigMap<RootStackParamList>
|
||||||
|
>(
|
||||||
(acc, name) => {
|
(acc, name) => {
|
||||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||||
const path = name
|
const path = name
|
||||||
@@ -236,13 +251,15 @@ export default function App() {
|
|||||||
.replace(/^-/, '')
|
.replace(/^-/, '')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
|
// @ts-expect-error: these types aren't accurate
|
||||||
|
// But we aren't too concerned for now
|
||||||
acc[name] = {
|
acc[name] = {
|
||||||
path,
|
path,
|
||||||
screens: {
|
screens: {
|
||||||
Article: {
|
Article: {
|
||||||
path: 'article/:author?',
|
path: 'article/:author?',
|
||||||
parse: {
|
parse: {
|
||||||
author: (author) =>
|
author: (author: string) =>
|
||||||
author.charAt(0).toUpperCase() +
|
author.charAt(0).toUpperCase() +
|
||||||
author.slice(1).replace(/-/g, ' '),
|
author.slice(1).replace(/-/g, ' '),
|
||||||
},
|
},
|
||||||
@@ -304,7 +321,11 @@ export default function App() {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
{({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: StackNavigationProp<RootStackParamList>;
|
||||||
|
}) => (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ backgroundColor: theme.colors.background }}
|
style={{ backgroundColor: theme.colors.background }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,74 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [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)
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/bottom-tabs",
|
"name": "@react-navigation/bottom-tabs",
|
||||||
"description": "Bottom tab navigator following iOS design guidelines",
|
"description": "Bottom tab navigator following iOS design guidelines",
|
||||||
"version": "6.0.0-next.5",
|
"version": "6.0.0-next.11",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -36,16 +36,16 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/elements": "^1.0.0-next.4",
|
"@react-navigation/elements": "^1.0.0-next.10",
|
||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"warn-once": "^0.0.1"
|
"warn-once": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-native": "~0.63.4",
|
"react-native": "~0.63.4",
|
||||||
|
|||||||
@@ -70,7 +70,12 @@ function BottomTabNavigator({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const {
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
navigation,
|
||||||
|
NavigationContent,
|
||||||
|
} = useNavigationBuilder<
|
||||||
TabNavigationState<ParamListBase>,
|
TabNavigationState<ParamListBase>,
|
||||||
TabRouterOptions,
|
TabRouterOptions,
|
||||||
TabActionHelpers<ParamListBase>,
|
TabActionHelpers<ParamListBase>,
|
||||||
@@ -85,13 +90,15 @@ function BottomTabNavigator({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomTabView
|
<NavigationContent>
|
||||||
{...rest}
|
<BottomTabView
|
||||||
state={state}
|
{...rest}
|
||||||
navigation={navigation}
|
state={state}
|
||||||
descriptors={descriptors}
|
navigation={navigation}
|
||||||
sceneContainerStyle={sceneContainerStyle}
|
descriptors={descriptors}
|
||||||
/>
|
sceneContainerStyle={sceneContainerStyle}
|
||||||
|
/>
|
||||||
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export type BottomTabNavigationHelpers = NavigationHelpers<
|
|||||||
|
|
||||||
export type BottomTabNavigationProp<
|
export type BottomTabNavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = NavigationProp<
|
> = NavigationProp<
|
||||||
ParamList,
|
ParamList,
|
||||||
RouteName,
|
RouteName,
|
||||||
@@ -54,7 +54,7 @@ export type BottomTabNavigationProp<
|
|||||||
|
|
||||||
export type BottomTabScreenProps<
|
export type BottomTabScreenProps<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = {
|
> = {
|
||||||
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
route: RouteProp<ParamList, RouteName>;
|
||||||
@@ -229,6 +229,15 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
|||||||
*/
|
*/
|
||||||
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
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.
|
* Whether this screen should be unmounted when navigating away from it.
|
||||||
* Defaults to `false`.
|
* Defaults to `false`.
|
||||||
@@ -239,7 +248,7 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
|||||||
export type BottomTabDescriptor = Descriptor<
|
export type BottomTabDescriptor = Descriptor<
|
||||||
BottomTabNavigationOptions,
|
BottomTabNavigationOptions,
|
||||||
BottomTabNavigationProp<ParamListBase>,
|
BottomTabNavigationProp<ParamListBase>,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
|
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
|
||||||
@@ -283,7 +292,7 @@ export type BottomTabHeaderProps = {
|
|||||||
/**
|
/**
|
||||||
* Route object for the current screen.
|
* Route object for the current screen.
|
||||||
*/
|
*/
|
||||||
route: RouteProp<ParamListBase, string>;
|
route: RouteProp<ParamListBase>;
|
||||||
/**
|
/**
|
||||||
* Navigation prop for the header.
|
* Navigation prop for the header.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ export default function BottomTabBar({
|
|||||||
tabBarHideOnKeyboard = false,
|
tabBarHideOnKeyboard = false,
|
||||||
tabBarVisibilityAnimationConfig,
|
tabBarVisibilityAnimationConfig,
|
||||||
tabBarStyle,
|
tabBarStyle,
|
||||||
|
tabBarBackground,
|
||||||
} = focusedOptions;
|
} = focusedOptions;
|
||||||
|
|
||||||
const dimensions = useSafeAreaFrame();
|
const dimensions = useSafeAreaFrame();
|
||||||
@@ -211,15 +212,7 @@ export default function BottomTabBar({
|
|||||||
const handleLayout = (e: LayoutChangeEvent) => {
|
const handleLayout = (e: LayoutChangeEvent) => {
|
||||||
const { height, width } = e.nativeEvent.layout;
|
const { height, width } = e.nativeEvent.layout;
|
||||||
|
|
||||||
const topBorderWidth =
|
onHeightChange?.(height);
|
||||||
// @ts-ignore
|
|
||||||
StyleSheet.flatten([styles.tabBar, tabBarStyle])?.borderTopWidth;
|
|
||||||
|
|
||||||
onHeightChange?.(
|
|
||||||
height +
|
|
||||||
paddingBottom +
|
|
||||||
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
setLayout((layout) => {
|
setLayout((layout) => {
|
||||||
if (height === layout.height && width === layout.width) {
|
if (height === layout.height && width === layout.width) {
|
||||||
@@ -252,12 +245,15 @@ export default function BottomTabBar({
|
|||||||
layout,
|
layout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tabBarBackgroundElement = tabBarBackground?.();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.tabBar,
|
styles.tabBar,
|
||||||
{
|
{
|
||||||
backgroundColor: colors.card,
|
backgroundColor:
|
||||||
|
tabBarBackgroundElement != null ? 'transparent' : colors.card,
|
||||||
borderTopColor: colors.border,
|
borderTopColor: colors.border,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -286,6 +282,9 @@ export default function BottomTabBar({
|
|||||||
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||||
onLayout={handleLayout}
|
onLayout={handleLayout}
|
||||||
>
|
>
|
||||||
|
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
||||||
|
{tabBarBackgroundElement}
|
||||||
|
</View>
|
||||||
<View accessibilityRole="tablist" style={styles.content}>
|
<View accessibilityRole="tablist" style={styles.content}>
|
||||||
{routes.map((route, index) => {
|
{routes.map((route, index) => {
|
||||||
const focused = index === state.index;
|
const focused = index === state.index;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
import { ScreenContainer } from 'react-native-screens';
|
|
||||||
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
||||||
import {
|
import type {
|
||||||
NavigationHelpersContext,
|
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
@@ -13,8 +11,7 @@ import {
|
|||||||
SafeAreaProviderCompat,
|
SafeAreaProviderCompat,
|
||||||
getHeaderTitle,
|
getHeaderTitle,
|
||||||
} from '@react-navigation/elements';
|
} from '@react-navigation/elements';
|
||||||
|
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
|
||||||
import ScreenFallback from './ScreenFallback';
|
|
||||||
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||||
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||||
@@ -40,7 +37,9 @@ export default function BottomTabView(props: Props) {
|
|||||||
navigation,
|
navigation,
|
||||||
descriptors,
|
descriptors,
|
||||||
safeAreaInsets,
|
safeAreaInsets,
|
||||||
detachInactiveScreens = true,
|
detachInactiveScreens = Platform.OS === 'web' ||
|
||||||
|
Platform.OS === 'android' ||
|
||||||
|
Platform.OS === 'ios',
|
||||||
sceneContainerStyle,
|
sceneContainerStyle,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -89,73 +88,70 @@ export default function BottomTabView(props: Props) {
|
|||||||
const { routes } = state;
|
const { routes } = state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<SafeAreaProviderCompat>
|
||||||
<SafeAreaProviderCompat>
|
<MaybeScreenContainer
|
||||||
<ScreenContainer
|
enabled={detachInactiveScreens}
|
||||||
// @ts-ignore
|
style={styles.container}
|
||||||
enabled={detachInactiveScreens}
|
>
|
||||||
style={styles.container}
|
{routes.map((route, index) => {
|
||||||
>
|
const descriptor = descriptors[route.key];
|
||||||
{routes.map((route, index) => {
|
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||||
const descriptor = descriptors[route.key];
|
const isFocused = state.index === index;
|
||||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
|
||||||
const isFocused = state.index === index;
|
|
||||||
|
|
||||||
if (unmountOnBlur && !isFocused) {
|
if (unmountOnBlur && !isFocused) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||||
// Don't render a lazy screen if we've never navigated to it
|
// Don't render a lazy screen if we've never navigated to it
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
header = ({ layout, options }: BottomTabHeaderProps) => (
|
header = ({ layout, options }: BottomTabHeaderProps) => (
|
||||||
<Header
|
<Header
|
||||||
{...options}
|
{...options}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
title={getHeaderTitle(options, route.name)}
|
title={getHeaderTitle(options, route.name)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
} = descriptor.options;
|
} = descriptor.options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenFallback
|
<MaybeScreen
|
||||||
key={route.key}
|
key={route.key}
|
||||||
style={StyleSheet.absoluteFill}
|
style={StyleSheet.absoluteFill}
|
||||||
visible={isFocused}
|
visible={isFocused}
|
||||||
enabled={detachInactiveScreens}
|
enabled={detachInactiveScreens}
|
||||||
>
|
>
|
||||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||||
<Screen
|
<Screen
|
||||||
focused={isFocused}
|
focused={isFocused}
|
||||||
route={descriptor.route}
|
route={descriptor.route}
|
||||||
navigation={descriptor.navigation}
|
navigation={descriptor.navigation}
|
||||||
headerShown={descriptor.options.headerShown}
|
headerShown={descriptor.options.headerShown}
|
||||||
headerStatusBarHeight={
|
headerStatusBarHeight={
|
||||||
descriptor.options.headerStatusBarHeight
|
descriptor.options.headerStatusBarHeight
|
||||||
}
|
}
|
||||||
header={header({
|
header={header({
|
||||||
layout: dimensions,
|
layout: dimensions,
|
||||||
route: descriptor.route,
|
route: descriptor.route,
|
||||||
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
||||||
options: descriptor.options,
|
options: descriptor.options,
|
||||||
})}
|
})}
|
||||||
style={sceneContainerStyle}
|
style={sceneContainerStyle}
|
||||||
>
|
>
|
||||||
{descriptor.render()}
|
{descriptor.render()}
|
||||||
</Screen>
|
</Screen>
|
||||||
</BottomTabBarHeightContext.Provider>
|
</BottomTabBarHeightContext.Provider>
|
||||||
</ScreenFallback>
|
</MaybeScreen>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ScreenContainer>
|
</MaybeScreenContainer>
|
||||||
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
||||||
{renderTabBar()}
|
{renderTabBar()}
|
||||||
</BottomTabBarHeightCallbackContext.Provider>
|
</BottomTabBarHeightCallbackContext.Provider>
|
||||||
</SafeAreaProviderCompat>
|
</SafeAreaProviderCompat>
|
||||||
</NavigationHelpersContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform, StyleProp, ViewStyle } from 'react-native';
|
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
||||||
import {
|
|
||||||
Screen,
|
|
||||||
screensEnabled,
|
|
||||||
// @ts-ignore
|
|
||||||
shouldUseActivityState,
|
|
||||||
} from 'react-native-screens';
|
|
||||||
import { ResourceSavingView } from '@react-navigation/elements';
|
import { ResourceSavingView } from '@react-navigation/elements';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -15,22 +9,35 @@ type Props = {
|
|||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ScreenFallback({ visible, children, ...rest }: Props) {
|
let Screens: typeof import('react-native-screens') | undefined;
|
||||||
// react-native-screens is buggy on web
|
|
||||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
try {
|
||||||
if (shouldUseActivityState) {
|
Screens = require('react-native-screens');
|
||||||
return (
|
} catch (e) {
|
||||||
<Screen activityState={visible ? 2 : 0} {...rest}>
|
// Ignore
|
||||||
{children}
|
}
|
||||||
</Screen>
|
|
||||||
);
|
export const MaybeScreenContainer = ({
|
||||||
} else {
|
enabled,
|
||||||
return (
|
...rest
|
||||||
<Screen active={visible ? 1 : 0} {...rest}>
|
}: ViewProps & {
|
||||||
{children}
|
enabled: boolean;
|
||||||
</Screen>
|
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 (
|
return (
|
||||||
|
|||||||
@@ -3,6 +3,74 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix type error when passing unannotated navigation ref ([dc4ffc0](https://github.com/react-navigation/react-navigation/commit/dc4ffc0171b4535fe1b6e839b9d54350121bcf55))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [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)
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/core",
|
"name": "@react-navigation/core",
|
||||||
"description": "Core utilities for building navigators",
|
"description": "Core utilities for building navigators",
|
||||||
"version": "6.0.0-next.2",
|
"version": "6.0.0-next.8",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
InitialState,
|
InitialState,
|
||||||
PartialState,
|
PartialState,
|
||||||
NavigationAction,
|
NavigationAction,
|
||||||
|
ParamListBase,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import UnhandledActionContext from './UnhandledActionContext';
|
import UnhandledActionContext from './UnhandledActionContext';
|
||||||
@@ -22,6 +23,7 @@ import useSyncState from './useSyncState';
|
|||||||
import checkSerializable from './checkSerializable';
|
import checkSerializable from './checkSerializable';
|
||||||
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
||||||
import findFocusedRoute from './findFocusedRoute';
|
import findFocusedRoute from './findFocusedRoute';
|
||||||
|
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
||||||
import type {
|
import type {
|
||||||
NavigationContainerEventMap,
|
NavigationContainerEventMap,
|
||||||
NavigationContainerRef,
|
NavigationContainerRef,
|
||||||
@@ -30,9 +32,6 @@ import type {
|
|||||||
|
|
||||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
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 serializableWarnings: string[] = [];
|
||||||
const duplicateNameWarnings: string[] = [];
|
const duplicateNameWarnings: string[] = [];
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
independent,
|
independent,
|
||||||
children,
|
children,
|
||||||
}: NavigationContainerProps,
|
}: NavigationContainerProps,
|
||||||
ref?: React.Ref<NavigationContainerRef>
|
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
|
||||||
) {
|
) {
|
||||||
const parent = React.useContext(NavigationStateContext);
|
const parent = React.useContext(NavigationStateContext);
|
||||||
|
|
||||||
@@ -202,16 +201,10 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
...(Object.keys(
|
...Object.keys(CommonActions).reduce<any>((acc, name) => {
|
||||||
CommonActions
|
|
||||||
) as (keyof typeof CommonActions)[]).reduce<any>((acc, name) => {
|
|
||||||
acc[name] = (...args: any[]) =>
|
acc[name] = (...args: any[]) =>
|
||||||
dispatch(
|
// @ts-expect-error: this is ok
|
||||||
CommonActions[name](
|
dispatch(CommonActions[name](...args));
|
||||||
// @ts-expect-error: we can't know the type statically
|
|
||||||
...args
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
...emitter.create('root'),
|
...emitter.create('root'),
|
||||||
@@ -223,6 +216,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
getParent: () => undefined,
|
getParent: () => undefined,
|
||||||
getCurrentRoute,
|
getCurrentRoute,
|
||||||
getCurrentOptions,
|
getCurrentOptions,
|
||||||
|
isReady: () => listeners.focus[0] != null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onDispatchAction = React.useCallback(
|
const onDispatchAction = React.useCallback(
|
||||||
|
|||||||
13
packages/core/src/Group.tsx
Normal file
13
packages/core/src/Group.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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.
|
* Context which holds the navigation prop for a screen.
|
||||||
*/
|
*/
|
||||||
const NavigationContext = React.createContext<
|
const NavigationContext = React.createContext<
|
||||||
NavigationProp<ParamListBase, string, any, any> | undefined
|
NavigationProp<ParamListBase> | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
export default NavigationContext;
|
export default NavigationContext;
|
||||||
|
|||||||
@@ -9,14 +9,10 @@ import NavigationStateContext from './NavigationStateContext';
|
|||||||
import StaticContainer from './StaticContainer';
|
import StaticContainer from './StaticContainer';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import useOptionsGetters from './useOptionsGetters';
|
import useOptionsGetters from './useOptionsGetters';
|
||||||
import type { NavigationProp, RouteConfig, EventMapBase } from './types';
|
import type { NavigationProp, RouteConfigComponent } from './types';
|
||||||
|
|
||||||
type Props<
|
type Props<State extends NavigationState, ScreenOptions extends {}> = {
|
||||||
State extends NavigationState,
|
screen: RouteConfigComponent<ParamListBase, string> & { name: string };
|
||||||
ScreenOptions extends {},
|
|
||||||
EventMap extends EventMapBase
|
|
||||||
> = {
|
|
||||||
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
|
|
||||||
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||||
route: Route<string>;
|
route: Route<string>;
|
||||||
routeState: NavigationState | PartialState<NavigationState> | undefined;
|
routeState: NavigationState | PartialState<NavigationState> | undefined;
|
||||||
@@ -31,8 +27,7 @@ type Props<
|
|||||||
*/
|
*/
|
||||||
export default function SceneView<
|
export default function SceneView<
|
||||||
State extends NavigationState,
|
State extends NavigationState,
|
||||||
ScreenOptions extends {},
|
ScreenOptions extends {}
|
||||||
EventMap extends EventMapBase
|
|
||||||
>({
|
>({
|
||||||
screen,
|
screen,
|
||||||
route,
|
route,
|
||||||
@@ -41,7 +36,7 @@ export default function SceneView<
|
|||||||
getState,
|
getState,
|
||||||
setState,
|
setState,
|
||||||
options,
|
options,
|
||||||
}: Props<State, ScreenOptions, EventMap>) {
|
}: Props<State, ScreenOptions>) {
|
||||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ import { act, render } from '@testing-library/react-native';
|
|||||||
import {
|
import {
|
||||||
DefaultRouterOptions,
|
DefaultRouterOptions,
|
||||||
NavigationState,
|
NavigationState,
|
||||||
|
ParamListBase,
|
||||||
Router,
|
Router,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
TabRouter,
|
TabRouter,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||||
import NavigationStateContext from '../NavigationStateContext';
|
import NavigationStateContext from '../NavigationStateContext';
|
||||||
|
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||||
import useNavigationBuilder from '../useNavigationBuilder';
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
import Screen from '../Screen';
|
import Screen from '../Screen';
|
||||||
import type { NavigationContainerRef } from '../types';
|
|
||||||
|
|
||||||
it('throws when getState is accessed without a container', () => {
|
it('throws when getState is accessed without a container', () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
@@ -128,7 +129,7 @@ it('handle dispatching with ref', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
@@ -226,7 +227,7 @@ it('handle resetting state with ref', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
@@ -316,7 +317,7 @@ it('handles getRootState', () => {
|
|||||||
return descriptors[state.routes[state.index].key].render();
|
return descriptors[state.routes[state.index].key].render();
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref}>
|
<BaseNavigationContainer ref={ref}>
|
||||||
@@ -378,7 +379,7 @@ it('emits state events when the state changes', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref}>
|
<BaseNavigationContainer ref={ref}>
|
||||||
@@ -448,7 +449,7 @@ it('emits state events when new navigator mounts', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const NestedNavigator = () => {
|
const NestedNavigator = () => {
|
||||||
const [isRendered, setIsRendered] = React.useState(false);
|
const [isRendered, setIsRendered] = React.useState(false);
|
||||||
@@ -537,7 +538,7 @@ it('emits option events when options change with tab router', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref}>
|
<BaseNavigationContainer ref={ref}>
|
||||||
@@ -611,7 +612,7 @@ it('emits option events when options change with stack router', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref}>
|
<BaseNavigationContainer ref={ref}>
|
||||||
@@ -677,7 +678,7 @@ it('emits option events when options change with stack router', () => {
|
|||||||
it('throws if there is no navigator rendered', () => {
|
it('throws if there is no navigator rendered', () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = <BaseNavigationContainer ref={ref} children={null} />;
|
const element = <BaseNavigationContainer ref={ref} children={null} />;
|
||||||
|
|
||||||
@@ -697,7 +698,7 @@ it('throws if there is no navigator rendered', () => {
|
|||||||
it("throws if the ref hasn't finished initializing", () => {
|
it("throws if the ref hasn't finished initializing", () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
@@ -733,7 +734,7 @@ it("throws if the ref hasn't finished initializing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('invokes the unhandled action listener with the unhandled action', () => {
|
it('invokes the unhandled action listener with the unhandled action', () => {
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
const fn = jest.fn();
|
const fn = jest.fn();
|
||||||
|
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
@@ -779,7 +780,7 @@ it('works with state change events in independent nested container', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ it('converts state to path string', () => {
|
|||||||
|
|
||||||
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
|
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
|
||||||
|
|
||||||
expect(getPathFromState(state)).toBe(path);
|
expect(getPathFromState<object>(state)).toBe(path);
|
||||||
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
|
expect(
|
||||||
|
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||||
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts state to path string with config', () => {
|
it('converts state to path string with config', () => {
|
||||||
@@ -97,9 +99,12 @@ it('converts state to path string with config', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,8 +121,10 @@ it('handles route without param', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state)).toBe(path);
|
expect(getPathFromState<object>(state)).toBe(path);
|
||||||
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
|
expect(
|
||||||
|
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||||
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't add query param for empty params", () => {
|
it("doesn't add query param for empty params", () => {
|
||||||
@@ -131,8 +138,10 @@ it("doesn't add query param for empty params", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state)).toBe(path);
|
expect(getPathFromState<object>(state)).toBe(path);
|
||||||
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
|
expect(
|
||||||
|
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||||
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles state with config with nested screens', () => {
|
it('handles state with config with nested screens', () => {
|
||||||
@@ -208,9 +217,12 @@ it('handles state with config with nested screens', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -287,9 +299,12 @@ it('handles state with config with nested screens and exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -352,9 +367,12 @@ it('handles state with config with nested screens and unused configs', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -418,9 +436,12 @@ it('handles state with config with nested screens and unused configs with exact'
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -498,9 +519,12 @@ it('handles nested object with stringify in it', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -580,9 +604,12 @@ it('handles nested object with stringify in it with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -623,9 +650,12 @@ it('handles nested object for second route depth', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -669,9 +699,12 @@ it('handles nested object for second route depth with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -719,9 +752,12 @@ it('handles nested object for second route depth and path and stringify in roots
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -774,9 +810,12 @@ it('handles nested object for second route depth and path and stringify in roots
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -805,9 +844,12 @@ it('ignores empty string paths', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -850,9 +892,12 @@ it('keeps query params if path is empty', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toEqual(path);
|
).toEqual(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -894,9 +939,12 @@ it('cuts nested configs too', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -939,9 +987,12 @@ it('cuts nested configs too with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -977,9 +1028,12 @@ it('handles empty path at the end', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1012,9 +1066,12 @@ it('returns "/" for empty path', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1042,9 +1099,12 @@ it('parses no path specified', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1121,9 +1181,12 @@ it('strips undefined query params', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1201,9 +1264,12 @@ it('strips undefined query params with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1278,9 +1344,12 @@ it('handles stripping all query params', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1357,9 +1426,12 @@ it('handles stripping all query params with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1380,9 +1452,12 @@ it('replaces undefined query params', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1405,9 +1480,12 @@ it('matches wildcard patterns at root', () => {
|
|||||||
routes: [{ name: '404' }],
|
routes: [{ name: '404' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe('/404');
|
expect(getPathFromState<object>(state, config)).toBe('/404');
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe('/404');
|
).toBe('/404');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1447,9 +1525,12 @@ it('matches wildcard patterns at nested level', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe('/bar/42/404');
|
expect(getPathFromState<object>(state, config)).toBe('/bar/42/404');
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe('/bar/42/404');
|
).toBe('/bar/42/404');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1492,9 +1573,12 @@ it('matches wildcard patterns at nested level with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe('/404');
|
expect(getPathFromState<object>(state, config)).toBe('/404');
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe('/404');
|
).toBe('/404');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1535,9 +1619,12 @@ it('tries to match wildcard patterns at the end', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState<object>(state, config)).toBe(path);
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe(path);
|
).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1570,8 +1657,11 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe('/404');
|
expect(getPathFromState<object>(state, config)).toBe('/404');
|
||||||
expect(
|
expect(
|
||||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
getPathFromState<object>(
|
||||||
|
getStateFromPath<object>(path, config) as State,
|
||||||
|
config
|
||||||
|
)
|
||||||
).toBe('/404');
|
).toBe('/404');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const changePath = <T extends InitialState>(state: T, path: string): T =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns undefined for invalid path', () => {
|
it('returns undefined for invalid path', () => {
|
||||||
expect(getStateFromPath('//')).toBe(undefined);
|
expect(getStateFromPath<object>('//')).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts path string to initial state', () => {
|
it('converts path string to initial state', () => {
|
||||||
@@ -41,8 +41,8 @@ it('converts path string to initial state', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path)).toEqual(state);
|
expect(getStateFromPath<object>(path)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state))).toEqual(
|
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
|
||||||
changePath(state, '/foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
|
changePath(state, '/foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -106,16 +106,16 @@ it('converts path string to initial state with config', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles leading slash when converting', () => {
|
it('handles leading slash when converting', () => {
|
||||||
const path = '/foo/bar/?count=42';
|
const path = '/foo/bar/?count=42';
|
||||||
|
|
||||||
expect(getStateFromPath(path)).toEqual({
|
expect(getStateFromPath<object>(path)).toEqual({
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -136,7 +136,7 @@ it('handles leading slash when converting', () => {
|
|||||||
it('handles ending slash when converting', () => {
|
it('handles ending slash when converting', () => {
|
||||||
const path = 'foo/bar/?count=42';
|
const path = 'foo/bar/?count=42';
|
||||||
|
|
||||||
expect(getStateFromPath(path)).toEqual({
|
expect(getStateFromPath<object>(path)).toEqual({
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -167,8 +167,8 @@ it('handles route without param', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path)).toEqual(state);
|
expect(getStateFromPath<object>(path)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state))).toEqual(
|
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
|
||||||
changePath(state, '/foo/bar')
|
changePath(state, '/foo/bar')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -245,10 +245,10 @@ it('converts path string to initial state with config with nested screens', () =
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
|
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
|
||||||
@@ -308,10 +308,10 @@ it('converts path string to initial state with config with nested screens and un
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles nested object with unused configs and with parse in it', () => {
|
it('handles nested object with unused configs and with parse in it', () => {
|
||||||
@@ -400,10 +400,10 @@ it('handles nested object with unused configs and with parse in it', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles parse in nested object for second route depth', () => {
|
it('handles parse in nested object for second route depth', () => {
|
||||||
@@ -450,10 +450,10 @@ it('handles parse in nested object for second route depth', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
|
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
|
||||||
@@ -501,10 +501,10 @@ it('handles parse in nested object for second route depth and and path and parse
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles initialRouteName at top level', () => {
|
it('handles initialRouteName at top level', () => {
|
||||||
@@ -545,10 +545,10 @@ it('handles initialRouteName at top level', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles initialRouteName inside a screen', () => {
|
it('handles initialRouteName inside a screen', () => {
|
||||||
@@ -591,10 +591,10 @@ it('handles initialRouteName inside a screen', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles initialRouteName included in path', () => {
|
it('handles initialRouteName included in path', () => {
|
||||||
@@ -633,10 +633,10 @@ it('handles initialRouteName included in path', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles two initialRouteNames', () => {
|
it('handles two initialRouteNames', () => {
|
||||||
@@ -728,10 +728,10 @@ it('handles two initialRouteNames', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts initialRouteName without config for it', () => {
|
it('accepts initialRouteName without config for it', () => {
|
||||||
@@ -823,10 +823,10 @@ it('accepts initialRouteName without config for it', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns undefined if path is empty and no matching screen is present', () => {
|
it('returns undefined if path is empty and no matching screen is present', () => {
|
||||||
@@ -847,7 +847,7 @@ it('returns undefined if path is empty and no matching screen is present', () =>
|
|||||||
|
|
||||||
const path = '';
|
const path = '';
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns matching screen if path is empty', () => {
|
it('returns matching screen if path is empty', () => {
|
||||||
@@ -886,10 +886,10 @@ it('returns matching screen if path is empty', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns matching screen with params if path is empty', () => {
|
it('returns matching screen with params if path is empty', () => {
|
||||||
@@ -931,10 +931,10 @@ it('returns matching screen with params if path is empty', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/?foo=42')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/?foo=42'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't match nested screen if path is empty", () => {
|
it("doesn't match nested screen if path is empty", () => {
|
||||||
@@ -959,7 +959,7 @@ it("doesn't match nested screen if path is empty", () => {
|
|||||||
|
|
||||||
const path = '';
|
const path = '';
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chooses more exhaustive pattern', () => {
|
it('chooses more exhaustive pattern', () => {
|
||||||
@@ -1004,10 +1004,10 @@ it('chooses more exhaustive pattern', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles same paths beginnings', () => {
|
it('handles same paths beginnings', () => {
|
||||||
@@ -1048,10 +1048,10 @@ it('handles same paths beginnings', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles same paths beginnings with params', () => {
|
it('handles same paths beginnings with params', () => {
|
||||||
@@ -1096,10 +1096,10 @@ it('handles same paths beginnings with params', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles not taking path with too many segments', () => {
|
it('handles not taking path with too many segments', () => {
|
||||||
@@ -1151,10 +1151,10 @@ it('handles not taking path with too many segments', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles differently ordered params v1', () => {
|
it('handles differently ordered params v1', () => {
|
||||||
@@ -1206,10 +1206,10 @@ it('handles differently ordered params v1', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles differently ordered params v2', () => {
|
it('handles differently ordered params v2', () => {
|
||||||
@@ -1261,10 +1261,10 @@ it('handles differently ordered params v2', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles differently ordered params v3', () => {
|
it('handles differently ordered params v3', () => {
|
||||||
@@ -1316,10 +1316,10 @@ it('handles differently ordered params v3', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles differently ordered params v4', () => {
|
it('handles differently ordered params v4', () => {
|
||||||
@@ -1371,10 +1371,10 @@ it('handles differently ordered params v4', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/5/foos/res/20')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/5/foos/res/20'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles simple optional params', () => {
|
it('handles simple optional params', () => {
|
||||||
@@ -1426,10 +1426,10 @@ it('handles simple optional params', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle 2 optional params at the end v1', () => {
|
it('handle 2 optional params at the end v1', () => {
|
||||||
@@ -1481,10 +1481,10 @@ it('handle 2 optional params at the end v1', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle 2 optional params at the end v2', () => {
|
it('handle 2 optional params at the end v2', () => {
|
||||||
@@ -1536,10 +1536,10 @@ it('handle 2 optional params at the end v2', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle 2 optional params at the end v3', () => {
|
it('handle 2 optional params at the end v3', () => {
|
||||||
@@ -1592,10 +1592,10 @@ it('handle 2 optional params at the end v3', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the middle v1', () => {
|
it('handle optional params in the middle v1', () => {
|
||||||
@@ -1648,10 +1648,10 @@ it('handle optional params in the middle v1', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the middle v2', () => {
|
it('handle optional params in the middle v2', () => {
|
||||||
@@ -1704,10 +1704,10 @@ it('handle optional params in the middle v2', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the middle v3', () => {
|
it('handle optional params in the middle v3', () => {
|
||||||
@@ -1761,10 +1761,10 @@ it('handle optional params in the middle v3', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the middle v4', () => {
|
it('handle optional params in the middle v4', () => {
|
||||||
@@ -1818,10 +1818,10 @@ it('handle optional params in the middle v4', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the middle v5', () => {
|
it('handle optional params in the middle v5', () => {
|
||||||
@@ -1875,10 +1875,10 @@ it('handle optional params in the middle v5', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the beginning v1', () => {
|
it('handle optional params in the beginning v1', () => {
|
||||||
@@ -1932,10 +1932,10 @@ it('handle optional params in the beginning v1', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/5/10/foos/15')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/5/10/foos/15'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle optional params in the beginning v2', () => {
|
it('handle optional params in the beginning v2', () => {
|
||||||
@@ -1989,10 +1989,10 @@ it('handle optional params in the beginning v2', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/5/10/foos/15')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/5/10/foos/15'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merges parent patterns if needed', () => {
|
it('merges parent patterns if needed', () => {
|
||||||
@@ -2030,10 +2030,10 @@ it('merges parent patterns if needed', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/foo/42/baz/babel')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/foo/42/baz/babel'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores extra slashes in the pattern', () => {
|
it('ignores extra slashes in the pattern', () => {
|
||||||
@@ -2067,10 +2067,10 @@ it('ignores extra slashes in the pattern', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches wildcard patterns at root', () => {
|
it('matches wildcard patterns at root', () => {
|
||||||
@@ -2092,10 +2092,10 @@ it('matches wildcard patterns at root', () => {
|
|||||||
routes: [{ name: '404', path }],
|
routes: [{ name: '404', path }],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/404')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/404'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches wildcard patterns at nested level', () => {
|
it('matches wildcard patterns at nested level', () => {
|
||||||
@@ -2134,10 +2134,10 @@ it('matches wildcard patterns at nested level', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/bar/42/404')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/bar/42/404'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches wildcard patterns at nested level with exact', () => {
|
it('matches wildcard patterns at nested level with exact', () => {
|
||||||
@@ -2179,10 +2179,10 @@ it('matches wildcard patterns at nested level with exact', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/404')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/404'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tries to match wildcard patterns at the end', () => {
|
it('tries to match wildcard patterns at the end', () => {
|
||||||
@@ -2222,10 +2222,10 @@ it('tries to match wildcard patterns at the end', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses nearest parent wildcard match for unmatched paths', () => {
|
it('uses nearest parent wildcard match for unmatched paths', () => {
|
||||||
@@ -2257,17 +2257,17 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
changePath(state, '/404')
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(changePath(state, '/404'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if two screens map to the same pattern', () => {
|
it('throws if two screens map to the same pattern', () => {
|
||||||
const path = '/bar/42/baz/test';
|
const path = '/bar/42/baz/test';
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getStateFromPath(path, {
|
getStateFromPath<object>(path, {
|
||||||
screens: {
|
screens: {
|
||||||
Foo: {
|
Foo: {
|
||||||
screens: {
|
screens: {
|
||||||
@@ -2287,7 +2287,7 @@ it('throws if two screens map to the same pattern', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getStateFromPath(path, {
|
getStateFromPath<object>(path, {
|
||||||
screens: {
|
screens: {
|
||||||
Foo: {
|
Foo: {
|
||||||
screens: {
|
screens: {
|
||||||
@@ -2354,10 +2354,10 @@ it('correctly applies initialRouteName for config with similar route names', ()
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly applies initialRouteName for config with similar route names v2', () => {
|
it('correctly applies initialRouteName for config with similar route names v2', () => {
|
||||||
@@ -2414,8 +2414,8 @@ it('correctly applies initialRouteName for config with similar route names v2',
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getStateFromPath(path, config)).toEqual(state);
|
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
expect(
|
||||||
state
|
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||||
);
|
).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { render, act } from '@testing-library/react-native';
|
import { render, act } from '@testing-library/react-native';
|
||||||
import type { NavigationState } from '@react-navigation/routers';
|
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||||
|
import Group from '../Group';
|
||||||
import Screen from '../Screen';
|
import Screen from '../Screen';
|
||||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||||
import useNavigationBuilder from '../useNavigationBuilder';
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
|
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||||
import useNavigation from '../useNavigation';
|
import useNavigation from '../useNavigation';
|
||||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||||
import type { NavigationContainerRef } from '../types';
|
|
||||||
|
|
||||||
beforeEach(() => (MockRouterKey.current = 0));
|
beforeEach(() => (MockRouterKey.current = 0));
|
||||||
|
|
||||||
@@ -248,6 +249,53 @@ 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', () => {
|
it('initializes state for nested navigator on navigation', () => {
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
@@ -679,7 +727,7 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = render(
|
const element = render(
|
||||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
@@ -715,7 +763,7 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
screen: 'bar-b',
|
screen: 'bar-b',
|
||||||
params: { test: 42 },
|
params: { test: 42 },
|
||||||
})
|
})
|
||||||
@@ -726,7 +774,7 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
screen: 'bar-a',
|
screen: 'bar-a',
|
||||||
params: { whoa: 'test' },
|
params: { whoa: 'test' },
|
||||||
})
|
})
|
||||||
@@ -736,15 +784,15 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||||
|
|
||||||
act(() => navigation.current?.goBack());
|
act(() => navigation.goBack());
|
||||||
|
|
||||||
expect(element).toMatchInlineSnapshot(
|
expect(element).toMatchInlineSnapshot(
|
||||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||||
|
|
||||||
expect(element).toMatchInlineSnapshot(
|
expect(element).toMatchInlineSnapshot(
|
||||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42,\\"whoa\\":\\"test\\"}]"`
|
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42,\\"whoa\\":\\"test\\"}]"`
|
||||||
@@ -799,7 +847,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const first = render(
|
const first = render(
|
||||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
@@ -833,7 +881,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
key: '0',
|
key: '0',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -866,7 +914,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
screen: 'bar-b',
|
screen: 'bar-b',
|
||||||
params: { test: 42 },
|
params: { test: 42 },
|
||||||
})
|
})
|
||||||
@@ -876,7 +924,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 2,
|
index: 2,
|
||||||
key: '0',
|
key: '0',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -944,7 +992,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
key: '4',
|
key: '4',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -971,7 +1019,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
screen: 'bar-b',
|
screen: 'bar-b',
|
||||||
params: { test: 42 },
|
params: { test: 42 },
|
||||||
initial: false,
|
initial: false,
|
||||||
@@ -980,7 +1028,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
|
|
||||||
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 2,
|
index: 2,
|
||||||
key: '4',
|
key: '4',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -1071,7 +1119,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
|||||||
|
|
||||||
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 1,
|
index: 1,
|
||||||
key: '11',
|
key: '11',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -1119,7 +1167,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const first = render(
|
const first = render(
|
||||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
@@ -1150,7 +1198,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
|
|
||||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 0,
|
index: 0,
|
||||||
key: '0',
|
key: '0',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -1183,7 +1231,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
state: {
|
state: {
|
||||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||||
},
|
},
|
||||||
@@ -1192,7 +1240,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
|
|
||||||
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 1,
|
index: 1,
|
||||||
key: '0',
|
key: '0',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -1231,7 +1279,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
navigation.current?.navigate('bar', {
|
navigation.navigate('bar', {
|
||||||
state: {
|
state: {
|
||||||
index: 2,
|
index: 2,
|
||||||
routes: [
|
routes: [
|
||||||
@@ -1245,7 +1293,7 @@ it('resets state of a nested child in a navigator', () => {
|
|||||||
|
|
||||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState()).toEqual({
|
expect(navigation.getRootState()).toEqual({
|
||||||
index: 1,
|
index: 1,
|
||||||
key: '0',
|
key: '0',
|
||||||
routeNames: ['foo', 'bar'],
|
routeNames: ['foo', 'bar'],
|
||||||
@@ -1336,7 +1384,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const root = (
|
const root = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
@@ -1350,11 +1398,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
|||||||
|
|
||||||
render(root);
|
render(root);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
expect(navigation.getRootState().routeNames).toEqual(['foo', 'bar', 'baz']);
|
||||||
'foo',
|
|
||||||
'bar',
|
|
||||||
'baz',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('preserves order of screens in state with numeric names', () => {
|
it('preserves order of screens in state with numeric names', () => {
|
||||||
@@ -1363,7 +1407,7 @@ it('preserves order of screens in state with numeric names', () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const root = (
|
const root = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
@@ -1377,11 +1421,7 @@ it('preserves order of screens in state with numeric names', () => {
|
|||||||
|
|
||||||
render(root);
|
render(root);
|
||||||
|
|
||||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
expect(navigation.getRootState().routeNames).toEqual(['4', '7', '1']);
|
||||||
'4',
|
|
||||||
'7',
|
|
||||||
'1',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws if navigator doesn't have any screens", () => {
|
it("throws if navigator doesn't have any screens", () => {
|
||||||
@@ -1458,7 +1498,7 @@ it('throws when Screen is not the direct children', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(() => render(element).update(element)).toThrowError(
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
"A navigator can only contain 'Screen' components as its direct children (found 'Bar')"
|
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Bar')"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1483,7 +1523,7 @@ it('throws when undefined component is a direct children', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(() => render(element).update(element)).toThrowError(
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
"A navigator can only contain 'Screen' components as its direct children (found 'undefined' for the screen 'foo')"
|
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'undefined' for the screen 'foo')"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1503,7 +1543,7 @@ it('throws when a tag is a direct children', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(() => render(element).update(element)).toThrowError(
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
"A navigator can only contain 'Screen' components as its direct children (found 'screen' for the screen 'foo')"
|
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'screen' for the screen 'foo')"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1523,7 +1563,7 @@ it('throws when a React Element is not the direct children', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(() => render(element).update(element)).toThrowError(
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
"A navigator can only contain 'Screen' components as its direct children (found 'Hello world')"
|
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Hello world')"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1801,7 +1841,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
|||||||
|
|
||||||
const TestScreen = () => null;
|
const TestScreen = () => null;
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
@@ -1824,7 +1864,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
|||||||
|
|
||||||
render(container).update(container);
|
render(container).update(container);
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentRoute()).toEqual({
|
expect(navigation.getCurrentRoute()).toEqual({
|
||||||
key: 'bar-a',
|
key: 'bar-a',
|
||||||
name: 'bar-a',
|
name: 'bar-a',
|
||||||
});
|
});
|
||||||
@@ -1839,26 +1879,23 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
|||||||
|
|
||||||
const TestScreen = () => null;
|
const TestScreen = () => null;
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="bar" options={{ a: 'b' }}>
|
<Screen name="bar" options={{ a: 'b' }}>
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator
|
<TestNavigator initialRouteName="bar-a">
|
||||||
initialRouteName="bar-a"
|
|
||||||
screenOptions={() => ({ sample2: 'data' })}
|
|
||||||
>
|
|
||||||
<Screen
|
<Screen
|
||||||
name="bar-a"
|
name="bar-a"
|
||||||
component={TestScreen}
|
component={TestScreen}
|
||||||
options={{ sample: 'data' }}
|
options={{ sample: '1' }}
|
||||||
/>
|
/>
|
||||||
<Screen
|
<Screen
|
||||||
name="bar-b"
|
name="bar-b"
|
||||||
component={TestScreen}
|
component={TestScreen}
|
||||||
options={{ sample3: 'data' }}
|
options={{ sample2: '2' }}
|
||||||
/>
|
/>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
@@ -1870,16 +1907,123 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
|||||||
|
|
||||||
render(container).update(container);
|
render(container).update(container);
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
expect(navigation.getCurrentOptions()).toEqual({
|
||||||
sample: 'data',
|
sample: '1',
|
||||||
sample2: 'data',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => navigation.current?.navigate('bar-b'));
|
act(() => navigation.navigate('bar-b'));
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
expect(navigation.getCurrentOptions()).toEqual({
|
||||||
sample2: 'data',
|
sample2: '2',
|
||||||
sample3: 'data',
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestScreen}
|
||||||
|
options={{ sample3: '3' }}
|
||||||
|
/>
|
||||||
|
</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',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1892,26 +2036,23 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
|||||||
|
|
||||||
const TestScreen = () => null;
|
const TestScreen = () => null;
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="bar" options={{ a: 'b' }}>
|
<Screen name="bar" options={{ a: 'b' }}>
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator
|
<TestNavigator initialRouteName="bar-a">
|
||||||
initialRouteName="bar-a"
|
|
||||||
screenOptions={() => ({ sample2: 'data' })}
|
|
||||||
>
|
|
||||||
<Screen
|
<Screen
|
||||||
name="bar-a"
|
name="bar-a"
|
||||||
component={TestScreen}
|
component={TestScreen}
|
||||||
options={{ sample: 'data' }}
|
options={{ sample: '1' }}
|
||||||
/>
|
/>
|
||||||
<Screen
|
<Screen
|
||||||
name="bar-b"
|
name="bar-b"
|
||||||
component={TestScreen}
|
component={TestScreen}
|
||||||
options={{ sample3: 'data' }}
|
options={{ sample2: '2' }}
|
||||||
/>
|
/>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
@@ -1923,16 +2064,123 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
|||||||
|
|
||||||
render(container).update(container);
|
render(container).update(container);
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
expect(navigation.getCurrentOptions()).toEqual({
|
||||||
sample: 'data',
|
sample: '1',
|
||||||
sample2: 'data',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => navigation.current?.navigate('bar-b'));
|
act(() => navigation.navigate('bar-b'));
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
expect(navigation.getCurrentOptions()).toEqual({
|
||||||
sample2: 'data',
|
sample2: '2',
|
||||||
sample3: 'data',
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestScreen}
|
||||||
|
options={{ sample3: '3' }}
|
||||||
|
/>
|
||||||
|
</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',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1945,7 +2193,7 @@ it('does not throw if while getting current options with no options defined', ()
|
|||||||
|
|
||||||
const TestScreen = () => null;
|
const TestScreen = () => null;
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<BaseNavigationContainer ref={navigation}>
|
<BaseNavigationContainer ref={navigation}>
|
||||||
@@ -1968,11 +2216,11 @@ it('does not throw if while getting current options with no options defined', ()
|
|||||||
|
|
||||||
render(container).update(container);
|
render(container).update(container);
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual({});
|
expect(navigation.getCurrentOptions()).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw if while getting current options with empty container', () => {
|
it('does not throw if while getting current options with empty container', () => {
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<BaseNavigationContainer ref={navigation} children={null} />
|
<BaseNavigationContainer ref={navigation} children={null} />
|
||||||
@@ -1980,5 +2228,5 @@ it('does not throw if while getting current options with empty container', () =>
|
|||||||
|
|
||||||
render(container).update(container);
|
render(container).update(container);
|
||||||
|
|
||||||
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
|
expect(navigation.getCurrentOptions()).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import {
|
|||||||
DefaultRouterOptions,
|
DefaultRouterOptions,
|
||||||
NavigationState,
|
NavigationState,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
|
ParamListBase,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import useNavigationBuilder from '../useNavigationBuilder';
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||||
import Screen from '../Screen';
|
import Screen from '../Screen';
|
||||||
|
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||||
import MockRouter, {
|
import MockRouter, {
|
||||||
MockActions,
|
MockActions,
|
||||||
MockRouterKey,
|
MockRouterKey,
|
||||||
} from './__fixtures__/MockRouter';
|
} from './__fixtures__/MockRouter';
|
||||||
import type { NavigationContainerRef } from '../types';
|
|
||||||
|
|
||||||
jest.mock('nanoid/non-secure', () => {
|
jest.mock('nanoid/non-secure', () => {
|
||||||
const m = { nanoid: () => String(++m.__key), __key: 0 };
|
const m = { nanoid: () => String(++m.__key), __key: 0 };
|
||||||
@@ -571,7 +572,7 @@ it("prevents removing a screen with 'beforeRemove' event", () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
@@ -706,7 +707,7 @@ it("prevents removing a child screen with 'beforeRemove' event", () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
@@ -867,7 +868,7 @@ it("prevents removing a grand child screen with 'beforeRemove' event", () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
@@ -1065,7 +1066,7 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
@@ -1217,7 +1218,7 @@ it("prevents removing a child screen with 'beforeRemove' event with 'resetRoot'"
|
|||||||
|
|
||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
|
|||||||
47
packages/core/src/createNavigationContainerRef.tsx
Normal file
47
packages/core/src/createNavigationContainerRef.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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,5 +1,6 @@
|
|||||||
import type * as React from 'react';
|
import type * as React from 'react';
|
||||||
import type { ParamListBase, NavigationState } from '@react-navigation/routers';
|
import type { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||||
|
import Group from './Group';
|
||||||
import Screen from './Screen';
|
import Screen from './Screen';
|
||||||
import type { TypedNavigator, EventMapBase } from './types';
|
import type { TypedNavigator, EventMapBase } from './types';
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export default function createNavigatorFactory<
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
Navigator,
|
Navigator,
|
||||||
|
Group,
|
||||||
Screen,
|
Screen,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ type ConfigItem = {
|
|||||||
screens?: Record<string, ConfigItem>;
|
screens?: Record<string, ConfigItem>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
type Options = {
|
||||||
|
initialRouteName?: string;
|
||||||
|
screens: PathConfigMap<object>;
|
||||||
|
};
|
||||||
|
|
||||||
type NavigateAction<State extends NavigationState> = {
|
type NavigateAction<State extends NavigationState> = {
|
||||||
type: 'NAVIGATE';
|
type: 'NAVIGATE';
|
||||||
@@ -29,7 +32,9 @@ export default function getActionFromState(
|
|||||||
options?: Options
|
options?: Options
|
||||||
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
|
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
|
||||||
// Create a normalized configs object which will be easier to use
|
// Create a normalized configs object which will be easier to use
|
||||||
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
|
const normalizedConfig = options
|
||||||
|
? createNormalizedConfigItem(options as PathConfig<object> | string)
|
||||||
|
: {};
|
||||||
|
|
||||||
const routes =
|
const routes =
|
||||||
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
|
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
|
||||||
@@ -130,7 +135,7 @@ export default function getActionFromState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNormalizedConfigItem = (config: PathConfig | string) =>
|
const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
|
||||||
typeof config === 'object' && config != null
|
typeof config === 'object' && config != null
|
||||||
? {
|
? {
|
||||||
initialRouteName: config.initialRouteName,
|
initialRouteName: config.initialRouteName,
|
||||||
@@ -141,7 +146,7 @@ const createNormalizedConfigItem = (config: PathConfig | string) =>
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const createNormalizedConfigs = (options: PathConfigMap) =>
|
const createNormalizedConfigs = (options: PathConfigMap<object>) =>
|
||||||
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
||||||
acc[k] = createNormalizedConfigItem(v);
|
acc[k] = createNormalizedConfigItem(v);
|
||||||
return acc;
|
return acc;
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import type {
|
|||||||
import fromEntries from './fromEntries';
|
import fromEntries from './fromEntries';
|
||||||
import type { PathConfig, PathConfigMap } from './types';
|
import type { PathConfig, PathConfigMap } from './types';
|
||||||
|
|
||||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
type Options<ParamList> = {
|
||||||
|
initialRouteName?: string;
|
||||||
|
screens: PathConfigMap<ParamList>;
|
||||||
|
};
|
||||||
|
|
||||||
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
||||||
|
|
||||||
@@ -61,9 +64,9 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
|
|||||||
* @param options Extra options to fine-tune how to serialize the path.
|
* @param options Extra options to fine-tune how to serialize the path.
|
||||||
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
||||||
*/
|
*/
|
||||||
export default function getPathFromState(
|
export default function getPathFromState<ParamList extends {}>(
|
||||||
state: State,
|
state: State,
|
||||||
options?: Options
|
options?: Options<ParamList>
|
||||||
): string {
|
): string {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@@ -238,7 +241,7 @@ const joinPaths = (...paths: string[]): string =>
|
|||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
const createConfigItem = (
|
const createConfigItem = (
|
||||||
config: PathConfig | string,
|
config: PathConfig<object> | string,
|
||||||
parentPattern?: string
|
parentPattern?: string
|
||||||
): ConfigItem => {
|
): ConfigItem => {
|
||||||
if (typeof config === 'string') {
|
if (typeof config === 'string') {
|
||||||
@@ -276,7 +279,7 @@ const createConfigItem = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createNormalizedConfigs = (
|
const createNormalizedConfigs = (
|
||||||
options: PathConfigMap,
|
options: PathConfigMap<object>,
|
||||||
pattern?: string
|
pattern?: string
|
||||||
): Record<string, ConfigItem> =>
|
): Record<string, ConfigItem> =>
|
||||||
fromEntries(
|
fromEntries(
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import type {
|
|||||||
import findFocusedRoute from './findFocusedRoute';
|
import findFocusedRoute from './findFocusedRoute';
|
||||||
import type { PathConfigMap } from './types';
|
import type { PathConfigMap } from './types';
|
||||||
|
|
||||||
type Options = {
|
type Options<ParamList extends {}> = {
|
||||||
initialRouteName?: string;
|
initialRouteName?: string;
|
||||||
screens: PathConfigMap;
|
screens: PathConfigMap<ParamList>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ParseConfig = Record<string, (value: string) => any>;
|
type ParseConfig = Record<string, (value: string) => any>;
|
||||||
@@ -60,9 +60,9 @@ type ParsedRoute = {
|
|||||||
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
|
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
|
||||||
* @param options Extra options to fine-tune how to parse the path.
|
* @param options Extra options to fine-tune how to parse the path.
|
||||||
*/
|
*/
|
||||||
export default function getStateFromPath(
|
export default function getStateFromPath<ParamList extends {}>(
|
||||||
path: string,
|
path: string,
|
||||||
options?: Options
|
options?: Options<ParamList>
|
||||||
): ResultState | undefined {
|
): ResultState | undefined {
|
||||||
let initialRoutes: InitialRouteConfig[] = [];
|
let initialRoutes: InitialRouteConfig[] = [];
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ export default function getStateFromPath(
|
|||||||
...Object.keys(screens).map((key) =>
|
...Object.keys(screens).map((key) =>
|
||||||
createNormalizedConfigs(
|
createNormalizedConfigs(
|
||||||
key,
|
key,
|
||||||
screens as PathConfigMap,
|
screens as PathConfigMap<object>,
|
||||||
[],
|
[],
|
||||||
initialRoutes,
|
initialRoutes,
|
||||||
[]
|
[]
|
||||||
@@ -307,7 +307,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
|||||||
|
|
||||||
const createNormalizedConfigs = (
|
const createNormalizedConfigs = (
|
||||||
screen: string,
|
screen: string,
|
||||||
routeConfig: PathConfigMap,
|
routeConfig: PathConfigMap<object>,
|
||||||
routeNames: string[] = [],
|
routeNames: string[] = [],
|
||||||
initials: InitialRouteConfig[],
|
initials: InitialRouteConfig[],
|
||||||
parentScreens: string[],
|
parentScreens: string[],
|
||||||
@@ -319,6 +319,7 @@ const createNormalizedConfigs = (
|
|||||||
|
|
||||||
parentScreens.push(screen);
|
parentScreens.push(screen);
|
||||||
|
|
||||||
|
// @ts-expect-error: we can't strongly typecheck this for now
|
||||||
const config = routeConfig[screen];
|
const config = routeConfig[screen];
|
||||||
|
|
||||||
if (typeof config === 'string') {
|
if (typeof config === 'string') {
|
||||||
@@ -345,7 +346,13 @@ const createNormalizedConfigs = (
|
|||||||
: config.path || '';
|
: config.path || '';
|
||||||
|
|
||||||
configs.push(
|
configs.push(
|
||||||
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
|
createConfigItem(
|
||||||
|
screen,
|
||||||
|
routeNames,
|
||||||
|
pattern!,
|
||||||
|
config.path,
|
||||||
|
config.parse
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +368,7 @@ const createNormalizedConfigs = (
|
|||||||
Object.keys(config.screens).forEach((nestedConfig) => {
|
Object.keys(config.screens).forEach((nestedConfig) => {
|
||||||
const result = createNormalizedConfigs(
|
const result = createNormalizedConfigs(
|
||||||
nestedConfig,
|
nestedConfig,
|
||||||
config.screens as PathConfigMap,
|
config.screens as PathConfigMap<object>,
|
||||||
routeNames,
|
routeNames,
|
||||||
initials,
|
initials,
|
||||||
[...parentScreens],
|
[...parentScreens],
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ export * from '@react-navigation/routers';
|
|||||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||||
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
||||||
|
|
||||||
|
export { default as createNavigationContainerRef } from './createNavigationContainerRef';
|
||||||
|
export { default as useNavigationContainerRef } from './useNavigationContainerRef';
|
||||||
|
|
||||||
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
||||||
export { default as NavigationContext } from './NavigationContext';
|
export { default as NavigationContext } from './NavigationContext';
|
||||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||||
|
|||||||
@@ -9,13 +9,23 @@ import type {
|
|||||||
ParamListBase,
|
ParamListBase,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace ReactNavigation {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
interface RootParamList {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Keyof<T extends {}> = Extract<keyof T, string>;
|
||||||
|
|
||||||
export type DefaultNavigatorOptions<
|
export type DefaultNavigatorOptions<
|
||||||
ScreenOptions extends {},
|
ScreenOptions extends {},
|
||||||
ParamList extends ParamListBase = ParamListBase
|
ParamList extends ParamListBase = ParamListBase
|
||||||
> = DefaultRouterOptions<Extract<keyof ParamList, string>> & {
|
> = DefaultRouterOptions<Keyof<ParamList>> & {
|
||||||
/**
|
/**
|
||||||
* Children React Elements to extract the route configuration from.
|
* Children React Elements to extract the route configuration from.
|
||||||
* Only `Screen` components are supported as children.
|
* Only `Screen`, `Group` and `React.Fragment` are supported as children.
|
||||||
*/
|
*/
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +34,7 @@ export type DefaultNavigatorOptions<
|
|||||||
screenOptions?:
|
screenOptions?:
|
||||||
| ScreenOptions
|
| ScreenOptions
|
||||||
| ((props: {
|
| ((props: {
|
||||||
route: RouteProp<ParamList, keyof ParamList>;
|
route: RouteProp<ParamList>;
|
||||||
navigation: any;
|
navigation: any;
|
||||||
}) => ScreenOptions);
|
}) => ScreenOptions);
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +44,7 @@ export type DefaultNavigatorOptions<
|
|||||||
defaultScreenOptions?:
|
defaultScreenOptions?:
|
||||||
| ScreenOptions
|
| ScreenOptions
|
||||||
| ((props: {
|
| ((props: {
|
||||||
route: RouteProp<ParamList, keyof ParamList>;
|
route: RouteProp<ParamList>;
|
||||||
navigation: any;
|
navigation: any;
|
||||||
options: ScreenOptions;
|
options: ScreenOptions;
|
||||||
}) => ScreenOptions);
|
}) => ScreenOptions);
|
||||||
@@ -96,11 +106,11 @@ export type EventConsumer<EventMap extends EventMapBase> = {
|
|||||||
* @param type Type of the event (e.g. `focus`, `blur`)
|
* @param type Type of the event (e.g. `focus`, `blur`)
|
||||||
* @param callback Callback listener which is executed upon receiving the event.
|
* @param callback Callback listener which is executed upon receiving the event.
|
||||||
*/
|
*/
|
||||||
addListener<EventName extends Extract<keyof EventMap, string>>(
|
addListener<EventName extends Keyof<EventMap>>(
|
||||||
type: EventName,
|
type: EventName,
|
||||||
callback: EventListenerCallback<EventMap, EventName>
|
callback: EventListenerCallback<EventMap, EventName>
|
||||||
): () => void;
|
): () => void;
|
||||||
removeListener<EventName extends Extract<keyof EventMap, string>>(
|
removeListener<EventName extends Keyof<EventMap>>(
|
||||||
type: EventName,
|
type: EventName,
|
||||||
callback: EventListenerCallback<EventMap, EventName>
|
callback: EventListenerCallback<EventMap, EventName>
|
||||||
): void;
|
): void;
|
||||||
@@ -115,7 +125,7 @@ export type EventEmitter<EventMap extends EventMapBase> = {
|
|||||||
* @param [options.target] Key of the target route which should receive the event.
|
* @param [options.target] Key of the target route which should receive the event.
|
||||||
* If not specified, all routes receive the event.
|
* If not specified, all routes receive the event.
|
||||||
*/
|
*/
|
||||||
emit<EventName extends Extract<keyof EventMap, string>>(
|
emit<EventName extends Keyof<EventMap>>(
|
||||||
options: {
|
options: {
|
||||||
type: EventName;
|
type: EventName;
|
||||||
target?: string;
|
target?: string;
|
||||||
@@ -263,8 +273,8 @@ export type NavigationContainerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationProp<
|
export type NavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends {},
|
||||||
RouteName extends keyof ParamList = string,
|
RouteName extends keyof ParamList = Keyof<ParamList>,
|
||||||
State extends NavigationState = NavigationState<ParamList>,
|
State extends NavigationState = NavigationState<ParamList>,
|
||||||
ScreenOptions extends {} = {},
|
ScreenOptions extends {} = {},
|
||||||
EventMap extends EventMapBase = {}
|
EventMap extends EventMapBase = {}
|
||||||
@@ -289,7 +299,7 @@ export type NavigationProp<
|
|||||||
|
|
||||||
export type RouteProp<
|
export type RouteProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList
|
RouteName extends keyof ParamList = Keyof<ParamList>
|
||||||
> = Route<Extract<RouteName, string>, ParamList[RouteName]>;
|
> = Route<Extract<RouteName, string>, ParamList[RouteName]>;
|
||||||
|
|
||||||
export type CompositeNavigationProp<
|
export type CompositeNavigationProp<
|
||||||
@@ -325,6 +335,19 @@ export type CompositeNavigationProp<
|
|||||||
A extends NavigationProp<any, any, any, any, infer E> ? E : {}
|
A extends NavigationProp<any, any, any, any, infer E> ? E : {}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type CompositeScreenProps<
|
||||||
|
A extends {
|
||||||
|
navigation: NavigationProp<ParamListBase, string, any, any>;
|
||||||
|
route: RouteProp<ParamListBase>;
|
||||||
|
},
|
||||||
|
B extends {
|
||||||
|
navigation: NavigationHelpersCommon<ParamListBase, any>;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
navigation: CompositeNavigationProp<A['navigation'], B['navigation']>;
|
||||||
|
route: A['route'];
|
||||||
|
};
|
||||||
|
|
||||||
export type Descriptor<
|
export type Descriptor<
|
||||||
ScreenOptions extends {},
|
ScreenOptions extends {},
|
||||||
Navigation extends NavigationProp<any, any, any, any, any>,
|
Navigation extends NavigationProp<any, any, any, any, any>,
|
||||||
@@ -361,6 +384,38 @@ export type ScreenListeners<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type RouteConfigComponent<
|
||||||
|
ParamList extends ParamListBase,
|
||||||
|
RouteName extends keyof ParamList
|
||||||
|
> =
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* React component to render for this screen.
|
||||||
|
*/
|
||||||
|
component: React.ComponentType<any>;
|
||||||
|
getComponent?: never;
|
||||||
|
children?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* Lazily get a React component to render for this screen.
|
||||||
|
*/
|
||||||
|
getComponent: () => React.ComponentType<any>;
|
||||||
|
component?: never;
|
||||||
|
children?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* Render callback to render content of this screen.
|
||||||
|
*/
|
||||||
|
children: (props: {
|
||||||
|
route: RouteProp<ParamList, RouteName>;
|
||||||
|
navigation: any;
|
||||||
|
}) => React.ReactNode;
|
||||||
|
component?: never;
|
||||||
|
getComponent?: never;
|
||||||
|
};
|
||||||
|
|
||||||
export type RouteConfig<
|
export type RouteConfig<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList,
|
RouteName extends keyof ParamList,
|
||||||
@@ -405,35 +460,27 @@ export type RouteConfig<
|
|||||||
* Initial params object for the route.
|
* Initial params object for the route.
|
||||||
*/
|
*/
|
||||||
initialParams?: Partial<ParamList[RouteName]>;
|
initialParams?: Partial<ParamList[RouteName]>;
|
||||||
} & (
|
} & RouteConfigComponent<ParamList, RouteName>;
|
||||||
| {
|
|
||||||
/**
|
export type RouteGroupConfig<
|
||||||
* React component to render for this screen.
|
ParamList extends ParamListBase,
|
||||||
*/
|
ScreenOptions extends {}
|
||||||
component: React.ComponentType<any>;
|
> = {
|
||||||
getComponent?: never;
|
/**
|
||||||
children?: never;
|
* Navigator options for this screen.
|
||||||
}
|
*/
|
||||||
| {
|
screenOptions?:
|
||||||
/**
|
| ScreenOptions
|
||||||
* Lazily get a React component to render for this screen.
|
| ((props: {
|
||||||
*/
|
route: RouteProp<ParamList, keyof ParamList>;
|
||||||
getComponent: () => React.ComponentType<any>;
|
|
||||||
component?: never;
|
|
||||||
children?: never;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
/**
|
|
||||||
* Render callback to render content of this screen.
|
|
||||||
*/
|
|
||||||
children: (props: {
|
|
||||||
route: RouteProp<ParamList, RouteName>;
|
|
||||||
navigation: any;
|
navigation: any;
|
||||||
}) => React.ReactNode;
|
}) => ScreenOptions);
|
||||||
component?: never;
|
/**
|
||||||
getComponent?: never;
|
* Children React Elements to extract the route configuration from.
|
||||||
}
|
* Only `Screen`, `Group` and `React.Fragment` are supported as children.
|
||||||
);
|
*/
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
export type NavigationContainerEventMap = {
|
export type NavigationContainerEventMap = {
|
||||||
/**
|
/**
|
||||||
@@ -470,7 +517,9 @@ export type NavigationContainerEventMap = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
export type NavigationContainerRef<
|
||||||
|
ParamList extends {}
|
||||||
|
> = NavigationHelpers<ParamList> &
|
||||||
EventConsumer<NavigationContainerEventMap> & {
|
EventConsumer<NavigationContainerEventMap> & {
|
||||||
/**
|
/**
|
||||||
* Reset the navigation state of the root navigator to the provided state.
|
* Reset the navigation state of the root navigator to the provided state.
|
||||||
@@ -490,8 +539,18 @@ export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
|||||||
* Get the currently focused route's options.
|
* Get the currently focused route's options.
|
||||||
*/
|
*/
|
||||||
getCurrentOptions(): object | undefined;
|
getCurrentOptions(): object | undefined;
|
||||||
|
/**
|
||||||
|
* Whether the navigation container is ready to handle actions.
|
||||||
|
*/
|
||||||
|
isReady(): boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NavigationContainerRefWithCurrent<
|
||||||
|
ParamList extends {}
|
||||||
|
> = NavigationContainerRef<ParamList> & {
|
||||||
|
current: NavigationContainerRef<ParamList> | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type TypedNavigator<
|
export type TypedNavigator<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
State extends NavigationState,
|
State extends NavigationState,
|
||||||
@@ -509,6 +568,10 @@ export type TypedNavigator<
|
|||||||
> &
|
> &
|
||||||
DefaultNavigatorOptions<ScreenOptions, ParamList>
|
DefaultNavigatorOptions<ScreenOptions, ParamList>
|
||||||
>;
|
>;
|
||||||
|
/**
|
||||||
|
* Component used for grouping multiple route configuration.
|
||||||
|
*/
|
||||||
|
Group: React.ComponentType<RouteGroupConfig<ParamList, ScreenOptions>>;
|
||||||
/**
|
/**
|
||||||
* Component used for specifying route configuration.
|
* Component used for specifying route configuration.
|
||||||
*/
|
*/
|
||||||
@@ -546,15 +609,20 @@ export type NavigatorScreenParams<
|
|||||||
};
|
};
|
||||||
}[keyof ParamList];
|
}[keyof ParamList];
|
||||||
|
|
||||||
export type PathConfig = {
|
export type PathConfig<ParamList extends {}> = {
|
||||||
path?: string;
|
path?: string;
|
||||||
exact?: boolean;
|
exact?: boolean;
|
||||||
parse?: Record<string, (value: string) => any>;
|
parse?: Record<string, (value: string) => any>;
|
||||||
stringify?: Record<string, (value: any) => string>;
|
stringify?: Record<string, (value: any) => string>;
|
||||||
screens?: PathConfigMap;
|
screens?: PathConfigMap<ParamList>;
|
||||||
initialRouteName?: string;
|
initialRouteName?: keyof ParamList;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PathConfigMap = {
|
export type PathConfigMap<ParamList extends {}> = {
|
||||||
[routeName: string]: string | PathConfig;
|
[RouteName in keyof ParamList]?: ParamList[RouteName] extends NavigatorScreenParams<
|
||||||
|
infer T,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
? string | PathConfig<T>
|
||||||
|
: string | Omit<PathConfig<{}>, 'screens' | 'initialRouteName'>;
|
||||||
};
|
};
|
||||||
|
|||||||
30
packages/core/src/useComponent.tsx
Normal file
30
packages/core/src/useComponent.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function useComponent<
|
||||||
|
T extends React.ComponentType<any>,
|
||||||
|
P extends {}
|
||||||
|
>(Component: T, props: P) {
|
||||||
|
const propsRef = React.useRef<P | null>(props);
|
||||||
|
|
||||||
|
// Normally refs shouldn't be mutated in render
|
||||||
|
// But we return a component which will be rendered
|
||||||
|
// So it's just for immediate consumption
|
||||||
|
propsRef.current = props;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
propsRef.current = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return React.useRef((rest: Omit<React.ComponentProps<T>, keyof P>) => {
|
||||||
|
const props = propsRef.current;
|
||||||
|
|
||||||
|
if (props === null) {
|
||||||
|
throw new Error(
|
||||||
|
'The returned component must be rendered in the same render phase as the hook.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error: the props should be fine here
|
||||||
|
return <Component {...props} {...rest} />;
|
||||||
|
}).current;
|
||||||
|
}
|
||||||
@@ -13,11 +13,7 @@ type Options = {
|
|||||||
navigation: NavigationHelpers<ParamListBase>;
|
navigation: NavigationHelpers<ParamListBase>;
|
||||||
descriptors: Record<
|
descriptors: Record<
|
||||||
string,
|
string,
|
||||||
Descriptor<
|
Descriptor<object, NavigationProp<ParamListBase>, RouteProp<ParamListBase>>
|
||||||
object,
|
|
||||||
NavigationProp<ParamListBase>,
|
|
||||||
RouteProp<ParamListBase, string>
|
|
||||||
>
|
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,22 @@ import type {
|
|||||||
NavigationProp,
|
NavigationProp,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
export type ScreenConfigWithParent<
|
||||||
|
State extends NavigationState,
|
||||||
|
ScreenOptions extends {},
|
||||||
|
EventMap extends EventMapBase
|
||||||
|
> = [
|
||||||
|
(ScreenOptionsOrCallback<ScreenOptions> | undefined)[] | undefined,
|
||||||
|
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||||
|
];
|
||||||
|
|
||||||
|
type ScreenOptionsOrCallback<ScreenOptions extends {}> =
|
||||||
|
| ScreenOptions
|
||||||
|
| ((props: {
|
||||||
|
route: RouteProp<ParamListBase, string>;
|
||||||
|
navigation: any;
|
||||||
|
}) => ScreenOptions);
|
||||||
|
|
||||||
type Options<
|
type Options<
|
||||||
State extends NavigationState,
|
State extends NavigationState,
|
||||||
ScreenOptions extends {},
|
ScreenOptions extends {},
|
||||||
@@ -32,19 +48,14 @@ type Options<
|
|||||||
state: State;
|
state: State;
|
||||||
screens: Record<
|
screens: Record<
|
||||||
string,
|
string,
|
||||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
||||||
>;
|
>;
|
||||||
navigation: NavigationHelpers<ParamListBase>;
|
navigation: NavigationHelpers<ParamListBase>;
|
||||||
screenOptions?:
|
screenOptions?: ScreenOptionsOrCallback<ScreenOptions>;
|
||||||
| ScreenOptions
|
|
||||||
| ((props: {
|
|
||||||
route: RouteProp<ParamListBase, string>;
|
|
||||||
navigation: any;
|
|
||||||
}) => ScreenOptions);
|
|
||||||
defaultScreenOptions?:
|
defaultScreenOptions?:
|
||||||
| ScreenOptions
|
| ScreenOptions
|
||||||
| ((props: {
|
| ((props: {
|
||||||
route: RouteProp<ParamListBase, string>;
|
route: RouteProp<ParamListBase>;
|
||||||
navigation: any;
|
navigation: any;
|
||||||
options: ScreenOptions;
|
options: ScreenOptions;
|
||||||
}) => ScreenOptions);
|
}) => ScreenOptions);
|
||||||
@@ -133,33 +144,35 @@ export default function useDescriptors<
|
|||||||
ScreenOptions,
|
ScreenOptions,
|
||||||
NavigationProp<ParamListBase, string, State, ScreenOptions, EventMap> &
|
NavigationProp<ParamListBase, string, State, ScreenOptions, EventMap> &
|
||||||
ActionHelpers,
|
ActionHelpers,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
>((acc, route, i) => {
|
>((acc, route, i) => {
|
||||||
const screen = screens[route.name];
|
const config = screens[route.name];
|
||||||
|
const screen = config[1];
|
||||||
const navigation = navigations[route.key];
|
const navigation = navigations[route.key];
|
||||||
|
|
||||||
const customOptions = {
|
const optionsList = [
|
||||||
// The default `screenOptions` passed to the navigator
|
// The default `screenOptions` passed to the navigator
|
||||||
...(typeof screenOptions === 'object' || screenOptions == null
|
screenOptions,
|
||||||
? screenOptions
|
// The `screenOptions` props passed to `Group` elements
|
||||||
: // @ts-expect-error: this is a function, but typescript doesn't think so
|
...((config[0]
|
||||||
screenOptions({
|
? config[0].filter(Boolean)
|
||||||
route,
|
: []) as ScreenOptionsOrCallback<ScreenOptions>[]),
|
||||||
navigation,
|
// The `options` prop passed to `Screen` elements,
|
||||||
})),
|
screen.options,
|
||||||
// The `options` prop passed to `Screen` elements
|
|
||||||
...(typeof screen.options === 'object' || screen.options == null
|
|
||||||
? screen.options
|
|
||||||
: // @ts-expect-error: this is a function, but typescript doesn't think so
|
|
||||||
screen.options({
|
|
||||||
route,
|
|
||||||
navigation,
|
|
||||||
})),
|
|
||||||
// The options set via `navigation.setOptions`
|
// The options set via `navigation.setOptions`
|
||||||
...options[route.key],
|
options[route.key],
|
||||||
};
|
];
|
||||||
|
|
||||||
|
const customOptions = optionsList.reduce<ScreenOptions>(
|
||||||
|
(acc, curr) =>
|
||||||
|
Object.assign(
|
||||||
|
acc,
|
||||||
|
typeof curr !== 'function' ? curr : curr({ route, navigation })
|
||||||
|
),
|
||||||
|
{} as ScreenOptions
|
||||||
|
);
|
||||||
|
|
||||||
const mergedOptions = {
|
const mergedOptions = {
|
||||||
...(typeof defaultScreenOptions === 'function'
|
...(typeof defaultScreenOptions === 'function'
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { ParamListBase } from '@react-navigation/routers';
|
|
||||||
import NavigationContext from './NavigationContext';
|
import NavigationContext from './NavigationContext';
|
||||||
import type { NavigationProp } from './types';
|
import type { NavigationProp } from './types';
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ import type { NavigationProp } from './types';
|
|||||||
* @returns Navigation prop of the parent screen.
|
* @returns Navigation prop of the parent screen.
|
||||||
*/
|
*/
|
||||||
export default function useNavigation<
|
export default function useNavigation<
|
||||||
T extends NavigationProp<ParamListBase>
|
T = NavigationProp<ReactNavigation.RootParamList>
|
||||||
>(): T {
|
>(): T {
|
||||||
const navigation = React.useContext(NavigationContext);
|
const navigation = React.useContext(NavigationContext);
|
||||||
|
|
||||||
@@ -19,5 +18,6 @@ export default function useNavigation<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigation as T;
|
// FIXME: Figure out a better way to do this
|
||||||
|
return (navigation as unknown) as T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import {
|
|||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import NavigationStateContext from './NavigationStateContext';
|
import NavigationStateContext from './NavigationStateContext';
|
||||||
import NavigationRouteContext from './NavigationRouteContext';
|
import NavigationRouteContext from './NavigationRouteContext';
|
||||||
|
import NavigationHelpersContext from './NavigationHelpersContext';
|
||||||
|
import Group from './Group';
|
||||||
import Screen from './Screen';
|
import Screen from './Screen';
|
||||||
import useEventEmitter from './useEventEmitter';
|
import useEventEmitter from './useEventEmitter';
|
||||||
import useRegisterNavigator from './useRegisterNavigator';
|
import useRegisterNavigator from './useRegisterNavigator';
|
||||||
import useDescriptors from './useDescriptors';
|
import useDescriptors, { ScreenConfigWithParent } from './useDescriptors';
|
||||||
import useNavigationHelpers from './useNavigationHelpers';
|
import useNavigationHelpers from './useNavigationHelpers';
|
||||||
import useOnAction from './useOnAction';
|
import useOnAction from './useOnAction';
|
||||||
import useFocusEvents from './useFocusEvents';
|
import useFocusEvents from './useFocusEvents';
|
||||||
@@ -28,6 +30,7 @@ import useKeyedChildListeners from './useKeyedChildListeners';
|
|||||||
import useOnGetState from './useOnGetState';
|
import useOnGetState from './useOnGetState';
|
||||||
import useScheduleUpdate from './useScheduleUpdate';
|
import useScheduleUpdate from './useScheduleUpdate';
|
||||||
import useCurrentRender from './useCurrentRender';
|
import useCurrentRender from './useCurrentRender';
|
||||||
|
import useComponent from './useComponent';
|
||||||
import isArrayEqual from './isArrayEqual';
|
import isArrayEqual from './isArrayEqual';
|
||||||
import {
|
import {
|
||||||
DefaultNavigatorOptions,
|
DefaultNavigatorOptions,
|
||||||
@@ -57,33 +60,40 @@ const getRouteConfigsFromChildren = <
|
|||||||
ScreenOptions extends {},
|
ScreenOptions extends {},
|
||||||
EventMap extends EventMapBase
|
EventMap extends EventMapBase
|
||||||
>(
|
>(
|
||||||
children: React.ReactNode
|
children: React.ReactNode,
|
||||||
|
options?: ScreenConfigWithParent<State, ScreenOptions, EventMap>[0]
|
||||||
) => {
|
) => {
|
||||||
const configs = React.Children.toArray(children).reduce<
|
const configs = React.Children.toArray(children).reduce<
|
||||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>[]
|
ScreenConfigWithParent<State, ScreenOptions, EventMap>[]
|
||||||
>((acc, child) => {
|
>((acc, child) => {
|
||||||
if (React.isValidElement(child)) {
|
if (React.isValidElement(child)) {
|
||||||
if (child.type === Screen) {
|
if (child.type === Screen) {
|
||||||
// We can only extract the config from `Screen` elements
|
// We can only extract the config from `Screen` elements
|
||||||
// If something else was rendered, it's probably a bug
|
// If something else was rendered, it's probably a bug
|
||||||
acc.push(
|
acc.push([
|
||||||
|
options,
|
||||||
child.props as RouteConfig<
|
child.props as RouteConfig<
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
string,
|
string,
|
||||||
State,
|
State,
|
||||||
ScreenOptions,
|
ScreenOptions,
|
||||||
EventMap
|
EventMap
|
||||||
>
|
>,
|
||||||
);
|
]);
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.type === React.Fragment) {
|
if (child.type === React.Fragment || child.type === Group) {
|
||||||
// When we encounter a fragment, we need to dive into its children to extract the configs
|
// When we encounter a fragment or group, we need to dive into its children to extract the configs
|
||||||
// This is handy to conditionally define a group of screens
|
// This is handy to conditionally define a group of screens
|
||||||
acc.push(
|
acc.push(
|
||||||
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
|
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
|
||||||
child.props.children
|
child.props.children,
|
||||||
|
child.type !== Group
|
||||||
|
? options
|
||||||
|
: options != null
|
||||||
|
? [...options, child.props.screenOptions]
|
||||||
|
: [child.props.screenOptions]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return acc;
|
return acc;
|
||||||
@@ -91,7 +101,7 @@ const getRouteConfigsFromChildren = <
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`A navigator can only contain 'Screen' components as its direct children (found ${
|
`A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found ${
|
||||||
React.isValidElement(child)
|
React.isValidElement(child)
|
||||||
? `'${
|
? `'${
|
||||||
typeof child.type === 'string' ? child.type : child.type?.name
|
typeof child.type === 'string' ? child.type : child.type?.name
|
||||||
@@ -107,7 +117,7 @@ const getRouteConfigsFromChildren = <
|
|||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
configs.forEach((config) => {
|
configs.forEach((config) => {
|
||||||
const { name, children, component, getComponent } = config;
|
const { name, children, component, getComponent } = config[1];
|
||||||
|
|
||||||
if (typeof name !== 'string' || !name) {
|
if (typeof name !== 'string' || !name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -220,25 +230,22 @@ export default function useNavigationBuilder<
|
|||||||
>(children);
|
>(children);
|
||||||
|
|
||||||
const screens = routeConfigs.reduce<
|
const screens = routeConfigs.reduce<
|
||||||
Record<
|
Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
|
||||||
string,
|
|
||||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
|
||||||
>
|
|
||||||
>((acc, config) => {
|
>((acc, config) => {
|
||||||
if (config.name in acc) {
|
if (config[1].name in acc) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.name}')`
|
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config[1].name}')`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[config.name] = config;
|
acc[config[1].name] = config;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const routeNames = routeConfigs.map((config) => config.name);
|
const routeNames = routeConfigs.map((config) => config[1].name);
|
||||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||||
(acc, curr) => {
|
(acc, curr) => {
|
||||||
const { initialParams } = screens[curr];
|
const { initialParams } = screens[curr][1];
|
||||||
const initialParamsFromParams =
|
const initialParamsFromParams =
|
||||||
route?.params?.state == null &&
|
route?.params?.state == null &&
|
||||||
route?.params?.initial !== false &&
|
route?.params?.initial !== false &&
|
||||||
@@ -263,7 +270,7 @@ export default function useNavigationBuilder<
|
|||||||
>(
|
>(
|
||||||
(acc, curr) =>
|
(acc, curr) =>
|
||||||
Object.assign(acc, {
|
Object.assign(acc, {
|
||||||
[curr]: screens[curr].getId,
|
[curr]: screens[curr][1].getId,
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
@@ -481,7 +488,7 @@ export default function useNavigationBuilder<
|
|||||||
const listeners = ([] as (((e: any) => void) | undefined)[])
|
const listeners = ([] as (((e: any) => void) | undefined)[])
|
||||||
.concat(
|
.concat(
|
||||||
...routeNames.map((name) => {
|
...routeNames.map((name) => {
|
||||||
const { listeners } = screens[name];
|
const { listeners } = screens[name][1];
|
||||||
const map =
|
const map =
|
||||||
typeof listeners === 'function'
|
typeof listeners === 'function'
|
||||||
? listeners({ route: route as any, navigation })
|
? listeners({ route: route as any, navigation })
|
||||||
@@ -581,9 +588,14 @@ export default function useNavigationBuilder<
|
|||||||
descriptors,
|
descriptors,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const NavigationContent = useComponent(NavigationHelpersContext.Provider, {
|
||||||
|
value: navigation,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
navigation,
|
navigation,
|
||||||
descriptors,
|
descriptors,
|
||||||
|
NavigationContent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
17
packages/core/src/useNavigationContainerRef.tsx
Normal file
17
packages/core/src/useNavigationContainerRef.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import createNavigationContainerRef from './createNavigationContainerRef';
|
||||||
|
import type { NavigationContainerRefWithCurrent } from './types';
|
||||||
|
|
||||||
|
export default function useNavigationContainerRef<
|
||||||
|
ParamList extends {} = ReactNavigation.RootParamList
|
||||||
|
>(): NavigationContainerRefWithCurrent<ParamList> {
|
||||||
|
const navigation = React.useRef<NavigationContainerRefWithCurrent<ParamList> | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (navigation.current == null) {
|
||||||
|
navigation.current = createNavigationContainerRef<ParamList>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigation.current;
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { ParamListBase, NavigationState } from '@react-navigation/routers';
|
import type { ParamListBase } from '@react-navigation/routers';
|
||||||
import NavigationStateContext from './NavigationStateContext';
|
import NavigationStateContext from './NavigationStateContext';
|
||||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||||
import type { NavigationProp } from './types';
|
import type { NavigationProp } from './types';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
key?: string;
|
key?: string;
|
||||||
navigation?: NavigationProp<ParamListBase, string, NavigationState, object>;
|
navigation?: NavigationProp<ParamListBase>;
|
||||||
options?: object | undefined;
|
options?: object | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import type { RouteProp } from './types';
|
|||||||
*
|
*
|
||||||
* @returns Route prop of the parent screen.
|
* @returns Route prop of the parent screen.
|
||||||
*/
|
*/
|
||||||
export default function useRoute<
|
export default function useRoute<T extends RouteProp<ParamListBase>>(): T {
|
||||||
T extends RouteProp<ParamListBase, string>
|
|
||||||
>(): T {
|
|
||||||
const route = React.useContext(NavigationRouteContext);
|
const route = React.useContext(NavigationRouteContext);
|
||||||
|
|
||||||
if (route === undefined) {
|
if (route === undefined) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type {
|
|||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import type { RouteProp } from './types';
|
import type { RouteProp } from './types';
|
||||||
|
|
||||||
type RouteCache = Map<Route<string>, RouteProp<ParamListBase, string>>;
|
type RouteCache = Map<Route<string>, RouteProp<ParamListBase>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilites such as `getFocusedRouteNameFromRoute` need to access state.
|
* Utilites such as `getFocusedRouteNameFromRoute` need to access state.
|
||||||
|
|||||||
@@ -3,6 +3,60 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.7...@react-navigation/devtools@6.0.0-next.8) (2021-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix type error when passing unannotated navigation ref ([dc4ffc0](https://github.com/react-navigation/react-navigation/commit/dc4ffc0171b4535fe1b6e839b9d54350121bcf55))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.6...@react-navigation/devtools@6.0.0-next.7) (2021-05-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.5...@react-navigation/devtools@6.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.4...@react-navigation/devtools@6.0.0-next.5) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.3...@react-navigation/devtools@6.0.0-next.4) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.2...@react-navigation/devtools@6.0.0-next.3) (2021-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 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/devtools@6.0.0-next.1...@react-navigation/devtools@6.0.0-next.2) (2021-04-08)
|
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.1...@react-navigation/devtools@6.0.0-next.2) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/devtools
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/devtools",
|
"name": "@react-navigation/devtools",
|
||||||
"description": "Developer tools for React Navigation",
|
"description": "Developer tools for React Navigation",
|
||||||
"version": "6.0.0-next.2",
|
"version": "6.0.0-next.8",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^6.0.0-next.2",
|
"@react-navigation/core": "^6.0.0-next.8",
|
||||||
"deep-equal": "^2.0.5"
|
"deep-equal": "^2.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type DevToolsExtension = {
|
|||||||
declare const __REDUX_DEVTOOLS_EXTENSION__: DevToolsExtension | undefined;
|
declare const __REDUX_DEVTOOLS_EXTENSION__: DevToolsExtension | undefined;
|
||||||
|
|
||||||
export default function useReduxDevToolsExtension(
|
export default function useReduxDevToolsExtension(
|
||||||
ref: React.RefObject<NavigationContainerRef>
|
ref: React.RefObject<NavigationContainerRef<any>>
|
||||||
) {
|
) {
|
||||||
const devToolsRef = React.useRef<DevToolsConnection>();
|
const devToolsRef = React.useRef<DevToolsConnection>();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,69 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.9...@react-navigation/drawer@6.0.0-next.10) (2021-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix drawer content padding in RTL ([ea8ea20](https://github.com/react-navigation/react-navigation/commit/ea8ea20127d979d8c8ddbddf56de1bdfdf0243f9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.8...@react-navigation/drawer@6.0.0-next.9) (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.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.7...@react-navigation/drawer@6.0.0-next.8) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.6...@react-navigation/drawer@6.0.0-next.7) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.5...@react-navigation/drawer@6.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.4...@react-navigation/drawer@6.0.0-next.5) (2021-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.3...@react-navigation/drawer@6.0.0-next.4) (2021-04-08)
|
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.3...@react-navigation/drawer@6.0.0-next.4) (2021-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/drawer",
|
"name": "@react-navigation/drawer",
|
||||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||||
"version": "6.0.0-next.4",
|
"version": "6.0.0-next.10",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -41,15 +41,15 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/elements": "^1.0.0-next.4",
|
"@react-navigation/elements": "^1.0.0-next.10",
|
||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"warn-once": "^0.0.1"
|
"warn-once": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-native": "~0.63.4",
|
"react-native": "~0.63.4",
|
||||||
|
|||||||
@@ -67,7 +67,12 @@ function DrawerNavigator({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const {
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
navigation,
|
||||||
|
NavigationContent,
|
||||||
|
} = useNavigationBuilder<
|
||||||
DrawerNavigationState<ParamListBase>,
|
DrawerNavigationState<ParamListBase>,
|
||||||
DrawerRouterOptions,
|
DrawerRouterOptions,
|
||||||
DrawerActionHelpers<ParamListBase>,
|
DrawerActionHelpers<ParamListBase>,
|
||||||
@@ -83,12 +88,14 @@ function DrawerNavigator({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerView
|
<NavigationContent>
|
||||||
{...rest}
|
<DrawerView
|
||||||
state={state}
|
{...rest}
|
||||||
descriptors={descriptors}
|
state={state}
|
||||||
navigation={navigation}
|
descriptors={descriptors}
|
||||||
/>
|
navigation={navigation}
|
||||||
|
/>
|
||||||
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ export type DrawerHeaderProps = {
|
|||||||
/**
|
/**
|
||||||
* Route object for the current screen.
|
* Route object for the current screen.
|
||||||
*/
|
*/
|
||||||
route: RouteProp<ParamListBase, string>;
|
route: RouteProp<ParamListBase>;
|
||||||
/**
|
/**
|
||||||
* Navigation prop for the header.
|
* Navigation prop for the header.
|
||||||
*/
|
*/
|
||||||
@@ -252,7 +252,7 @@ export type DrawerNavigationHelpers = NavigationHelpers<
|
|||||||
|
|
||||||
export type DrawerNavigationProp<
|
export type DrawerNavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = NavigationProp<
|
> = NavigationProp<
|
||||||
ParamList,
|
ParamList,
|
||||||
RouteName,
|
RouteName,
|
||||||
@@ -264,7 +264,7 @@ export type DrawerNavigationProp<
|
|||||||
|
|
||||||
export type DrawerScreenProps<
|
export type DrawerScreenProps<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = {
|
> = {
|
||||||
navigation: DrawerNavigationProp<ParamList, RouteName>;
|
navigation: DrawerNavigationProp<ParamList, RouteName>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
route: RouteProp<ParamList, RouteName>;
|
||||||
@@ -273,7 +273,7 @@ export type DrawerScreenProps<
|
|||||||
export type DrawerDescriptor = Descriptor<
|
export type DrawerDescriptor = Descriptor<
|
||||||
DrawerNavigationOptions,
|
DrawerNavigationOptions,
|
||||||
DrawerNavigationProp<ParamListBase>,
|
DrawerNavigationProp<ParamListBase>,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type DrawerDescriptorMap = Record<string, DrawerDescriptor>;
|
export type DrawerDescriptorMap = Record<string, DrawerDescriptor>;
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ScrollView, StyleSheet, ScrollViewProps } from 'react-native';
|
import {
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollViewProps,
|
||||||
|
I18nManager,
|
||||||
|
} from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||||
|
|
||||||
@@ -16,14 +21,18 @@ export default function DrawerContentScrollView({
|
|||||||
const drawerPosition = React.useContext(DrawerPositionContext);
|
const drawerPosition = React.useContext(DrawerPositionContext);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
|
const isRight = I18nManager.isRTL
|
||||||
|
? drawerPosition === 'left'
|
||||||
|
: drawerPosition === 'right';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
{...rest}
|
{...rest}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
{
|
{
|
||||||
paddingTop: insets.top + 4,
|
paddingTop: insets.top + 4,
|
||||||
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
|
paddingStart: !isRight ? insets.left : 0,
|
||||||
paddingRight: drawerPosition === 'right' ? insets.right : 0,
|
paddingEnd: isRight ? insets.right : 0,
|
||||||
},
|
},
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
BackHandler,
|
BackHandler,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { ScreenContainer } from 'react-native-screens';
|
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated from 'react-native-reanimated';
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
|
||||||
DrawerNavigationState,
|
DrawerNavigationState,
|
||||||
DrawerActions,
|
DrawerActions,
|
||||||
useTheme,
|
useTheme,
|
||||||
@@ -22,9 +20,8 @@ import {
|
|||||||
SafeAreaProviderCompat,
|
SafeAreaProviderCompat,
|
||||||
getHeaderTitle,
|
getHeaderTitle,
|
||||||
} from '@react-navigation/elements';
|
} from '@react-navigation/elements';
|
||||||
|
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
|
||||||
import { GestureHandlerRootView } from './GestureHandler';
|
import { GestureHandlerRootView } from './GestureHandler';
|
||||||
import ScreenFallback from './ScreenFallback';
|
|
||||||
import DrawerToggleButton from './DrawerToggleButton';
|
import DrawerToggleButton from './DrawerToggleButton';
|
||||||
import DrawerContent from './DrawerContent';
|
import DrawerContent from './DrawerContent';
|
||||||
import DrawerStatusContext from '../utils/DrawerStatusContext';
|
import DrawerStatusContext from '../utils/DrawerStatusContext';
|
||||||
@@ -76,7 +73,9 @@ function DrawerViewBase({
|
|||||||
drawerContent = (props: DrawerContentComponentProps) => (
|
drawerContent = (props: DrawerContentComponentProps) => (
|
||||||
<DrawerContent {...props} />
|
<DrawerContent {...props} />
|
||||||
),
|
),
|
||||||
detachInactiveScreens = true,
|
detachInactiveScreens = Platform.OS === 'web' ||
|
||||||
|
Platform.OS === 'android' ||
|
||||||
|
Platform.OS === 'ios',
|
||||||
// Running in chrome debugger
|
// Running in chrome debugger
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
useLegacyImplementation = !global.nativeCallSyncHook ||
|
useLegacyImplementation = !global.nativeCallSyncHook ||
|
||||||
@@ -189,8 +188,10 @@ function DrawerViewBase({
|
|||||||
|
|
||||||
const renderSceneContent = () => {
|
const renderSceneContent = () => {
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
<MaybeScreenContainer
|
||||||
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
|
enabled={detachInactiveScreens}
|
||||||
|
style={styles.content}
|
||||||
|
>
|
||||||
{state.routes.map((route, index) => {
|
{state.routes.map((route, index) => {
|
||||||
const descriptor = descriptors[route.key];
|
const descriptor = descriptors[route.key];
|
||||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||||
@@ -221,7 +222,7 @@ function DrawerViewBase({
|
|||||||
} = descriptor.options;
|
} = descriptor.options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenFallback
|
<MaybeScreen
|
||||||
key={route.key}
|
key={route.key}
|
||||||
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
|
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
|
||||||
visible={isFocused}
|
visible={isFocused}
|
||||||
@@ -243,10 +244,10 @@ function DrawerViewBase({
|
|||||||
>
|
>
|
||||||
{descriptor.render()}
|
{descriptor.render()}
|
||||||
</Screen>
|
</Screen>
|
||||||
</ScreenFallback>
|
</MaybeScreen>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ScreenContainer>
|
</MaybeScreenContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -294,13 +295,11 @@ function DrawerViewBase({
|
|||||||
|
|
||||||
export default function DrawerView({ navigation, ...rest }: Props) {
|
export default function DrawerView({ navigation, ...rest }: Props) {
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<SafeAreaProviderCompat>
|
||||||
<SafeAreaProviderCompat>
|
<GestureHandlerWrapper style={styles.content}>
|
||||||
<GestureHandlerWrapper style={styles.content}>
|
<DrawerViewBase navigation={navigation} {...rest} />
|
||||||
<DrawerViewBase navigation={navigation} {...rest} />
|
</GestureHandlerWrapper>
|
||||||
</GestureHandlerWrapper>
|
</SafeAreaProviderCompat>
|
||||||
</SafeAreaProviderCompat>
|
|
||||||
</NavigationHelpersContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform, StyleProp, ViewStyle } from 'react-native';
|
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
||||||
import {
|
|
||||||
Screen,
|
|
||||||
screensEnabled,
|
|
||||||
// @ts-ignore
|
|
||||||
shouldUseActivityState,
|
|
||||||
} from 'react-native-screens';
|
|
||||||
import { ResourceSavingView } from '@react-navigation/elements';
|
import { ResourceSavingView } from '@react-navigation/elements';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -15,22 +9,35 @@ type Props = {
|
|||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ScreenFallback({ visible, children, ...rest }: Props) {
|
let Screens: typeof import('react-native-screens') | undefined;
|
||||||
// react-native-screens is buggy on web
|
|
||||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
try {
|
||||||
if (shouldUseActivityState) {
|
Screens = require('react-native-screens');
|
||||||
return (
|
} catch (e) {
|
||||||
<Screen activityState={visible ? 2 : 0} {...rest}>
|
// Ignore
|
||||||
{children}
|
}
|
||||||
</Screen>
|
|
||||||
);
|
export const MaybeScreenContainer = ({
|
||||||
} else {
|
enabled,
|
||||||
return (
|
...rest
|
||||||
<Screen active={visible ? 1 : 0} {...rest}>
|
}: ViewProps & {
|
||||||
{children}
|
enabled: boolean;
|
||||||
</Screen>
|
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 (
|
return (
|
||||||
|
|||||||
@@ -3,6 +3,60 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.0.0-next.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.9...@react-navigation/elements@1.0.0-next.10) (2021-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix drawer content padding in RTL ([ea8ea20](https://github.com/react-navigation/react-navigation/commit/ea8ea20127d979d8c8ddbddf56de1bdfdf0243f9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.8...@react-navigation/elements@1.0.0-next.9) (2021-05-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/elements
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.7...@react-navigation/elements@1.0.0-next.8) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/elements
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.6...@react-navigation/elements@1.0.0-next.7) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/elements
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.5...@react-navigation/elements@1.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* animate pressable opacity ([459fd27](https://github.com/react-navigation/react-navigation/commit/459fd270503075343b71ad446efdc2517eedcf21))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.4...@react-navigation/elements@1.0.0-next.5) (2021-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/elements
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.3...@react-navigation/elements@1.0.0-next.4) (2021-04-08)
|
# [1.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.3...@react-navigation/elements@1.0.0-next.4) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/elements
|
**Note:** Version bump only for package @react-navigation/elements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/elements",
|
"name": "@react-navigation/elements",
|
||||||
"description": "UI Components for React Navigation",
|
"description": "UI Components for React Navigation",
|
||||||
"version": "1.0.0-next.4",
|
"version": "1.0.0-next.10",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation",
|
"react-navigation",
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-masked-view/masked-view": "^0.2.3",
|
"@react-native-masked-view/masked-view": "^0.2.3",
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-native": "~0.63.4",
|
"react-native": "~0.63.4",
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export default function Header(props: Props) {
|
|||||||
style={[
|
style={[
|
||||||
styles.left,
|
styles.left,
|
||||||
headerTitleAlign === 'center' && styles.expand,
|
headerTitleAlign === 'center' && styles.expand,
|
||||||
{ marginLeft: insets.left },
|
{ marginStart: insets.left },
|
||||||
leftContainerStyle,
|
leftContainerStyle,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -236,7 +236,7 @@ export default function Header(props: Props) {
|
|||||||
style={[
|
style={[
|
||||||
styles.right,
|
styles.right,
|
||||||
styles.expand,
|
styles.expand,
|
||||||
{ marginRight: insets.right },
|
{ marginEnd: insets.right },
|
||||||
rightContainerStyle,
|
rightContainerStyle,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform, Pressable, PressableProps } from 'react-native';
|
import {
|
||||||
|
Animated,
|
||||||
|
Easing,
|
||||||
|
GestureResponderEvent,
|
||||||
|
Platform,
|
||||||
|
Pressable,
|
||||||
|
PressableProps,
|
||||||
|
StyleProp,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native';
|
||||||
import { useTheme } from '@react-navigation/native';
|
import { useTheme } from '@react-navigation/native';
|
||||||
|
|
||||||
export type Props = PressableProps & {
|
export type Props = Omit<PressableProps, 'style'> & {
|
||||||
pressColor?: string;
|
pressColor?: string;
|
||||||
pressOpacity?: number;
|
pressOpacity?: number;
|
||||||
|
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
||||||
|
|
||||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||||
const ANDROID_SUPPORTS_RIPPLE =
|
const ANDROID_SUPPORTS_RIPPLE =
|
||||||
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
|
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
|
||||||
@@ -16,16 +28,44 @@ const ANDROID_SUPPORTS_RIPPLE =
|
|||||||
* PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
|
* PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
|
||||||
*/
|
*/
|
||||||
export default function PlatformPressable({
|
export default function PlatformPressable({
|
||||||
|
onPressIn,
|
||||||
|
onPressOut,
|
||||||
android_ripple,
|
android_ripple,
|
||||||
pressColor,
|
pressColor,
|
||||||
pressOpacity,
|
pressOpacity = 0.3,
|
||||||
style,
|
style,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { dark } = useTheme();
|
const { dark } = useTheme();
|
||||||
|
const [opacity] = React.useState(() => new Animated.Value(1));
|
||||||
|
|
||||||
|
const animateTo = (toValue: number, duration: number) => {
|
||||||
|
if (ANDROID_SUPPORTS_RIPPLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Animated.timing(opacity, {
|
||||||
|
toValue,
|
||||||
|
duration,
|
||||||
|
easing: Easing.inOut(Easing.quad),
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePressIn = (e: GestureResponderEvent) => {
|
||||||
|
animateTo(pressOpacity, 150);
|
||||||
|
onPressIn?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePressOut = (e: GestureResponderEvent) => {
|
||||||
|
animateTo(1, 200);
|
||||||
|
onPressOut?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<AnimatedPressable
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
android_ripple={
|
android_ripple={
|
||||||
ANDROID_SUPPORTS_RIPPLE
|
ANDROID_SUPPORTS_RIPPLE
|
||||||
? {
|
? {
|
||||||
@@ -39,10 +79,7 @@ export default function PlatformPressable({
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
style={({ pressed }) => [
|
style={[{ opacity: !ANDROID_SUPPORTS_RIPPLE ? opacity : 1 }, style]}
|
||||||
{ opacity: pressed && !ANDROID_SUPPORTS_RIPPLE ? pressOpacity : 1 },
|
|
||||||
typeof style === 'function' ? style({ pressed }) : style,
|
|
||||||
]}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import getDefaultHeaderHeight from './Header/getDefaultHeaderHeight';
|
|||||||
type Props = {
|
type Props = {
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
navigation: NavigationProp<ParamListBase>;
|
navigation: NavigationProp<ParamListBase>;
|
||||||
route: RouteProp<ParamListBase, string>;
|
route: RouteProp<ParamListBase>;
|
||||||
header: React.ReactNode;
|
header: React.ReactNode;
|
||||||
headerShown?: boolean;
|
headerShown?: boolean;
|
||||||
headerStatusBarHeight?: number;
|
headerStatusBarHeight?: number;
|
||||||
|
|||||||
@@ -3,6 +3,57 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.7...@react-navigation/material-bottom-tabs@6.0.0-next.8) (2021-05-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.6...@react-navigation/material-bottom-tabs@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/material-bottom-tabs@6.0.0-next.5...@react-navigation/material-bottom-tabs@6.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.4...@react-navigation/material-bottom-tabs@6.0.0-next.5) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.3...@react-navigation/material-bottom-tabs@6.0.0-next.4) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.2...@react-navigation/material-bottom-tabs@6.0.0-next.3) (2021-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.1...@react-navigation/material-bottom-tabs@6.0.0-next.2) (2021-04-08)
|
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.1...@react-navigation/material-bottom-tabs@6.0.0-next.2) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-bottom-tabs",
|
"name": "@react-navigation/material-bottom-tabs",
|
||||||
"description": "Integration for bottom navigation component from react-native-paper",
|
"description": "Integration for bottom navigation component from react-native-paper",
|
||||||
"version": "6.0.0-next.2",
|
"version": "6.0.0-next.8",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -41,10 +41,10 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"@types/react-native-vector-icons": "^6.4.6",
|
"@types/react-native-vector-icons": "^6.4.6",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ function MaterialBottomTabNavigator({
|
|||||||
screenOptions,
|
screenOptions,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const {
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
navigation,
|
||||||
|
NavigationContent,
|
||||||
|
} = useNavigationBuilder<
|
||||||
TabNavigationState<ParamListBase>,
|
TabNavigationState<ParamListBase>,
|
||||||
TabRouterOptions,
|
TabRouterOptions,
|
||||||
TabActionHelpers<ParamListBase>,
|
TabActionHelpers<ParamListBase>,
|
||||||
@@ -42,12 +47,14 @@ function MaterialBottomTabNavigator({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaterialBottomTabView
|
<NavigationContent>
|
||||||
{...rest}
|
<MaterialBottomTabView
|
||||||
state={state}
|
{...rest}
|
||||||
navigation={navigation}
|
state={state}
|
||||||
descriptors={descriptors}
|
navigation={navigation}
|
||||||
/>
|
descriptors={descriptors}
|
||||||
|
/>
|
||||||
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type MaterialBottomTabNavigationHelpers = NavigationHelpers<
|
|||||||
|
|
||||||
export type MaterialBottomTabNavigationProp<
|
export type MaterialBottomTabNavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = NavigationProp<
|
> = NavigationProp<
|
||||||
ParamList,
|
ParamList,
|
||||||
RouteName,
|
RouteName,
|
||||||
@@ -36,7 +36,7 @@ export type MaterialBottomTabNavigationProp<
|
|||||||
|
|
||||||
export type MaterialBottomTabScreenProps<
|
export type MaterialBottomTabScreenProps<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = {
|
> = {
|
||||||
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
|
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
route: RouteProp<ParamList, RouteName>;
|
||||||
@@ -85,7 +85,7 @@ export type MaterialBottomTabNavigationOptions = {
|
|||||||
export type MaterialBottomTabDescriptor = Descriptor<
|
export type MaterialBottomTabDescriptor = Descriptor<
|
||||||
MaterialBottomTabNavigationOptions,
|
MaterialBottomTabNavigationOptions,
|
||||||
MaterialBottomTabNavigationProp<ParamListBase>,
|
MaterialBottomTabNavigationProp<ParamListBase>,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type MaterialBottomTabDescriptorMap = Record<
|
export type MaterialBottomTabDescriptorMap = Record<
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as React from 'react';
|
|||||||
import { Text, StyleSheet, Platform } from 'react-native';
|
import { Text, StyleSheet, Platform } from 'react-native';
|
||||||
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
|
||||||
Route,
|
Route,
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
TabActions,
|
TabActions,
|
||||||
@@ -75,7 +74,7 @@ try {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function MaterialBottomTabViewInner({
|
export default function MaterialBottomTabView({
|
||||||
state,
|
state,
|
||||||
navigation,
|
navigation,
|
||||||
descriptors,
|
descriptors,
|
||||||
@@ -192,14 +191,6 @@ function MaterialBottomTabViewInner({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MaterialBottomTabView(props: Props) {
|
|
||||||
return (
|
|
||||||
<NavigationHelpersContext.Provider value={props.navigation}>
|
|
||||||
<MaterialBottomTabViewInner {...props} />
|
|
||||||
</NavigationHelpersContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
icon: {
|
icon: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
|||||||
@@ -3,6 +3,62 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.8...@react-navigation/material-top-tabs@6.0.0-next.9) (2021-05-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.7...@react-navigation/material-top-tabs@6.0.0-next.8) (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.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.6...@react-navigation/material-top-tabs@6.0.0-next.7) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.5...@react-navigation/material-top-tabs@6.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.4...@react-navigation/material-top-tabs@6.0.0-next.5) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.3...@react-navigation/material-top-tabs@6.0.0-next.4) (2021-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.2...@react-navigation/material-top-tabs@6.0.0-next.3) (2021-04-08)
|
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.2...@react-navigation/material-top-tabs@6.0.0-next.3) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-top-tabs",
|
"name": "@react-navigation/material-top-tabs",
|
||||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||||
"version": "6.0.0-next.3",
|
"version": "6.0.0-next.9",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -42,13 +42,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"warn-once": "^0.0.1"
|
"warn-once": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-native": "~0.63.4",
|
"react-native": "~0.63.4",
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ function MaterialTopTabNavigator({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const {
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
navigation,
|
||||||
|
NavigationContent,
|
||||||
|
} = useNavigationBuilder<
|
||||||
TabNavigationState<ParamListBase>,
|
TabNavigationState<ParamListBase>,
|
||||||
TabRouterOptions,
|
TabRouterOptions,
|
||||||
TabActionHelpers<ParamListBase>,
|
TabActionHelpers<ParamListBase>,
|
||||||
@@ -85,12 +90,14 @@ function MaterialTopTabNavigator({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaterialTopTabView
|
<NavigationContent>
|
||||||
{...rest}
|
<MaterialTopTabView
|
||||||
state={state}
|
{...rest}
|
||||||
navigation={navigation}
|
state={state}
|
||||||
descriptors={descriptors}
|
navigation={navigation}
|
||||||
/>
|
descriptors={descriptors}
|
||||||
|
/>
|
||||||
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export type MaterialTopTabNavigationHelpers = NavigationHelpers<
|
|||||||
|
|
||||||
export type MaterialTopTabNavigationProp<
|
export type MaterialTopTabNavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = NavigationProp<
|
> = NavigationProp<
|
||||||
ParamList,
|
ParamList,
|
||||||
RouteName,
|
RouteName,
|
||||||
@@ -50,7 +50,7 @@ export type MaterialTopTabNavigationProp<
|
|||||||
|
|
||||||
export type MaterialTopTabScreenProps<
|
export type MaterialTopTabScreenProps<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = {
|
> = {
|
||||||
navigation: MaterialTopTabNavigationProp<ParamList, RouteName>;
|
navigation: MaterialTopTabNavigationProp<ParamList, RouteName>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
route: RouteProp<ParamList, RouteName>;
|
||||||
@@ -189,7 +189,7 @@ export type MaterialTopTabNavigationOptions = {
|
|||||||
export type MaterialTopTabDescriptor = Descriptor<
|
export type MaterialTopTabDescriptor = Descriptor<
|
||||||
MaterialTopTabNavigationOptions,
|
MaterialTopTabNavigationOptions,
|
||||||
MaterialTopTabNavigationProp<ParamListBase>,
|
MaterialTopTabNavigationProp<ParamListBase>,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type MaterialTopTabDescriptorMap = Record<
|
export type MaterialTopTabDescriptorMap = Record<
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TabView, SceneRendererProps } from 'react-native-tab-view';
|
import { TabView, SceneRendererProps } from 'react-native-tab-view';
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
TabActions,
|
TabActions,
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
@@ -43,29 +42,27 @@ export default function MaterialTopTabView({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<TabView<Route<string>>
|
||||||
<TabView<Route<string>>
|
{...rest}
|
||||||
{...rest}
|
onIndexChange={(index) =>
|
||||||
onIndexChange={(index) =>
|
navigation.dispatch({
|
||||||
navigation.dispatch({
|
...TabActions.jumpTo(state.routes[index].name),
|
||||||
...TabActions.jumpTo(state.routes[index].name),
|
target: state.key,
|
||||||
target: state.key,
|
})
|
||||||
})
|
}
|
||||||
}
|
renderScene={({ route }) => descriptors[route.key].render()}
|
||||||
renderScene={({ route }) => descriptors[route.key].render()}
|
navigationState={state}
|
||||||
navigationState={state}
|
renderTabBar={renderTabBar}
|
||||||
renderTabBar={renderTabBar}
|
renderLazyPlaceholder={({ route }) =>
|
||||||
renderLazyPlaceholder={({ route }) =>
|
descriptors[route.key].options.lazyPlaceholder?.() ?? null
|
||||||
descriptors[route.key].options.lazyPlaceholder?.() ?? null
|
}
|
||||||
}
|
lazy={({ route }) => descriptors[route.key].options.lazy === true}
|
||||||
lazy={({ route }) => descriptors[route.key].options.lazy === true}
|
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
sceneContainerStyle={[
|
||||||
sceneContainerStyle={[
|
{ backgroundColor: colors.background },
|
||||||
{ backgroundColor: colors.background },
|
sceneContainerStyle,
|
||||||
sceneContainerStyle,
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
|
||||||
</NavigationHelpersContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,67 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.7...@react-navigation/native@6.0.0-next.8) (2021-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add ability to pass generic params to Link ([9c30c42](https://github.com/react-navigation/react-navigation/commit/9c30c42c0bddbc90c58b79a8be6d57e57a131e77))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.6...@react-navigation/native@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/native@6.0.0-next.5...@react-navigation/native@6.0.0-next.6) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.4...@react-navigation/native@6.0.0-next.5) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.3...@react-navigation/native@6.0.0-next.4) (2021-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add ability to specify root param list ([b28bfdd](https://github.com/react-navigation/react-navigation/commit/b28bfddc17cbf3996fac04a34b2a7085ecf88be5))
|
||||||
|
* support navigate-like object in Link ([1478659](https://github.com/react-navigation/react-navigation/commit/14786594c004d8176570f1a4ab013b57b3180665))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.2...@react-navigation/native@6.0.0-next.3) (2021-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 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/native@6.0.0-next.1...@react-navigation/native@6.0.0-next.2) (2021-04-08)
|
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.1...@react-navigation/native@6.0.0-next.2) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/native
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/native",
|
"name": "@react-navigation/native",
|
||||||
"description": "React Native integration for React Navigation",
|
"description": "React Native integration for React Navigation",
|
||||||
"version": "6.0.0-next.2",
|
"version": "6.0.0-next.8",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation",
|
"react-navigation",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^6.0.0-next.2",
|
"@react-navigation/core": "^6.0.0-next.8",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"nanoid": "^3.1.22"
|
"nanoid": "^3.1.22"
|
||||||
},
|
},
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import * as React from 'react';
|
|||||||
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
|
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
|
||||||
import type { NavigationAction } from '@react-navigation/core';
|
import type { NavigationAction } from '@react-navigation/core';
|
||||||
import useLinkProps from './useLinkProps';
|
import useLinkProps from './useLinkProps';
|
||||||
|
import type { To } from './useLinkTo';
|
||||||
|
|
||||||
type Props = {
|
type Props<ParamList extends ReactNavigation.RootParamList> = {
|
||||||
to: string;
|
to: To<ParamList>;
|
||||||
action?: NavigationAction;
|
action?: NavigationAction;
|
||||||
target?: string;
|
target?: string;
|
||||||
onPress?: (
|
onPress?: (
|
||||||
@@ -20,8 +21,12 @@ type Props = {
|
|||||||
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
||||||
* @param props.children Child elements to render the content.
|
* @param props.children Child elements to render the content.
|
||||||
*/
|
*/
|
||||||
export default function Link({ to, action, ...rest }: Props) {
|
export default function Link<ParamList extends ReactNavigation.RootParamList>({
|
||||||
const props = useLinkProps({ to, action });
|
to,
|
||||||
|
action,
|
||||||
|
...rest
|
||||||
|
}: Props<ParamList>) {
|
||||||
|
const props = useLinkProps<ParamList>({ to, action });
|
||||||
|
|
||||||
const onPress = (
|
const onPress = (
|
||||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { ParamListBase } from '@react-navigation/core';
|
||||||
import type { LinkingOptions } from './types';
|
import type { LinkingOptions } from './types';
|
||||||
|
|
||||||
const LinkingContext = React.createContext<{
|
const LinkingContext = React.createContext<{
|
||||||
options: LinkingOptions | undefined;
|
options: LinkingOptions<ParamListBase> | undefined;
|
||||||
}>({ options: undefined });
|
}>({ options: undefined });
|
||||||
|
|
||||||
LinkingContext.displayName = 'LinkingContext';
|
LinkingContext.displayName = 'LinkingContext';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
BaseNavigationContainer,
|
BaseNavigationContainer,
|
||||||
NavigationContainerProps,
|
NavigationContainerProps,
|
||||||
NavigationContainerRef,
|
NavigationContainerRef,
|
||||||
|
ParamListBase,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import ThemeProvider from './theming/ThemeProvider';
|
import ThemeProvider from './theming/ThemeProvider';
|
||||||
import DefaultTheme from './theming/DefaultTheme';
|
import DefaultTheme from './theming/DefaultTheme';
|
||||||
@@ -13,9 +14,9 @@ import useDocumentTitle from './useDocumentTitle';
|
|||||||
import useBackButton from './useBackButton';
|
import useBackButton from './useBackButton';
|
||||||
import type { Theme, LinkingOptions, DocumentTitleOptions } from './types';
|
import type { Theme, LinkingOptions, DocumentTitleOptions } from './types';
|
||||||
|
|
||||||
type Props = NavigationContainerProps & {
|
type Props<ParamList extends {}> = NavigationContainerProps & {
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
linking?: LinkingOptions;
|
linking?: LinkingOptions<ParamList>;
|
||||||
fallback?: React.ReactNode;
|
fallback?: React.ReactNode;
|
||||||
documentTitle?: DocumentTitleOptions;
|
documentTitle?: DocumentTitleOptions;
|
||||||
onReady?: () => void;
|
onReady?: () => void;
|
||||||
@@ -35,7 +36,7 @@ type Props = NavigationContainerProps & {
|
|||||||
* @param props.children Child elements to render the content.
|
* @param props.children Child elements to render the content.
|
||||||
* @param props.ref Ref object which refers to the navigation object containing helper methods.
|
* @param props.ref Ref object which refers to the navigation object containing helper methods.
|
||||||
*/
|
*/
|
||||||
const NavigationContainer = React.forwardRef(function NavigationContainer(
|
function NavigationContainerInner(
|
||||||
{
|
{
|
||||||
theme = DefaultTheme,
|
theme = DefaultTheme,
|
||||||
linking,
|
linking,
|
||||||
@@ -43,12 +44,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
|
|||||||
documentTitle,
|
documentTitle,
|
||||||
onReady,
|
onReady,
|
||||||
...rest
|
...rest
|
||||||
}: Props,
|
}: Props<ParamListBase>,
|
||||||
ref?: React.Ref<NavigationContainerRef | null>
|
ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
|
||||||
) {
|
) {
|
||||||
const isLinkingEnabled = linking ? linking.enabled !== false : false;
|
const isLinkingEnabled = linking ? linking.enabled !== false : false;
|
||||||
|
|
||||||
const refContainer = React.useRef<NavigationContainerRef>(null);
|
const refContainer = React.useRef<NavigationContainerRef<ParamListBase>>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
useBackButton(refContainer);
|
useBackButton(refContainer);
|
||||||
useDocumentTitle(refContainer, documentTitle);
|
useDocumentTitle(refContainer, documentTitle);
|
||||||
@@ -98,6 +101,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</LinkingContext.Provider>
|
</LinkingContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const NavigationContainer = React.forwardRef(NavigationContainerInner) as <
|
||||||
|
RootParamList extends {} = ReactNavigation.RootParamList
|
||||||
|
>(
|
||||||
|
props: Props<RootParamList> & {
|
||||||
|
ref?: React.Ref<NavigationContainerRef<RootParamList>>;
|
||||||
|
}
|
||||||
|
) => React.ReactElement;
|
||||||
|
|
||||||
export default NavigationContainer;
|
export default NavigationContainer;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
TabRouter,
|
TabRouter,
|
||||||
NavigationHelpersContext,
|
createNavigationContainerRef,
|
||||||
NavigationContainerRef,
|
ParamListBase,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import { act, render } from '@testing-library/react-native';
|
import { act, render } from '@testing-library/react-native';
|
||||||
import NavigationContainer from '../NavigationContainer';
|
import NavigationContainer from '../NavigationContainer';
|
||||||
@@ -21,36 +21,36 @@ it('integrates with the history API', () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const createStackNavigator = createNavigatorFactory((props: any) => {
|
const createStackNavigator = createNavigatorFactory((props: any) => {
|
||||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||||
StackRouter,
|
StackRouter,
|
||||||
props
|
props
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationContent>
|
||||||
{state.routes.map((route, i) => (
|
{state.routes.map((route, i) => (
|
||||||
<div key={route.key} aria-current={state.index === i || undefined}>
|
<div key={route.key} aria-current={state.index === i || undefined}>
|
||||||
{descriptors[route.key].render()}
|
{descriptors[route.key].render()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</NavigationHelpersContext.Provider>
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const createTabNavigator = createNavigatorFactory((props: any) => {
|
const createTabNavigator = createNavigatorFactory((props: any) => {
|
||||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||||
TabRouter,
|
TabRouter,
|
||||||
props
|
props
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationContent>
|
||||||
{state.routes.map((route, i) => (
|
{state.routes.map((route, i) => (
|
||||||
<div key={route.key} aria-current={state.index === i || undefined}>
|
<div key={route.key} aria-current={state.index === i || undefined}>
|
||||||
{descriptors[route.key].render()}
|
{descriptors[route.key].render()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</NavigationHelpersContext.Provider>
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ it('integrates with the history API', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = React.createRef<NavigationContainerRef>();
|
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<NavigationContainer ref={navigation} linking={linking}>
|
<NavigationContainer ref={navigation} linking={linking}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
TabRouter,
|
TabRouter,
|
||||||
NavigationHelpersContext,
|
NavigatorScreenParams,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
import NavigationContainer from '../NavigationContainer';
|
import NavigationContainer from '../NavigationContainer';
|
||||||
@@ -22,38 +22,51 @@ jest.mock('../useLinking', () => require('../useLinking.tsx').default);
|
|||||||
|
|
||||||
it('renders correct state with location', () => {
|
it('renders correct state with location', () => {
|
||||||
const createStackNavigator = createNavigatorFactory((props: any) => {
|
const createStackNavigator = createNavigatorFactory((props: any) => {
|
||||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||||
StackRouter,
|
StackRouter,
|
||||||
props
|
props
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationContent>
|
||||||
{state.routes.map((route) => (
|
{state.routes.map((route) => (
|
||||||
<div key={route.key}>{descriptors[route.key].render()}</div>
|
<div key={route.key}>{descriptors[route.key].render()}</div>
|
||||||
))}
|
))}
|
||||||
</NavigationHelpersContext.Provider>
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
type StackAParamList = {
|
||||||
|
Home: NavigatorScreenParams<StackBParamList>;
|
||||||
|
Chat: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StackBParamList = {
|
||||||
|
Profile: undefined;
|
||||||
|
Settings: undefined;
|
||||||
|
Feed: undefined;
|
||||||
|
Updates: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StackA = createStackNavigator<StackAParamList>();
|
||||||
|
const StackB = createStackNavigator<StackBParamList>();
|
||||||
|
|
||||||
const TestScreen = ({ route }: any): any =>
|
const TestScreen = ({ route }: any): any =>
|
||||||
`${route.name} ${JSON.stringify(route.params)}`;
|
`${route.name} ${JSON.stringify(route.params)}`;
|
||||||
|
|
||||||
const NestedStack = () => {
|
const NestedStack = () => {
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator initialRouteName="Feed">
|
<StackB.Navigator initialRouteName="Feed">
|
||||||
<Stack.Screen name="Profile" component={TestScreen} />
|
<StackB.Screen name="Profile" component={TestScreen} />
|
||||||
<Stack.Screen name="Settings" component={TestScreen} />
|
<StackB.Screen name="Settings" component={TestScreen} />
|
||||||
<Stack.Screen name="Feed" component={TestScreen} />
|
<StackB.Screen name="Feed" component={TestScreen} />
|
||||||
<Stack.Screen name="Updates" component={TestScreen} />
|
<StackB.Screen name="Updates" component={TestScreen} />
|
||||||
</Stack.Navigator>
|
</StackB.Navigator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<NavigationContainer
|
<NavigationContainer<StackAParamList>
|
||||||
linking={{
|
linking={{
|
||||||
prefixes: [],
|
prefixes: [],
|
||||||
config: {
|
config: {
|
||||||
@@ -73,10 +86,10 @@ it('renders correct state with location', () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack.Navigator>
|
<StackA.Navigator>
|
||||||
<Stack.Screen name="Home" component={NestedStack} />
|
<StackA.Screen name="Home" component={NestedStack} />
|
||||||
<Stack.Screen name="Chat" component={TestScreen} />
|
<StackA.Screen name="Chat" component={TestScreen} />
|
||||||
</Stack.Navigator>
|
</StackA.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,17 +115,17 @@ it('renders correct state with location', () => {
|
|||||||
|
|
||||||
it('gets the current options', () => {
|
it('gets the current options', () => {
|
||||||
const createTabNavigator = createNavigatorFactory((props: any) => {
|
const createTabNavigator = createNavigatorFactory((props: any) => {
|
||||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||||
TabRouter,
|
TabRouter,
|
||||||
props
|
props
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationContent>
|
||||||
{state.routes.map((route) => (
|
{state.routes.map((route) => (
|
||||||
<div key={route.key}>{descriptors[route.key].render()}</div>
|
<div key={route.key}>{descriptors[route.key].render()}</div>
|
||||||
))}
|
))}
|
||||||
</NavigationHelpersContext.Provider>
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { render, RenderAPI } from '@testing-library/react-native';
|
import { render, RenderAPI } from '@testing-library/react-native';
|
||||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
import {
|
||||||
|
createNavigationContainerRef,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/core';
|
||||||
import useLinking from '../useLinking';
|
import useLinking from '../useLinking';
|
||||||
|
|
||||||
it('throws if multiple instances of useLinking are used', () => {
|
it('throws if multiple instances of useLinking are used', () => {
|
||||||
const ref = React.createRef<NavigationContainerRef>();
|
const ref = createNavigationContainerRef<ParamListBase>();
|
||||||
|
|
||||||
const options = { prefixes: [] };
|
const options = { prefixes: [] };
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export * from '@react-navigation/core';
|
|||||||
|
|
||||||
export { default as NavigationContainer } from './NavigationContainer';
|
export { default as NavigationContainer } from './NavigationContainer';
|
||||||
|
|
||||||
export { default as useBackButton } from './useBackButton';
|
|
||||||
export { default as useScrollToTop } from './useScrollToTop';
|
export { default as useScrollToTop } from './useScrollToTop';
|
||||||
|
|
||||||
export { default as DefaultTheme } from './theming/DefaultTheme';
|
export { default as DefaultTheme } from './theming/DefaultTheme';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export type Theme = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LinkingOptions = {
|
export type LinkingOptions<ParamList extends {}> = {
|
||||||
/**
|
/**
|
||||||
* Whether deep link handling should be enabled.
|
* Whether deep link handling should be enabled.
|
||||||
* Defaults to true.
|
* Defaults to true.
|
||||||
@@ -53,7 +53,10 @@ export type LinkingOptions = {
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
config?: { initialRouteName?: string; screens: PathConfigMap };
|
config?: {
|
||||||
|
initialRouteName?: keyof ParamList;
|
||||||
|
screens: PathConfigMap<ParamList>;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Custom function to get the initial URL used for linking.
|
* Custom function to get the initial URL used for linking.
|
||||||
* Uses `Linking.getInitialURL()` by default.
|
* Uses `Linking.getInitialURL()` by default.
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { BackHandler } from 'react-native';
|
import { BackHandler } from 'react-native';
|
||||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
import type {
|
||||||
|
NavigationContainerRef,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/core';
|
||||||
|
|
||||||
export default function useBackButton(
|
export default function useBackButton(
|
||||||
ref: React.RefObject<NavigationContainerRef>
|
ref: React.RefObject<NavigationContainerRef<ParamListBase>>
|
||||||
) {
|
) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const subscription = BackHandler.addEventListener(
|
const subscription = BackHandler.addEventListener(
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
import type {
|
||||||
|
NavigationContainerRef,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/core';
|
||||||
import type { DocumentTitleOptions } from './types';
|
import type { DocumentTitleOptions } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the document title for the active screen
|
* Set the document title for the active screen
|
||||||
*/
|
*/
|
||||||
export default function useDocumentTitle(
|
export default function useDocumentTitle(
|
||||||
ref: React.RefObject<NavigationContainerRef>,
|
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
formatter = (options, route) => options?.title ?? route?.name,
|
formatter = (options, route) => options?.title ?? route?.name,
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
NavigationAction,
|
NavigationAction,
|
||||||
NavigationHelpersContext,
|
NavigationHelpersContext,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import useLinkTo from './useLinkTo';
|
import useLinkTo, { To } from './useLinkTo';
|
||||||
|
|
||||||
type Props = {
|
type Props<ParamList extends ReactNavigation.RootParamList> = {
|
||||||
to: string;
|
to: To<ParamList>;
|
||||||
action?: NavigationAction;
|
action?: NavigationAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,9 +17,11 @@ type Props = {
|
|||||||
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
|
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
|
||||||
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
||||||
*/
|
*/
|
||||||
export default function useLinkProps({ to, action }: Props) {
|
export default function useLinkProps<
|
||||||
|
ParamList extends ReactNavigation.RootParamList
|
||||||
|
>({ to, action }: Props<ParamList>) {
|
||||||
const navigation = React.useContext(NavigationHelpersContext);
|
const navigation = React.useContext(NavigationHelpersContext);
|
||||||
const linkTo = useLinkTo();
|
const linkTo = useLinkTo<ParamList>();
|
||||||
|
|
||||||
const onPress = (
|
const onPress = (
|
||||||
e?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
e?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||||
@@ -49,14 +51,6 @@ export default function useLinkProps({ to, action }: Props) {
|
|||||||
throw new Error("Couldn't find a navigation object.");
|
throw new Error("Couldn't find a navigation object.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (typeof to !== 'string') {
|
|
||||||
throw new Error(
|
|
||||||
`To 'to' option is invalid (found '${String(
|
|
||||||
to
|
|
||||||
)}'. It must be a valid string for navigation.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
linkTo(to);
|
linkTo(to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,37 +6,60 @@ import {
|
|||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import LinkingContext from './LinkingContext';
|
import LinkingContext from './LinkingContext';
|
||||||
|
|
||||||
export default function useLinkTo() {
|
export type To<
|
||||||
|
ParamList extends ReactNavigation.RootParamList = ReactNavigation.RootParamList,
|
||||||
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
|
> =
|
||||||
|
| string
|
||||||
|
| (undefined extends ParamList[RouteName]
|
||||||
|
? {
|
||||||
|
screen: RouteName;
|
||||||
|
params?: ParamList[RouteName];
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
screen: RouteName;
|
||||||
|
params: ParamList[RouteName];
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function useLinkTo<
|
||||||
|
ParamList extends ReactNavigation.RootParamList
|
||||||
|
>() {
|
||||||
const navigation = React.useContext(NavigationContext);
|
const navigation = React.useContext(NavigationContext);
|
||||||
const linking = React.useContext(LinkingContext);
|
const linking = React.useContext(LinkingContext);
|
||||||
|
|
||||||
const linkTo = React.useCallback(
|
const linkTo = React.useCallback(
|
||||||
(path: string) => {
|
(to: To<ParamList>) => {
|
||||||
if (!path.startsWith('/')) {
|
|
||||||
throw new Error(`The path must start with '/' (${path}).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navigation === undefined) {
|
if (navigation === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let root = navigation;
|
||||||
|
let current;
|
||||||
|
|
||||||
|
// Traverse up to get the root navigation
|
||||||
|
while ((current = root.getParent())) {
|
||||||
|
root = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof to !== 'string') {
|
||||||
|
// @ts-expect-error: This is fine
|
||||||
|
root.navigate(to.screen, to.params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to.startsWith('/')) {
|
||||||
|
throw new Error(`The path must start with '/' (${to}).`);
|
||||||
|
}
|
||||||
|
|
||||||
const { options } = linking;
|
const { options } = linking;
|
||||||
|
|
||||||
const state = options?.getStateFromPath
|
const state = options?.getStateFromPath
|
||||||
? options.getStateFromPath(path, options.config)
|
? options.getStateFromPath(to, options.config)
|
||||||
: getStateFromPath(path, options?.config);
|
: getStateFromPath(to, options?.config);
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
let root = navigation;
|
|
||||||
let current;
|
|
||||||
|
|
||||||
// Traverse up to get the root navigation
|
|
||||||
while ((current = root.getParent())) {
|
|
||||||
root = current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = getActionFromState(state, options?.config);
|
const action = getActionFromState(state, options?.config);
|
||||||
|
|
||||||
if (action !== undefined) {
|
if (action !== undefined) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
getActionFromState,
|
getActionFromState,
|
||||||
getStateFromPath as getStateFromPathDefault,
|
getStateFromPath as getStateFromPathDefault,
|
||||||
NavigationContainerRef,
|
NavigationContainerRef,
|
||||||
|
ParamListBase,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import extractPathFromURL from './extractPathFromURL';
|
import extractPathFromURL from './extractPathFromURL';
|
||||||
import type { LinkingOptions } from './types';
|
import type { LinkingOptions } from './types';
|
||||||
@@ -13,7 +14,7 @@ type ResultState = ReturnType<typeof getStateFromPathDefault>;
|
|||||||
let isUsingLinking = false;
|
let isUsingLinking = false;
|
||||||
|
|
||||||
export default function useLinking(
|
export default function useLinking(
|
||||||
ref: React.RefObject<NavigationContainerRef>,
|
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
prefixes,
|
prefixes,
|
||||||
@@ -44,7 +45,7 @@ export default function useLinking(
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
getStateFromPath = getStateFromPathDefault,
|
getStateFromPath = getStateFromPathDefault,
|
||||||
}: LinkingOptions
|
}: LinkingOptions<ParamListBase>
|
||||||
) {
|
) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (enabled !== false && isUsingLinking) {
|
if (enabled !== false && isUsingLinking) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
NavigationState,
|
NavigationState,
|
||||||
getActionFromState,
|
getActionFromState,
|
||||||
findFocusedRoute,
|
findFocusedRoute,
|
||||||
|
ParamListBase,
|
||||||
} from '@react-navigation/core';
|
} from '@react-navigation/core';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import ServerContext from './ServerContext';
|
import ServerContext from './ServerContext';
|
||||||
@@ -288,13 +289,13 @@ const series = (cb: () => Promise<void>) => {
|
|||||||
let isUsingLinking = false;
|
let isUsingLinking = false;
|
||||||
|
|
||||||
export default function useLinking(
|
export default function useLinking(
|
||||||
ref: React.RefObject<NavigationContainerRef>,
|
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
config,
|
config,
|
||||||
getStateFromPath = getStateFromPathDefault,
|
getStateFromPath = getStateFromPathDefault,
|
||||||
getPathFromState = getPathFromStateDefault,
|
getPathFromState = getPathFromStateDefault,
|
||||||
}: LinkingOptions
|
}: LinkingOptions<ParamListBase>
|
||||||
) {
|
) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (enabled !== false && isUsingLinking) {
|
if (enabled !== false && isUsingLinking) {
|
||||||
|
|||||||
@@ -3,6 +3,98 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [6.0.0-next.16](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.15...@react-navigation/stack@6.0.0-next.16) (2021-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't enable animation & gestures for first screen ([6ebe082](https://github.com/react-navigation/react-navigation/commit/6ebe0824df3df4973190428a14286aab0d14d3c1))
|
||||||
|
* make stack screens accessible on web without screens ([22b7c3f](https://github.com/react-navigation/react-navigation/commit/22b7c3f6c18a73fc55b0289b91b8d3a96f5be29c))
|
||||||
|
* move keyboardHandlingEnabled to screen options ([82900cc](https://github.com/react-navigation/react-navigation/commit/82900cceffa3e338b7b93e831f9ba6cbb3784815))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.15](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.14...@react-navigation/stack@6.0.0-next.15) (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.14](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.13...@react-navigation/stack@6.0.0-next.14) (2021-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix modal animation not being set properly ([08e74af](https://github.com/react-navigation/react-navigation/commit/08e74af54529582dcbc8d91e77bfed70f006f00d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.13](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.12...@react-navigation/stack@6.0.0-next.13) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.12](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.11...@react-navigation/stack@6.0.0-next.12) (2021-05-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.10...@react-navigation/stack@6.0.0-next.11) (2021-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* drop mode prop in favor of animationPresentation option ([9ac709e](https://github.com/react-navigation/react-navigation/commit/9ac709ea1e5a63c3a48abfa334ff6a6925095a72))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* automatically set headerMode if it's modal presentation style ([4bb0b43](https://github.com/react-navigation/react-navigation/commit/4bb0b43f1a0f27c96843415de6eaa37edebfb561))
|
||||||
|
* support mixing regular and modal presentation in same stack ([60fa3b9](https://github.com/react-navigation/react-navigation/commit/60fa3b9be976a73a5b04b632b4b37672674c956b))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This drops the mode prop on the navigator in favor of a per-screen option animationPresentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [6.0.0-next.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.9...@react-navigation/stack@6.0.0-next.10) (2021-05-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.8...@react-navigation/stack@6.0.0-next.9) (2021-04-08)
|
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.8...@react-navigation/stack@6.0.0-next.9) (2021-04-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/stack
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/stack",
|
"name": "@react-navigation/stack",
|
||||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||||
"version": "6.0.0-next.9",
|
"version": "6.0.0-next.16",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -40,17 +40,17 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/elements": "^1.0.0-next.4",
|
"@react-navigation/elements": "^1.0.0-next.10",
|
||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"react-native-iphone-x-helper": "^1.3.0",
|
"react-native-iphone-x-helper": "^1.3.0",
|
||||||
"warn-once": "^0.0.1"
|
"warn-once": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-navigation/native": "^6.0.0-next.2",
|
"@react-navigation/native": "^6.0.0-next.8",
|
||||||
"@testing-library/react-native": "^7.2.0",
|
"@testing-library/react-native": "^7.2.0",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "~0.63.51",
|
"@types/react-native": "~0.64.4",
|
||||||
"del-cli": "^3.0.1",
|
"del-cli": "^3.0.1",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"react-native": "~0.63.4",
|
"react-native": "~0.63.4",
|
||||||
|
|||||||
@@ -118,13 +118,15 @@ export function forModalPresentationIOS({
|
|||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isFirst = index === 0;
|
||||||
|
|
||||||
const translateY = multiply(
|
const translateY = multiply(
|
||||||
progress.interpolate({
|
progress.interpolate({
|
||||||
inputRange: [0, 1, 2],
|
inputRange: [0, 1, 2],
|
||||||
outputRange: [
|
outputRange: [
|
||||||
screen.height,
|
screen.height,
|
||||||
index === 0 ? 0 : topOffset,
|
isFirst ? 0 : topOffset,
|
||||||
(index === 0 ? statusBarHeight : 0) - topOffset * aspectRatio,
|
(isFirst ? statusBarHeight : 0) - topOffset * aspectRatio,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
inverted
|
inverted
|
||||||
@@ -148,7 +150,7 @@ export function forModalPresentationIOS({
|
|||||||
|
|
||||||
const borderRadius = isLandscape
|
const borderRadius = isLandscape
|
||||||
? 0
|
? 0
|
||||||
: index === 0
|
: isFirst
|
||||||
? progress.interpolate({
|
? progress.interpolate({
|
||||||
inputRange: [0, 1, 1.0001, 2],
|
inputRange: [0, 1, 1.0001, 2],
|
||||||
outputRange: [0, 0, isIphoneX() ? 38 : 0, 10],
|
outputRange: [0, 0, isIphoneX() ? 38 : 0, 10],
|
||||||
@@ -164,8 +166,8 @@ export function forModalPresentationIOS({
|
|||||||
// But different border radius for corners improves animation perf
|
// But different border radius for corners improves animation perf
|
||||||
borderBottomLeftRadius: isIphoneX() ? borderRadius : 0,
|
borderBottomLeftRadius: isIphoneX() ? borderRadius : 0,
|
||||||
borderBottomRightRadius: isIphoneX() ? borderRadius : 0,
|
borderBottomRightRadius: isIphoneX() ? borderRadius : 0,
|
||||||
marginTop: index === 0 ? 0 : statusBarHeight,
|
marginTop: isFirst ? 0 : statusBarHeight,
|
||||||
marginBottom: index === 0 ? 0 : topOffset,
|
marginBottom: isFirst ? 0 : topOffset,
|
||||||
transform: [{ translateY }, { scale }],
|
transform: [{ translateY }, { scale }],
|
||||||
},
|
},
|
||||||
overlayStyle: { opacity: overlayOpacity },
|
overlayStyle: { opacity: overlayOpacity },
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform } from 'react-native';
|
|
||||||
import {
|
import {
|
||||||
useNavigationBuilder,
|
useNavigationBuilder,
|
||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
@@ -31,20 +30,33 @@ function StackNavigator({
|
|||||||
screenOptions,
|
screenOptions,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
// @ts-expect-error: mode is deprecated
|
||||||
|
const mode = rest.mode as 'card' | 'modal' | undefined;
|
||||||
|
|
||||||
// @ts-expect-error: headerMode='none' is deprecated
|
// @ts-expect-error: headerMode='none' is deprecated
|
||||||
const headerMode = rest.headerMode as StackHeaderMode | 'none' | undefined;
|
const headerMode = rest.headerMode as StackHeaderMode | 'none' | undefined;
|
||||||
|
|
||||||
|
warnOnce(
|
||||||
|
mode != null,
|
||||||
|
`Stack Navigator: 'mode="${mode}"' is deprecated. Use 'presentation: "${mode}"' in 'screenOptions' instead.`
|
||||||
|
);
|
||||||
|
|
||||||
warnOnce(
|
warnOnce(
|
||||||
headerMode === 'none',
|
headerMode === 'none',
|
||||||
`Stack Navigator: 'headerMode="none"' is deprecated. Use 'headerShown: false' in 'screenOptions' instead.`
|
`Stack Navigator: 'headerMode="none"' is deprecated. Use 'headerShown: false' in 'screenOptions' instead.`
|
||||||
);
|
);
|
||||||
|
|
||||||
warnOnce(
|
warnOnce(
|
||||||
headerMode && headerMode !== 'none',
|
headerMode != null && headerMode !== 'none',
|
||||||
`Stack Navigator: 'headerMode' is moved to 'options'. Moved it to 'screenOptions' to keep current behavior.`
|
`Stack Navigator: 'headerMode' is moved to 'options'. Moved it to 'screenOptions' to keep current behavior.`
|
||||||
);
|
);
|
||||||
|
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const {
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
navigation,
|
||||||
|
NavigationContent,
|
||||||
|
} = useNavigationBuilder<
|
||||||
StackNavigationState<ParamListBase>,
|
StackNavigationState<ParamListBase>,
|
||||||
StackRouterOptions,
|
StackRouterOptions,
|
||||||
StackActionHelpers<ParamListBase>,
|
StackActionHelpers<ParamListBase>,
|
||||||
@@ -54,21 +66,10 @@ function StackNavigator({
|
|||||||
initialRouteName,
|
initialRouteName,
|
||||||
children,
|
children,
|
||||||
screenOptions,
|
screenOptions,
|
||||||
defaultScreenOptions: ({ options }) => ({
|
defaultScreenOptions: () => ({
|
||||||
|
presentation: mode,
|
||||||
headerShown: headerMode ? headerMode !== 'none' : true,
|
headerShown: headerMode ? headerMode !== 'none' : true,
|
||||||
headerMode:
|
headerMode: headerMode && headerMode !== 'none' ? headerMode : undefined,
|
||||||
headerMode && headerMode !== 'none'
|
|
||||||
? headerMode
|
|
||||||
: rest.mode !== 'modal' &&
|
|
||||||
Platform.OS === 'ios' &&
|
|
||||||
options.header === undefined
|
|
||||||
? 'float'
|
|
||||||
: 'screen',
|
|
||||||
gestureEnabled: Platform.OS === 'ios',
|
|
||||||
animationEnabled:
|
|
||||||
Platform.OS !== 'web' &&
|
|
||||||
Platform.OS !== 'windows' &&
|
|
||||||
Platform.OS !== 'macos',
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,12 +99,14 @@ function StackNavigator({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StackView
|
<NavigationContent>
|
||||||
{...rest}
|
<StackView
|
||||||
state={state}
|
{...rest}
|
||||||
descriptors={descriptors}
|
state={state}
|
||||||
navigation={navigation}
|
descriptors={descriptors}
|
||||||
/>
|
navigation={navigation}
|
||||||
|
/>
|
||||||
|
</NavigationContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export type StackNavigationHelpers = NavigationHelpers<
|
|||||||
|
|
||||||
export type StackNavigationProp<
|
export type StackNavigationProp<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = NavigationProp<
|
> = NavigationProp<
|
||||||
ParamList,
|
ParamList,
|
||||||
RouteName,
|
RouteName,
|
||||||
@@ -58,7 +58,7 @@ export type StackNavigationProp<
|
|||||||
|
|
||||||
export type StackScreenProps<
|
export type StackScreenProps<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
RouteName extends keyof ParamList = string
|
RouteName extends keyof ParamList = keyof ParamList
|
||||||
> = {
|
> = {
|
||||||
navigation: StackNavigationProp<ParamList, RouteName>;
|
navigation: StackNavigationProp<ParamList, RouteName>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
route: RouteProp<ParamList, RouteName>;
|
||||||
@@ -72,11 +72,21 @@ export type GestureDirection =
|
|||||||
| 'vertical'
|
| 'vertical'
|
||||||
| 'vertical-inverted';
|
| 'vertical-inverted';
|
||||||
|
|
||||||
|
type SceneOptionsDefaults = TransitionPreset & {
|
||||||
|
animationEnabled: boolean;
|
||||||
|
gestureEnabled: boolean;
|
||||||
|
cardOverlayEnabled: boolean;
|
||||||
|
headerMode: StackHeaderMode;
|
||||||
|
};
|
||||||
|
|
||||||
export type Scene = {
|
export type Scene = {
|
||||||
/**
|
/**
|
||||||
* Descriptor object for the screen.
|
* Descriptor object for the screen.
|
||||||
*/
|
*/
|
||||||
descriptor: StackDescriptor;
|
descriptor: Omit<StackDescriptor, 'options'> & {
|
||||||
|
options: Omit<StackDescriptor['options'], keyof SceneOptionsDefaults> &
|
||||||
|
SceneOptionsDefaults;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Animated nodes representing the progress of the animation.
|
* Animated nodes representing the progress of the animation.
|
||||||
*/
|
*/
|
||||||
@@ -102,7 +112,7 @@ export type SceneProgress = {
|
|||||||
|
|
||||||
export type StackHeaderMode = 'float' | 'screen';
|
export type StackHeaderMode = 'float' | 'screen';
|
||||||
|
|
||||||
export type StackCardMode = 'card' | 'modal';
|
export type StackPresentationMode = 'card' | 'modal';
|
||||||
|
|
||||||
export type StackHeaderOptions = HeaderOptions & {
|
export type StackHeaderOptions = HeaderOptions & {
|
||||||
/**
|
/**
|
||||||
@@ -182,7 +192,7 @@ export type StackHeaderProps = {
|
|||||||
export type StackDescriptor = Descriptor<
|
export type StackDescriptor = Descriptor<
|
||||||
StackNavigationOptions,
|
StackNavigationOptions,
|
||||||
StackNavigationProp<ParamListBase>,
|
StackNavigationProp<ParamListBase>,
|
||||||
RouteProp<ParamListBase, string>
|
RouteProp<ParamListBase>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type StackDescriptorMap = Record<string, StackDescriptor>;
|
export type StackDescriptorMap = Record<string, StackDescriptor>;
|
||||||
@@ -228,10 +238,27 @@ export type StackNavigationOptions = StackHeaderOptions &
|
|||||||
*
|
*
|
||||||
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
|
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
|
||||||
* This is useful to implement things like modal dialogs.
|
* This is useful to implement things like modal dialogs.
|
||||||
* If you use [`react-native-screens`](https://github.com/kmagiera/react-native-screens), you should also specify `mode: 'modal'`
|
*
|
||||||
* in the stack view config when using a transparent background so previous screens aren't detached.
|
* You should also specify `detachPreviousScreen: false` in options when using a transparent background
|
||||||
|
* so that the previous screen isn't detached and stays below the current screen.
|
||||||
|
*
|
||||||
|
* You might also need to change the animation of the screen using `cardStyleInterpolator`
|
||||||
|
* so that the previous screen isn't transformed or invisible.
|
||||||
*/
|
*/
|
||||||
cardStyle?: StyleProp<ViewStyle>;
|
cardStyle?: StyleProp<ViewStyle>;
|
||||||
|
/**
|
||||||
|
* Whether this screen should be presented as a modal or a regular card.
|
||||||
|
*
|
||||||
|
* Specifying this will configure several options:
|
||||||
|
* - `card`: Use the default OS animations for iOS and Android screen transitions.
|
||||||
|
* - `modal`: Use Modal animations. This changes a few things:
|
||||||
|
* - Sets `headerMode` to `screen` for the screen unless specified otherwise.
|
||||||
|
* - Changes the screen animation to match the platform behavior for modals.
|
||||||
|
* - Adjusts the `detachPreviousScreen` option so that the previous screen stays rendered.
|
||||||
|
*
|
||||||
|
* Defaults to 'card'.
|
||||||
|
*/
|
||||||
|
presentation?: 'card' | 'modal';
|
||||||
/**
|
/**
|
||||||
* Whether transition animation should be enabled the screen.
|
* Whether transition animation should be enabled the screen.
|
||||||
* If you set it to `false`, the screen won't animate when pushing or popping.
|
* If you set it to `false`, the screen won't animate when pushing or popping.
|
||||||
@@ -262,22 +289,22 @@ export type StackNavigationOptions = StackHeaderOptions &
|
|||||||
* Whether to detach the previous screen from the view hierarchy to save memory.
|
* Whether to detach the previous screen from the view hierarchy to save memory.
|
||||||
* Set it to `false` if you need the previous screen to be seen through the active screen.
|
* Set it to `false` if you need the previous screen to be seen through the active screen.
|
||||||
* Only applicable if `detachInactiveScreens` isn't set to `false`.
|
* Only applicable if `detachInactiveScreens` isn't set to `false`.
|
||||||
* Defaults to `false` for the last screen when mode='modal', otherwise `true`.
|
* Defaults to `false` for the last screen for modals, otherwise `true`.
|
||||||
*/
|
*/
|
||||||
detachPreviousScreen?: boolean;
|
detachPreviousScreen?: boolean;
|
||||||
|
/**
|
||||||
|
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen from this screen.
|
||||||
|
* Defaults to `true`.
|
||||||
|
*/
|
||||||
|
keyboardHandlingEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StackNavigationConfig = {
|
export type StackNavigationConfig = {
|
||||||
mode?: StackCardMode;
|
|
||||||
/**
|
|
||||||
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
|
|
||||||
* Defaults to `true`.
|
|
||||||
*/
|
|
||||||
keyboardHandlingEnabled?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
* 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.
|
* This will have no effect if you disable `react-native-screens`.
|
||||||
* Defaults to `true` on Android, depends on the version of `react-native-screens` on iOS.
|
*
|
||||||
|
* Defaults to `true`.
|
||||||
*/
|
*/
|
||||||
detachInactiveScreens?: boolean;
|
detachInactiveScreens?: boolean;
|
||||||
};
|
};
|
||||||
@@ -309,7 +336,7 @@ export type StackCardInterpolationProps = {
|
|||||||
progress: Animated.AnimatedInterpolation;
|
progress: Animated.AnimatedInterpolation;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Values for the current screen the screen after this one in the stack.
|
* Values for the screen after this one in the stack.
|
||||||
* This can be `undefined` in case the screen animating is the last one.
|
* This can be `undefined` in case the screen animating is the last one.
|
||||||
*/
|
*/
|
||||||
next?: {
|
next?: {
|
||||||
@@ -319,7 +346,7 @@ export type StackCardInterpolationProps = {
|
|||||||
progress: Animated.AnimatedInterpolation;
|
progress: Animated.AnimatedInterpolation;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* The index of the card in the stack.
|
* The index of the card with this interpolation in the stack.
|
||||||
*/
|
*/
|
||||||
index: number;
|
index: number;
|
||||||
/**
|
/**
|
||||||
@@ -388,7 +415,7 @@ export type StackHeaderInterpolationProps = {
|
|||||||
progress: Animated.AnimatedInterpolation;
|
progress: Animated.AnimatedInterpolation;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Values for the current screen the screen after this one in the stack.
|
* Values for the screen after this one in the stack.
|
||||||
* This can be `undefined` in case the screen animating is the last one.
|
* This can be `undefined` in case the screen animating is the last one.
|
||||||
*/
|
*/
|
||||||
next?: {
|
next?: {
|
||||||
|
|||||||
105
packages/stack/src/utils/useKeyboardManager.tsx
Normal file
105
packages/stack/src/utils/useKeyboardManager.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { TextInput, Keyboard, HostComponent } from 'react-native';
|
||||||
|
|
||||||
|
type InputRef = React.ElementRef<HostComponent<unknown>> | undefined;
|
||||||
|
|
||||||
|
export default function useKeyboardManager(isEnabled: () => boolean) {
|
||||||
|
// Numeric id of the previously focused text input
|
||||||
|
// When a gesture didn't change the tab, we can restore the focused input with this
|
||||||
|
const previouslyFocusedTextInputRef = React.useRef<InputRef>(undefined);
|
||||||
|
const startTimestampRef = React.useRef<number>(0);
|
||||||
|
const keyboardTimeoutRef = React.useRef<any>();
|
||||||
|
|
||||||
|
const clearKeyboardTimeout = React.useCallback(() => {
|
||||||
|
if (keyboardTimeoutRef.current !== undefined) {
|
||||||
|
clearTimeout(keyboardTimeoutRef.current);
|
||||||
|
keyboardTimeoutRef.current = undefined;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onPageChangeStart = React.useCallback(() => {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearKeyboardTimeout();
|
||||||
|
|
||||||
|
const input: InputRef = TextInput.State.currentlyFocusedInput();
|
||||||
|
|
||||||
|
// When a page change begins, blur the currently focused input
|
||||||
|
input?.blur();
|
||||||
|
|
||||||
|
// Store the id of this input so we can refocus it if change was cancelled
|
||||||
|
previouslyFocusedTextInputRef.current = input;
|
||||||
|
|
||||||
|
// Store timestamp for touch start
|
||||||
|
startTimestampRef.current = Date.now();
|
||||||
|
}, [clearKeyboardTimeout, isEnabled]);
|
||||||
|
|
||||||
|
const onPageChangeConfirm = React.useCallback(
|
||||||
|
(force: boolean) => {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearKeyboardTimeout();
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
// Always dismiss input, even if we don't have a ref to it
|
||||||
|
// We might not have the ref if onPageChangeStart was never called
|
||||||
|
// This can happen if page change was not from a gesture
|
||||||
|
Keyboard.dismiss();
|
||||||
|
} else {
|
||||||
|
const input = previouslyFocusedTextInputRef.current;
|
||||||
|
|
||||||
|
// Dismiss the keyboard only if an input was a focused before
|
||||||
|
// This makes sure we don't dismiss input on going back and focusing an input
|
||||||
|
input?.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup the ID on successful page change
|
||||||
|
previouslyFocusedTextInputRef.current = undefined;
|
||||||
|
},
|
||||||
|
[clearKeyboardTimeout, isEnabled]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onPageChangeCancel = React.useCallback(() => {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearKeyboardTimeout();
|
||||||
|
|
||||||
|
// The page didn't change, we should restore the focus of text input
|
||||||
|
const input = previouslyFocusedTextInputRef.current;
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
// If the interaction was super short we should make sure keyboard won't hide again.
|
||||||
|
|
||||||
|
// Too fast input refocus will result only in keyboard flashing on screen and hiding right away.
|
||||||
|
// During first ~100ms keyboard will be dismissed no matter what,
|
||||||
|
// so we have to make sure it won't interrupt input refocus logic.
|
||||||
|
// That's why when the interaction is shorter than 100ms we add delay so it won't hide once again.
|
||||||
|
// Subtracting timestamps makes us sure the delay is executed only when needed.
|
||||||
|
if (Date.now() - startTimestampRef.current < 100) {
|
||||||
|
keyboardTimeoutRef.current = setTimeout(() => {
|
||||||
|
input?.focus();
|
||||||
|
previouslyFocusedTextInputRef.current = undefined;
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
input?.focus();
|
||||||
|
previouslyFocusedTextInputRef.current = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [clearKeyboardTimeout, isEnabled]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => clearKeyboardTimeout();
|
||||||
|
}, [clearKeyboardTimeout]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onPageChangeStart,
|
||||||
|
onPageChangeConfirm,
|
||||||
|
onPageChangeCancel,
|
||||||
|
};
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user