mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-19 18:38:16 +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:
|
||||
default:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
- image: circleci/node:14
|
||||
working_directory: ~/project
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||
@@ -36,10 +36,12 @@ jobs:
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths: ~/.cache/yarn
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: .
|
||||
paths:
|
||||
- .
|
||||
|
||||
lint-and-typecheck:
|
||||
executor: default
|
||||
|
||||
2
.github/workflows/expo-preview.yml
vendored
2
.github/workflows/expo-preview.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
node-version: 14.x
|
||||
|
||||
- name: Setup Expo
|
||||
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
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
node-version: 14.x
|
||||
|
||||
- name: Setup Expo
|
||||
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
|
||||
on: pull_request
|
||||
on: pull_request_target
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
@@ -12,13 +12,13 @@ jobs:
|
||||
// Get a list of all issues created by the PR opener
|
||||
// See: https://octokit.github.io/rest.js/#pagination
|
||||
const creator = context.payload.sender.login;
|
||||
const opts = github.issues.listForRepo.endpoint.merge({
|
||||
const options = github.issues.listForRepo.endpoint.merge({
|
||||
...context.issue,
|
||||
creator,
|
||||
state: 'all'
|
||||
});
|
||||
|
||||
const issues = await github.paginate(opts);
|
||||
const issues = await github.paginate(options);
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.number === context.issue.number) {
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
}
|
||||
|
||||
if (issue.pull_request) {
|
||||
return ;// Creator is already a contributor.
|
||||
return; // Creator is already a contributor.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@ jobs:
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Hey ${creator}! Thanks for opening 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.*
|
||||
|
||||
*.tsbuildinfo
|
||||
*.log
|
||||
*.jks
|
||||
*.p8
|
||||
@@ -33,3 +34,5 @@ npm-debug.*
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
|
||||
*.iml
|
||||
|
||||
@@ -4,42 +4,41 @@ beforeEach(async () => {
|
||||
await page.click('[data-testid=LinkComponent]');
|
||||
});
|
||||
|
||||
it('loads the article page', async () => {
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/article/gandalf'
|
||||
);
|
||||
const getPageInfo = async () => ({
|
||||
url: await page.evaluate(() => location.pathname + location.search),
|
||||
title: await page.evaluate(() => document.title),
|
||||
heading: (await page.accessibility.snapshot())?.children?.find(
|
||||
(it) => it.role === 'heading'
|
||||
)?.name,
|
||||
});
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Article by Gandalf');
|
||||
it('loads the article page', async () => {
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
|
||||
expect(url).toBe('/link-component/article/gandalf');
|
||||
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||
expect(heading).toBe('Article by Gandalf');
|
||||
});
|
||||
|
||||
it('goes to the album page and goes back', async () => {
|
||||
await page.click('[href="/link-component/music"]');
|
||||
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/music'
|
||||
);
|
||||
{
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Albums');
|
||||
expect(url).toBe('/link-component/music');
|
||||
expect(title).toBe('Albums - React Navigation Example');
|
||||
expect(heading).toBe('Albums');
|
||||
}
|
||||
|
||||
await page.click('[aria-label="Article by Gandalf, back"]');
|
||||
|
||||
await page.waitForNavigation();
|
||||
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/article/gandalf'
|
||||
);
|
||||
{
|
||||
const { url, title, heading } = await getPageInfo();
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Article by Gandalf');
|
||||
expect(url).toBe('/link-component/article/gandalf');
|
||||
expect(title).toBe('Article by Gandalf - React Navigation Example');
|
||||
expect(heading).toBe('Article by Gandalf');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.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",
|
||||
"expo": "^41.0.0-beta.2",
|
||||
"expo": "^41.0.1",
|
||||
"expo-asset": "~8.3.1",
|
||||
"expo-blur": "~9.0.3",
|
||||
"expo-linking": "~2.2.1",
|
||||
"expo-updates": "~0.5.3",
|
||||
"expo-linking": "~2.2.3",
|
||||
"expo-updates": "~0.5.4",
|
||||
"koa": "^2.13.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
@@ -43,6 +43,7 @@
|
||||
"@types/cheerio": "^0.22.28",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/koa": "^2.13.1",
|
||||
"@types/mock-require": "^2.0.0",
|
||||
"@types/node-fetch": "^2.5.9",
|
||||
"@types/react": "~16.9.35",
|
||||
"@types/react-dom": "~16.9.8",
|
||||
@@ -51,9 +52,10 @@
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-preset-expo": "8.3.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"expo-cli": "^4.3.4",
|
||||
"expo-cli": "^4.4.4",
|
||||
"jest": "^26.6.3",
|
||||
"jest-dev-server": "^4.4.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"mock-require-assets": "^0.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^2.0.6",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'mock-require-assets';
|
||||
|
||||
import mock from 'mock-require';
|
||||
import Module from 'module';
|
||||
|
||||
// We need to make sure that .web.xx extensions are resolved before .xx
|
||||
@@ -10,3 +11,14 @@ Module._extensions = Object.fromEntries(
|
||||
return b[0].split('.').length - a[0].split('.').length;
|
||||
})
|
||||
);
|
||||
|
||||
// Set __DEV__ that expo needs
|
||||
// @ts-expect-error
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
// Reanimated doesn't support SSR :(
|
||||
mock(
|
||||
'react-native-reanimated',
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
{ ...require('react-native-reanimated/mock').default, call() {} }
|
||||
);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
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 { BlurView } from 'expo-blur';
|
||||
import {
|
||||
getFocusedRouteNameFromRoute,
|
||||
ParamListBase,
|
||||
NavigatorScreenParams,
|
||||
} from '@react-navigation/native';
|
||||
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 TouchableBounce from '../Shared/TouchableBounce';
|
||||
import Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
import Chat from '../Shared/Chat';
|
||||
@@ -30,6 +33,16 @@ type BottomTabParams = {
|
||||
TabChat: undefined;
|
||||
};
|
||||
|
||||
const AlbumsScreen = () => {
|
||||
const tabBarHeight = useBottomTabBarHeight();
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: tabBarHeight }}>
|
||||
<Albums scrollEnabled={false} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||
|
||||
export default function BottomTabsScreen({
|
||||
@@ -51,10 +64,6 @@ export default function BottomTabsScreen({
|
||||
headerLeft: (props) => (
|
||||
<HeaderBackButton {...props} onPress={navigation.goBack} />
|
||||
),
|
||||
tabBarButton:
|
||||
Platform.OS === 'web'
|
||||
? undefined
|
||||
: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
@@ -84,10 +93,18 @@ export default function BottomTabsScreen({
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="TabAlbums"
|
||||
component={Albums}
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
tabBarIcon: getTabBarIcon('image-album'),
|
||||
tabBarStyle: { position: 'absolute' },
|
||||
tabBarBackground: () => (
|
||||
<BlurView
|
||||
tint="light"
|
||||
intensity={100}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
|
||||
@@ -24,7 +24,8 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
const LinkButton = ({
|
||||
to,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
||||
}: React.ComponentProps<typeof Button> &
|
||||
Parameters<typeof useLinkProps>[0]) => {
|
||||
const props = useLinkProps({ to });
|
||||
|
||||
return <Button {...props} {...rest} />;
|
||||
@@ -57,6 +58,13 @@ const ArticleScreen = ({
|
||||
>
|
||||
Go to /link-component/music
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
to={{ screen: 'Home' }}
|
||||
mode="contained"
|
||||
style={styles.button}
|
||||
>
|
||||
Go to /
|
||||
</LinkButton>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
|
||||
@@ -117,24 +117,29 @@ export default function SimpleStackScreen({
|
||||
return (
|
||||
<SimpleStack.Navigator
|
||||
screenOptions={{
|
||||
...TransitionPresets.SlideFromRightIOS,
|
||||
headerMode: 'float',
|
||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||
}}
|
||||
>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author ?? 'Unknown'}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="NewsFeed"
|
||||
component={NewsFeedScreen}
|
||||
options={{ title: 'Feed' }}
|
||||
/>
|
||||
<SimpleStack.Group
|
||||
screenOptions={{
|
||||
...TransitionPresets.SlideFromRightIOS,
|
||||
headerMode: 'float',
|
||||
}}
|
||||
>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author ?? 'Unknown'}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="NewsFeed"
|
||||
component={NewsFeedScreen}
|
||||
options={{ title: 'Feed' }}
|
||||
/>
|
||||
</SimpleStack.Group>
|
||||
<SimpleStack.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<ModalStack.Navigator mode="modal">
|
||||
<ModalStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||
<ModalStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
|
||||
type SimpleStackParams = {
|
||||
type TransparentStackParams = {
|
||||
Article: { author: string };
|
||||
Dialog: undefined;
|
||||
};
|
||||
@@ -18,7 +18,7 @@ const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
|
||||
}: StackScreenProps<TransparentStackParams, 'Article'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
@@ -45,7 +45,9 @@ const ArticleScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
||||
const DialogScreen = ({
|
||||
navigation,
|
||||
}: StackScreenProps<TransparentStackParams>) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
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>> &
|
||||
StackScreenProps<ParamListBase>;
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
|
||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
export default function TransparentStackScreen({ navigation }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -83,13 +84,13 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator mode="modal" {...rest}>
|
||||
<SimpleStack.Screen
|
||||
<TransparentStack.Navigator screenOptions={{ presentation: 'modal' }}>
|
||||
<TransparentStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
<TransparentStack.Screen
|
||||
name="Dialog"
|
||||
component={DialogScreen}
|
||||
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,
|
||||
DarkTheme,
|
||||
PathConfigMap,
|
||||
NavigationContainerRef,
|
||||
useNavigationContainerRef,
|
||||
NavigatorScreenParams,
|
||||
} from '@react-navigation/native';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
HeaderStyleInterpolators,
|
||||
StackNavigationProp,
|
||||
} from '@react-navigation/stack';
|
||||
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
||||
|
||||
@@ -42,6 +43,7 @@ import { restartApp } from './Restart';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
import ModalStack from './Screens/ModalStack';
|
||||
import MixedStack from './Screens/MixedStack';
|
||||
import MixedHeaderMode from './Screens/MixedHeaderMode';
|
||||
import StackTransparent from './Screens/StackTransparent';
|
||||
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||
@@ -61,6 +63,13 @@ if (Platform.OS !== 'web') {
|
||||
|
||||
enableScreens();
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace ReactNavigation {
|
||||
interface RootParamList extends RootStackParamList {}
|
||||
}
|
||||
}
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Examples: undefined;
|
||||
};
|
||||
@@ -71,6 +80,10 @@ const SCREENS = {
|
||||
title: 'Modal Stack',
|
||||
component: ModalStack,
|
||||
},
|
||||
MixedStack: {
|
||||
title: 'Regular + Modal Stack',
|
||||
component: MixedStack,
|
||||
},
|
||||
MixedHeaderMode: {
|
||||
title: 'Float + Screen Header Stack',
|
||||
component: MixedHeaderMode,
|
||||
@@ -115,7 +128,7 @@ const SCREENS = {
|
||||
};
|
||||
|
||||
type RootStackParamList = {
|
||||
Home: undefined;
|
||||
Home: NavigatorScreenParams<RootDrawerParamList>;
|
||||
NotFound: undefined;
|
||||
} & {
|
||||
[P in keyof typeof SCREENS]: undefined;
|
||||
@@ -193,7 +206,7 @@ export default function App() {
|
||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||
}, []);
|
||||
|
||||
const navigationRef = React.useRef<NavigationContainerRef>(null);
|
||||
const navigationRef = useNavigationContainerRef();
|
||||
|
||||
useReduxDevToolsExtension(navigationRef);
|
||||
|
||||
@@ -228,7 +241,9 @@ export default function App() {
|
||||
prefixes: [createURL('/')],
|
||||
config: {
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||
screens: Object.keys(SCREENS).reduce<
|
||||
PathConfigMap<RootStackParamList>
|
||||
>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
const path = name
|
||||
@@ -236,13 +251,15 @@ export default function App() {
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
// @ts-expect-error: these types aren't accurate
|
||||
// But we aren't too concerned for now
|
||||
acc[name] = {
|
||||
path,
|
||||
screens: {
|
||||
Article: {
|
||||
path: 'article/:author?',
|
||||
parse: {
|
||||
author: (author) =>
|
||||
author: (author: string) =>
|
||||
author.charAt(0).toUpperCase() +
|
||||
author.slice(1).replace(/-/g, ' '),
|
||||
},
|
||||
@@ -304,7 +321,11 @@ export default function App() {
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
||||
{({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: StackNavigationProp<RootStackParamList>;
|
||||
}) => (
|
||||
<ScrollView
|
||||
style={{ backgroundColor: theme.colors.background }}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,74 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.10...@react-navigation/bottom-tabs@6.0.0-next.11) (2021-05-16)
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "6.0.0-next.5",
|
||||
"version": "6.0.0-next.11",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,16 +36,16 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.4",
|
||||
"@react-navigation/elements": "^1.0.0-next.10",
|
||||
"color": "^3.1.3",
|
||||
"warn-once": "^0.0.1"
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
|
||||
@@ -70,7 +70,12 @@ function BottomTabNavigator({
|
||||
);
|
||||
}
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
NavigationContent,
|
||||
} = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
@@ -85,13 +90,15 @@ function BottomTabNavigator({
|
||||
});
|
||||
|
||||
return (
|
||||
<BottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
/>
|
||||
<NavigationContent>
|
||||
<BottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
/>
|
||||
</NavigationContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export type BottomTabNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type BottomTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -54,7 +54,7 @@ export type BottomTabNavigationProp<
|
||||
|
||||
export type BottomTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = {
|
||||
navigation: BottomTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -229,6 +229,15 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
*/
|
||||
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
|
||||
/**
|
||||
* Component to use as background for the tab bar.
|
||||
* You could render an image, a gradient, blur view etc.
|
||||
*
|
||||
* When using `BlurView`, make sure to set `position: 'absolute'` in `tabBarStyle` as well.
|
||||
* You'd also need to use `useBottomTabBarHeight()` to add a bottom padding to your content.
|
||||
*/
|
||||
tabBarBackground?: () => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether this screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
@@ -239,7 +248,7 @@ export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
export type BottomTabDescriptor = Descriptor<
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>;
|
||||
|
||||
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
|
||||
@@ -283,7 +292,7 @@ export type BottomTabHeaderProps = {
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
route: RouteProp<ParamListBase>;
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
|
||||
@@ -144,6 +144,7 @@ export default function BottomTabBar({
|
||||
tabBarHideOnKeyboard = false,
|
||||
tabBarVisibilityAnimationConfig,
|
||||
tabBarStyle,
|
||||
tabBarBackground,
|
||||
} = focusedOptions;
|
||||
|
||||
const dimensions = useSafeAreaFrame();
|
||||
@@ -211,15 +212,7 @@ export default function BottomTabBar({
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
const topBorderWidth =
|
||||
// @ts-ignore
|
||||
StyleSheet.flatten([styles.tabBar, tabBarStyle])?.borderTopWidth;
|
||||
|
||||
onHeightChange?.(
|
||||
height +
|
||||
paddingBottom +
|
||||
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
|
||||
);
|
||||
onHeightChange?.(height);
|
||||
|
||||
setLayout((layout) => {
|
||||
if (height === layout.height && width === layout.width) {
|
||||
@@ -252,12 +245,15 @@ export default function BottomTabBar({
|
||||
layout,
|
||||
});
|
||||
|
||||
const tabBarBackgroundElement = tabBarBackground?.();
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
backgroundColor:
|
||||
tabBarBackgroundElement != null ? 'transparent' : colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
{
|
||||
@@ -286,6 +282,9 @@ export default function BottomTabBar({
|
||||
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||
onLayout={handleLayout}
|
||||
>
|
||||
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
||||
{tabBarBackgroundElement}
|
||||
</View>
|
||||
<View accessibilityRole="tablist" style={styles.content}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
import type {
|
||||
ParamListBase,
|
||||
TabNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
@@ -13,8 +11,7 @@ import {
|
||||
SafeAreaProviderCompat,
|
||||
getHeaderTitle,
|
||||
} from '@react-navigation/elements';
|
||||
|
||||
import ScreenFallback from './ScreenFallback';
|
||||
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
|
||||
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||
@@ -40,7 +37,9 @@ export default function BottomTabView(props: Props) {
|
||||
navigation,
|
||||
descriptors,
|
||||
safeAreaInsets,
|
||||
detachInactiveScreens = true,
|
||||
detachInactiveScreens = Platform.OS === 'web' ||
|
||||
Platform.OS === 'android' ||
|
||||
Platform.OS === 'ios',
|
||||
sceneContainerStyle,
|
||||
} = props;
|
||||
|
||||
@@ -89,73 +88,70 @@ export default function BottomTabView(props: Props) {
|
||||
const { routes } = state;
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<ScreenContainer
|
||||
// @ts-ignore
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
<SafeAreaProviderCompat>
|
||||
<MaybeScreenContainer
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a lazy screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a lazy screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
header = ({ layout, options }: BottomTabHeaderProps) => (
|
||||
<Header
|
||||
{...options}
|
||||
layout={layout}
|
||||
title={getHeaderTitle(options, route.name)}
|
||||
/>
|
||||
),
|
||||
} = descriptor.options;
|
||||
const {
|
||||
header = ({ layout, options }: BottomTabHeaderProps) => (
|
||||
<Header
|
||||
{...options}
|
||||
layout={layout}
|
||||
title={getHeaderTitle(options, route.name)}
|
||||
/>
|
||||
),
|
||||
} = descriptor.options;
|
||||
|
||||
return (
|
||||
<ScreenFallback
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
visible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
<Screen
|
||||
focused={isFocused}
|
||||
route={descriptor.route}
|
||||
navigation={descriptor.navigation}
|
||||
headerShown={descriptor.options.headerShown}
|
||||
headerStatusBarHeight={
|
||||
descriptor.options.headerStatusBarHeight
|
||||
}
|
||||
header={header({
|
||||
layout: dimensions,
|
||||
route: descriptor.route,
|
||||
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
||||
options: descriptor.options,
|
||||
})}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</Screen>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</ScreenFallback>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
||||
{renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
visible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
<Screen
|
||||
focused={isFocused}
|
||||
route={descriptor.route}
|
||||
navigation={descriptor.navigation}
|
||||
headerShown={descriptor.options.headerShown}
|
||||
headerStatusBarHeight={
|
||||
descriptor.options.headerStatusBarHeight
|
||||
}
|
||||
header={header({
|
||||
layout: dimensions,
|
||||
route: descriptor.route,
|
||||
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
||||
options: descriptor.options,
|
||||
})}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</Screen>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
||||
{renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
||||
import { ResourceSavingView } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
@@ -15,22 +9,35 @@ type Props = {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export default function ScreenFallback({ visible, children, ...rest }: Props) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
<Screen activityState={visible ? 2 : 0} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Screen active={visible ? 1 : 0} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
let Screens: typeof import('react-native-screens') | undefined;
|
||||
|
||||
try {
|
||||
Screens = require('react-native-screens');
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
export function MaybeScreen({ visible, children, ...rest }: Props) {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return (
|
||||
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
|
||||
{children}
|
||||
</Screens.Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,74 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.7...@react-navigation/core@6.0.0-next.8) (2021-05-16)
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "6.0.0-next.2",
|
||||
"version": "6.0.0-next.8",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
InitialState,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import UnhandledActionContext from './UnhandledActionContext';
|
||||
@@ -22,6 +23,7 @@ import useSyncState from './useSyncState';
|
||||
import checkSerializable from './checkSerializable';
|
||||
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
||||
import type {
|
||||
NavigationContainerEventMap,
|
||||
NavigationContainerRef,
|
||||
@@ -30,9 +32,6 @@ import type {
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
const NOT_INITIALIZED_ERROR =
|
||||
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
|
||||
|
||||
const serializableWarnings: string[] = [];
|
||||
const duplicateNameWarnings: string[] = [];
|
||||
|
||||
@@ -103,7 +102,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
independent,
|
||||
children,
|
||||
}: NavigationContainerProps,
|
||||
ref?: React.Ref<NavigationContainerRef>
|
||||
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
|
||||
) {
|
||||
const parent = React.useContext(NavigationStateContext);
|
||||
|
||||
@@ -202,16 +201,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(
|
||||
CommonActions
|
||||
) as (keyof typeof CommonActions)[]).reduce<any>((acc, name) => {
|
||||
...Object.keys(CommonActions).reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
dispatch(
|
||||
CommonActions[name](
|
||||
// @ts-expect-error: we can't know the type statically
|
||||
...args
|
||||
)
|
||||
);
|
||||
// @ts-expect-error: this is ok
|
||||
dispatch(CommonActions[name](...args));
|
||||
return acc;
|
||||
}, {}),
|
||||
...emitter.create('root'),
|
||||
@@ -223,6 +216,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
getParent: () => undefined,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
isReady: () => listeners.focus[0] != null,
|
||||
}));
|
||||
|
||||
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.
|
||||
*/
|
||||
const NavigationContext = React.createContext<
|
||||
NavigationProp<ParamListBase, string, any, any> | undefined
|
||||
NavigationProp<ParamListBase> | undefined
|
||||
>(undefined);
|
||||
|
||||
export default NavigationContext;
|
||||
|
||||
@@ -9,14 +9,10 @@ import NavigationStateContext from './NavigationStateContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import useOptionsGetters from './useOptionsGetters';
|
||||
import type { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||
import type { NavigationProp, RouteConfigComponent } from './types';
|
||||
|
||||
type Props<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
|
||||
type Props<State extends NavigationState, ScreenOptions extends {}> = {
|
||||
screen: RouteConfigComponent<ParamListBase, string> & { name: string };
|
||||
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||
route: Route<string>;
|
||||
routeState: NavigationState | PartialState<NavigationState> | undefined;
|
||||
@@ -31,8 +27,7 @@ type Props<
|
||||
*/
|
||||
export default function SceneView<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
ScreenOptions extends {}
|
||||
>({
|
||||
screen,
|
||||
route,
|
||||
@@ -41,7 +36,7 @@ export default function SceneView<
|
||||
getState,
|
||||
setState,
|
||||
options,
|
||||
}: Props<State, ScreenOptions, EventMap>) {
|
||||
}: Props<State, ScreenOptions>) {
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||
|
||||
|
||||
@@ -3,16 +3,17 @@ import { act, render } from '@testing-library/react-native';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
} from '@react-navigation/routers';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import NavigationStateContext from '../NavigationStateContext';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import Screen from '../Screen';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
it('throws when getState is accessed without a container', () => {
|
||||
expect.assertions(1);
|
||||
@@ -128,7 +129,7 @@ it('handle dispatching with ref', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
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();
|
||||
|
||||
@@ -316,7 +317,7 @@ it('handles getRootState', () => {
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<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 = (
|
||||
<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 [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 = (
|
||||
<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 = (
|
||||
<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', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
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", () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
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', () => {
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const fn = jest.fn();
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -33,8 +33,10 @@ it('converts state to path string', () => {
|
||||
|
||||
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
|
||||
|
||||
expect(getPathFromState(state)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
|
||||
expect(getPathFromState<object>(state)).toBe(path);
|
||||
expect(
|
||||
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||
).toBe(path);
|
||||
});
|
||||
|
||||
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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).toBe(path);
|
||||
});
|
||||
|
||||
@@ -116,8 +121,10 @@ it('handles route without param', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
|
||||
expect(getPathFromState<object>(state)).toBe(path);
|
||||
expect(
|
||||
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||
).toBe(path);
|
||||
});
|
||||
|
||||
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(getStateFromPath(path) as State)).toBe(path);
|
||||
expect(getPathFromState<object>(state)).toBe(path);
|
||||
expect(
|
||||
getPathFromState<object>(getStateFromPath<object>(path) as State)
|
||||
).toBe(path);
|
||||
});
|
||||
|
||||
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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).toBe(path);
|
||||
});
|
||||
|
||||
@@ -1405,9 +1480,12 @@ it('matches wildcard patterns at root', () => {
|
||||
routes: [{ name: '404' }],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/404');
|
||||
expect(getPathFromState<object>(state, config)).toBe('/404');
|
||||
expect(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).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(
|
||||
getPathFromState(getStateFromPath(path, config) as State, config)
|
||||
getPathFromState<object>(
|
||||
getStateFromPath<object>(path, config) as State,
|
||||
config
|
||||
)
|
||||
).toBe('/404');
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ const changePath = <T extends InitialState>(state: T, path: string): T =>
|
||||
});
|
||||
|
||||
it('returns undefined for invalid path', () => {
|
||||
expect(getStateFromPath('//')).toBe(undefined);
|
||||
expect(getStateFromPath<object>('//')).toBe(undefined);
|
||||
});
|
||||
|
||||
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(getPathFromState(state))).toEqual(
|
||||
expect(getStateFromPath<object>(path)).toEqual(state);
|
||||
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles leading slash when converting', () => {
|
||||
const path = '/foo/bar/?count=42';
|
||||
|
||||
expect(getStateFromPath(path)).toEqual({
|
||||
expect(getStateFromPath<object>(path)).toEqual({
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
@@ -136,7 +136,7 @@ it('handles leading slash when converting', () => {
|
||||
it('handles ending slash when converting', () => {
|
||||
const path = 'foo/bar/?count=42';
|
||||
|
||||
expect(getStateFromPath(path)).toEqual({
|
||||
expect(getStateFromPath<object>(path)).toEqual({
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
@@ -167,8 +167,8 @@ it('handles route without param', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state))).toEqual(
|
||||
expect(getStateFromPath<object>(path)).toEqual(state);
|
||||
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
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', () => {
|
||||
@@ -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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
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', () => {
|
||||
@@ -400,10 +400,10 @@ it('handles nested object with unused configs and with parse in it', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
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', () => {
|
||||
@@ -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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles initialRouteName at top level', () => {
|
||||
@@ -545,10 +545,10 @@ it('handles initialRouteName at top level', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles initialRouteName inside a screen', () => {
|
||||
@@ -591,10 +591,10 @@ it('handles initialRouteName inside a screen', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles initialRouteName included in path', () => {
|
||||
@@ -633,10 +633,10 @@ it('handles initialRouteName included in path', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles two initialRouteNames', () => {
|
||||
@@ -728,10 +728,10 @@ it('handles two initialRouteNames', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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 = '';
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/?foo=42')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/?foo=42'));
|
||||
});
|
||||
|
||||
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 = '';
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('chooses more exhaustive pattern', () => {
|
||||
@@ -1004,10 +1004,10 @@ it('chooses more exhaustive pattern', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles same paths beginnings', () => {
|
||||
@@ -1048,10 +1048,10 @@ it('handles same paths beginnings', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles differently ordered params v1', () => {
|
||||
@@ -1206,10 +1206,10 @@ it('handles differently ordered params v1', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles differently ordered params v2', () => {
|
||||
@@ -1261,10 +1261,10 @@ it('handles differently ordered params v2', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles differently ordered params v3', () => {
|
||||
@@ -1316,10 +1316,10 @@ it('handles differently ordered params v3', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('handles differently ordered params v4', () => {
|
||||
@@ -1371,10 +1371,10 @@ it('handles differently ordered params v4', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/5/foos/res/20')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/5/foos/res/20'));
|
||||
});
|
||||
|
||||
it('handles simple optional params', () => {
|
||||
@@ -1426,10 +1426,10 @@ it('handles simple optional params', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/5/10/foos/15')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/5/10/foos/15'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/5/10/foos/15')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/5/10/foos/15'));
|
||||
});
|
||||
|
||||
it('merges parent patterns if needed', () => {
|
||||
@@ -2030,10 +2030,10 @@ it('merges parent patterns if needed', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/foo/42/baz/babel')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/foo/42/baz/babel'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
it('matches wildcard patterns at root', () => {
|
||||
@@ -2092,10 +2092,10 @@ it('matches wildcard patterns at root', () => {
|
||||
routes: [{ name: '404', path }],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/404')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/404'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/bar/42/404')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/bar/42/404'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/404')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/404'));
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
changePath(state, '/404')
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(changePath(state, '/404'));
|
||||
});
|
||||
|
||||
it('throws if two screens map to the same pattern', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
getStateFromPath<object>(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
@@ -2287,7 +2287,7 @@ it('throws if two screens map to the same pattern', () => {
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
getStateFromPath<object>(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
@@ -2354,10 +2354,10 @@ it('correctly applies initialRouteName for config with similar route names', ()
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
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(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
expect(getStateFromPath<object>(path, config)).toEqual(state);
|
||||
expect(
|
||||
getStateFromPath<object>(getPathFromState<object>(state, config), config)
|
||||
).toEqual(state);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import * as React from 'react';
|
||||
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 BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import useNavigation from '../useNavigation';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -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', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -679,7 +727,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -715,7 +763,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -726,7 +774,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
);
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-a',
|
||||
params: { whoa: 'test' },
|
||||
})
|
||||
@@ -736,15 +784,15 @@ it('navigates to nested child in a navigator', () => {
|
||||
`"[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(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[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 navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const first = render(
|
||||
<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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -866,7 +914,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -876,7 +924,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||
);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '0',
|
||||
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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -971,7 +1019,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '4',
|
||||
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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '11',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1119,7 +1167,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const first = render(
|
||||
<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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1183,7 +1231,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
state: {
|
||||
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(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1231,7 +1279,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
@@ -1245,7 +1293,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1336,7 +1384,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1350,11 +1398,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
]);
|
||||
expect(navigation.getRootState().routeNames).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1377,11 +1421,7 @@ it('preserves order of screens in state with numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'4',
|
||||
'7',
|
||||
'1',
|
||||
]);
|
||||
expect(navigation.getRootState().routeNames).toEqual(['4', '7', '1']);
|
||||
});
|
||||
|
||||
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(
|
||||
"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();
|
||||
|
||||
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(
|
||||
"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(
|
||||
"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 navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1824,7 +1864,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentRoute()).toEqual({
|
||||
expect(navigation.getCurrentRoute()).toEqual({
|
||||
key: 'bar-a',
|
||||
name: 'bar-a',
|
||||
});
|
||||
@@ -1839,26 +1879,23 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: 'data' })}
|
||||
>
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: 'data' }}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: 'data' }}
|
||||
options={{ sample2: '2' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
@@ -1870,16 +1907,123 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
});
|
||||
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using screenOptions", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const 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 navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
<TestNavigator>
|
||||
<Screen name="bar" options={{ a: 'b' }}>
|
||||
{() => (
|
||||
<TestNavigator
|
||||
initialRouteName="bar-a"
|
||||
screenOptions={() => ({ sample2: 'data' })}
|
||||
>
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen
|
||||
name="bar-a"
|
||||
component={TestScreen}
|
||||
options={{ sample: 'data' }}
|
||||
options={{ sample: '1' }}
|
||||
/>
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestScreen}
|
||||
options={{ sample3: 'data' }}
|
||||
options={{ sample2: '2' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
@@ -1923,16 +2064,123 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: '1',
|
||||
});
|
||||
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: '2',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns focused screen's options with getCurrentOptions when all screens are rendered with screenOptions", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
|
||||
};
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const 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 navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1968,11 +2216,11 @@ it('does not throw if while getting current options with no options defined', ()
|
||||
|
||||
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', () => {
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<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);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
|
||||
expect(navigation.getCurrentOptions()).toEqual(undefined);
|
||||
});
|
||||
|
||||
@@ -5,15 +5,16 @@ import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
StackRouter,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => {
|
||||
const m = { nanoid: () => String(++m.__key), __key: 0 };
|
||||
@@ -571,7 +572,7 @@ it("prevents removing a screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -706,7 +707,7 @@ it("prevents removing a child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -867,7 +868,7 @@ it("prevents removing a grand child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -1065,7 +1066,7 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<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 ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<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 { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import Group from './Group';
|
||||
import Screen from './Screen';
|
||||
import type { TypedNavigator, EventMapBase } from './types';
|
||||
|
||||
@@ -31,6 +32,7 @@ export default function createNavigatorFactory<
|
||||
|
||||
return {
|
||||
Navigator,
|
||||
Group,
|
||||
Screen,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,7 +13,10 @@ type ConfigItem = {
|
||||
screens?: Record<string, ConfigItem>;
|
||||
};
|
||||
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap<object>;
|
||||
};
|
||||
|
||||
type NavigateAction<State extends NavigationState> = {
|
||||
type: 'NAVIGATE';
|
||||
@@ -29,7 +32,9 @@ export default function getActionFromState(
|
||||
options?: Options
|
||||
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
|
||||
const normalizedConfig = options
|
||||
? createNormalizedConfigItem(options as PathConfig<object> | string)
|
||||
: {};
|
||||
|
||||
const 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
|
||||
? {
|
||||
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]) => {
|
||||
acc[k] = createNormalizedConfigItem(v);
|
||||
return acc;
|
||||
|
||||
@@ -7,7 +7,10 @@ import type {
|
||||
import fromEntries from './fromEntries';
|
||||
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'>;
|
||||
|
||||
@@ -61,9 +64,9 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
|
||||
* @param options Extra options to fine-tune how to serialize the path.
|
||||
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
||||
*/
|
||||
export default function getPathFromState(
|
||||
export default function getPathFromState<ParamList extends {}>(
|
||||
state: State,
|
||||
options?: Options
|
||||
options?: Options<ParamList>
|
||||
): string {
|
||||
if (state == null) {
|
||||
throw Error(
|
||||
@@ -238,7 +241,7 @@ const joinPaths = (...paths: string[]): string =>
|
||||
.join('/');
|
||||
|
||||
const createConfigItem = (
|
||||
config: PathConfig | string,
|
||||
config: PathConfig<object> | string,
|
||||
parentPattern?: string
|
||||
): ConfigItem => {
|
||||
if (typeof config === 'string') {
|
||||
@@ -276,7 +279,7 @@ const createConfigItem = (
|
||||
};
|
||||
|
||||
const createNormalizedConfigs = (
|
||||
options: PathConfigMap,
|
||||
options: PathConfigMap<object>,
|
||||
pattern?: string
|
||||
): Record<string, ConfigItem> =>
|
||||
fromEntries(
|
||||
|
||||
@@ -8,9 +8,9 @@ import type {
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import type { PathConfigMap } from './types';
|
||||
|
||||
type Options = {
|
||||
type Options<ParamList extends {}> = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap;
|
||||
screens: PathConfigMap<ParamList>;
|
||||
};
|
||||
|
||||
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 options Extra options to fine-tune how to parse the path.
|
||||
*/
|
||||
export default function getStateFromPath(
|
||||
export default function getStateFromPath<ParamList extends {}>(
|
||||
path: string,
|
||||
options?: Options
|
||||
options?: Options<ParamList>
|
||||
): ResultState | undefined {
|
||||
let initialRoutes: InitialRouteConfig[] = [];
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function getStateFromPath(
|
||||
...Object.keys(screens).map((key) =>
|
||||
createNormalizedConfigs(
|
||||
key,
|
||||
screens as PathConfigMap,
|
||||
screens as PathConfigMap<object>,
|
||||
[],
|
||||
initialRoutes,
|
||||
[]
|
||||
@@ -307,7 +307,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
||||
|
||||
const createNormalizedConfigs = (
|
||||
screen: string,
|
||||
routeConfig: PathConfigMap,
|
||||
routeConfig: PathConfigMap<object>,
|
||||
routeNames: string[] = [],
|
||||
initials: InitialRouteConfig[],
|
||||
parentScreens: string[],
|
||||
@@ -319,6 +319,7 @@ const createNormalizedConfigs = (
|
||||
|
||||
parentScreens.push(screen);
|
||||
|
||||
// @ts-expect-error: we can't strongly typecheck this for now
|
||||
const config = routeConfig[screen];
|
||||
|
||||
if (typeof config === 'string') {
|
||||
@@ -345,7 +346,13 @@ const createNormalizedConfigs = (
|
||||
: config.path || '';
|
||||
|
||||
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) => {
|
||||
const result = createNormalizedConfigs(
|
||||
nestedConfig,
|
||||
config.screens as PathConfigMap,
|
||||
config.screens as PathConfigMap<object>,
|
||||
routeNames,
|
||||
initials,
|
||||
[...parentScreens],
|
||||
|
||||
@@ -3,6 +3,9 @@ export * from '@react-navigation/routers';
|
||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||
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 NavigationContext } from './NavigationContext';
|
||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||
|
||||
@@ -9,13 +9,23 @@ import type {
|
||||
ParamListBase,
|
||||
} 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<
|
||||
ScreenOptions extends {},
|
||||
ParamList extends ParamListBase = ParamListBase
|
||||
> = DefaultRouterOptions<Extract<keyof ParamList, string>> & {
|
||||
> = DefaultRouterOptions<Keyof<ParamList>> & {
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
@@ -24,7 +34,7 @@ export type DefaultNavigatorOptions<
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
route: RouteProp<ParamList>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
/**
|
||||
@@ -34,7 +44,7 @@ export type DefaultNavigatorOptions<
|
||||
defaultScreenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
route: RouteProp<ParamList>;
|
||||
navigation: any;
|
||||
options: ScreenOptions;
|
||||
}) => ScreenOptions);
|
||||
@@ -96,11 +106,11 @@ export type EventConsumer<EventMap extends EventMapBase> = {
|
||||
* @param type Type of the event (e.g. `focus`, `blur`)
|
||||
* @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,
|
||||
callback: EventListenerCallback<EventMap, EventName>
|
||||
): () => void;
|
||||
removeListener<EventName extends Extract<keyof EventMap, string>>(
|
||||
removeListener<EventName extends Keyof<EventMap>>(
|
||||
type: EventName,
|
||||
callback: EventListenerCallback<EventMap, EventName>
|
||||
): void;
|
||||
@@ -115,7 +125,7 @@ export type EventEmitter<EventMap extends EventMapBase> = {
|
||||
* @param [options.target] Key of the target route which should receive the event.
|
||||
* If not specified, all routes receive the event.
|
||||
*/
|
||||
emit<EventName extends Extract<keyof EventMap, string>>(
|
||||
emit<EventName extends Keyof<EventMap>>(
|
||||
options: {
|
||||
type: EventName;
|
||||
target?: string;
|
||||
@@ -263,8 +273,8 @@ export type NavigationContainerProps = {
|
||||
};
|
||||
|
||||
export type NavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string,
|
||||
ParamList extends {},
|
||||
RouteName extends keyof ParamList = Keyof<ParamList>,
|
||||
State extends NavigationState = NavigationState<ParamList>,
|
||||
ScreenOptions extends {} = {},
|
||||
EventMap extends EventMapBase = {}
|
||||
@@ -289,7 +299,7 @@ export type NavigationProp<
|
||||
|
||||
export type RouteProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList
|
||||
RouteName extends keyof ParamList = Keyof<ParamList>
|
||||
> = Route<Extract<RouteName, string>, ParamList[RouteName]>;
|
||||
|
||||
export type CompositeNavigationProp<
|
||||
@@ -325,6 +335,19 @@ export type CompositeNavigationProp<
|
||||
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<
|
||||
ScreenOptions extends {},
|
||||
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<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
@@ -405,35 +460,27 @@ export type RouteConfig<
|
||||
* Initial params object for the route.
|
||||
*/
|
||||
initialParams?: Partial<ParamList[RouteName]>;
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* 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>;
|
||||
} & RouteConfigComponent<ParamList, RouteName>;
|
||||
|
||||
export type RouteGroupConfig<
|
||||
ParamList extends ParamListBase,
|
||||
ScreenOptions extends {}
|
||||
> = {
|
||||
/**
|
||||
* Navigator options for this screen.
|
||||
*/
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
navigation: any;
|
||||
}) => React.ReactNode;
|
||||
component?: never;
|
||||
getComponent?: never;
|
||||
}
|
||||
);
|
||||
}) => ScreenOptions);
|
||||
/**
|
||||
* 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 = {
|
||||
/**
|
||||
@@ -470,7 +517,9 @@ export type NavigationContainerEventMap = {
|
||||
};
|
||||
};
|
||||
|
||||
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
||||
export type NavigationContainerRef<
|
||||
ParamList extends {}
|
||||
> = NavigationHelpers<ParamList> &
|
||||
EventConsumer<NavigationContainerEventMap> & {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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<
|
||||
ParamList extends ParamListBase,
|
||||
State extends NavigationState,
|
||||
@@ -509,6 +568,10 @@ export type TypedNavigator<
|
||||
> &
|
||||
DefaultNavigatorOptions<ScreenOptions, ParamList>
|
||||
>;
|
||||
/**
|
||||
* Component used for grouping multiple route configuration.
|
||||
*/
|
||||
Group: React.ComponentType<RouteGroupConfig<ParamList, ScreenOptions>>;
|
||||
/**
|
||||
* Component used for specifying route configuration.
|
||||
*/
|
||||
@@ -546,15 +609,20 @@ export type NavigatorScreenParams<
|
||||
};
|
||||
}[keyof ParamList];
|
||||
|
||||
export type PathConfig = {
|
||||
export type PathConfig<ParamList extends {}> = {
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
parse?: Record<string, (value: string) => any>;
|
||||
stringify?: Record<string, (value: any) => string>;
|
||||
screens?: PathConfigMap;
|
||||
initialRouteName?: string;
|
||||
screens?: PathConfigMap<ParamList>;
|
||||
initialRouteName?: keyof ParamList;
|
||||
};
|
||||
|
||||
export type PathConfigMap = {
|
||||
[routeName: string]: string | PathConfig;
|
||||
export type PathConfigMap<ParamList extends {}> = {
|
||||
[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>;
|
||||
descriptors: Record<
|
||||
string,
|
||||
Descriptor<
|
||||
object,
|
||||
NavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
>
|
||||
Descriptor<object, NavigationProp<ParamListBase>, RouteProp<ParamListBase>>
|
||||
>;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,22 @@ import type {
|
||||
NavigationProp,
|
||||
} 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<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {},
|
||||
@@ -32,19 +48,14 @@ type Options<
|
||||
state: State;
|
||||
screens: Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
||||
>;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
screenOptions?: ScreenOptionsOrCallback<ScreenOptions>;
|
||||
defaultScreenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
route: RouteProp<ParamListBase>;
|
||||
navigation: any;
|
||||
options: ScreenOptions;
|
||||
}) => ScreenOptions);
|
||||
@@ -133,33 +144,35 @@ export default function useDescriptors<
|
||||
ScreenOptions,
|
||||
NavigationProp<ParamListBase, string, State, ScreenOptions, EventMap> &
|
||||
ActionHelpers,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>
|
||||
>
|
||||
>((acc, route, i) => {
|
||||
const screen = screens[route.name];
|
||||
const config = screens[route.name];
|
||||
const screen = config[1];
|
||||
const navigation = navigations[route.key];
|
||||
|
||||
const customOptions = {
|
||||
const optionsList = [
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...(typeof screenOptions === 'object' || screenOptions == null
|
||||
? screenOptions
|
||||
: // @ts-expect-error: this is a function, but typescript doesn't think so
|
||||
screenOptions({
|
||||
route,
|
||||
navigation,
|
||||
})),
|
||||
// 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,
|
||||
})),
|
||||
screenOptions,
|
||||
// The `screenOptions` props passed to `Group` elements
|
||||
...((config[0]
|
||||
? config[0].filter(Boolean)
|
||||
: []) as ScreenOptionsOrCallback<ScreenOptions>[]),
|
||||
// The `options` prop passed to `Screen` elements,
|
||||
screen.options,
|
||||
// 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 = {
|
||||
...(typeof defaultScreenOptions === 'function'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import type { ParamListBase } from '@react-navigation/routers';
|
||||
import NavigationContext from './NavigationContext';
|
||||
import type { NavigationProp } from './types';
|
||||
|
||||
@@ -9,7 +8,7 @@ import type { NavigationProp } from './types';
|
||||
* @returns Navigation prop of the parent screen.
|
||||
*/
|
||||
export default function useNavigation<
|
||||
T extends NavigationProp<ParamListBase>
|
||||
T = NavigationProp<ReactNavigation.RootParamList>
|
||||
>(): T {
|
||||
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';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import NavigationHelpersContext from './NavigationHelpersContext';
|
||||
import Group from './Group';
|
||||
import Screen from './Screen';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useRegisterNavigator from './useRegisterNavigator';
|
||||
import useDescriptors from './useDescriptors';
|
||||
import useDescriptors, { ScreenConfigWithParent } from './useDescriptors';
|
||||
import useNavigationHelpers from './useNavigationHelpers';
|
||||
import useOnAction from './useOnAction';
|
||||
import useFocusEvents from './useFocusEvents';
|
||||
@@ -28,6 +30,7 @@ import useKeyedChildListeners from './useKeyedChildListeners';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import useComponent from './useComponent';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
import {
|
||||
DefaultNavigatorOptions,
|
||||
@@ -57,33 +60,40 @@ const getRouteConfigsFromChildren = <
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
>(
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode,
|
||||
options?: ScreenConfigWithParent<State, ScreenOptions, EventMap>[0]
|
||||
) => {
|
||||
const configs = React.Children.toArray(children).reduce<
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>[]
|
||||
ScreenConfigWithParent<State, ScreenOptions, EventMap>[]
|
||||
>((acc, child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
if (child.type === Screen) {
|
||||
// We can only extract the config from `Screen` elements
|
||||
// If something else was rendered, it's probably a bug
|
||||
acc.push(
|
||||
acc.push([
|
||||
options,
|
||||
child.props as RouteConfig<
|
||||
ParamListBase,
|
||||
string,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>
|
||||
);
|
||||
>,
|
||||
]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (child.type === React.Fragment) {
|
||||
// When we encounter a fragment, we need to dive into its children to extract the configs
|
||||
if (child.type === React.Fragment || child.type === Group) {
|
||||
// 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
|
||||
acc.push(
|
||||
...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;
|
||||
@@ -91,7 +101,7 @@ const getRouteConfigsFromChildren = <
|
||||
}
|
||||
|
||||
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)
|
||||
? `'${
|
||||
typeof child.type === 'string' ? child.type : child.type?.name
|
||||
@@ -107,7 +117,7 @@ const getRouteConfigsFromChildren = <
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
configs.forEach((config) => {
|
||||
const { name, children, component, getComponent } = config;
|
||||
const { name, children, component, getComponent } = config[1];
|
||||
|
||||
if (typeof name !== 'string' || !name) {
|
||||
throw new Error(
|
||||
@@ -220,25 +230,22 @@ export default function useNavigationBuilder<
|
||||
>(children);
|
||||
|
||||
const screens = routeConfigs.reduce<
|
||||
Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>
|
||||
Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
|
||||
>((acc, config) => {
|
||||
if (config.name in acc) {
|
||||
if (config[1].name in acc) {
|
||||
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;
|
||||
}, {});
|
||||
|
||||
const routeNames = routeConfigs.map((config) => config.name);
|
||||
const routeNames = routeConfigs.map((config) => config[1].name);
|
||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
const { initialParams } = screens[curr][1];
|
||||
const initialParamsFromParams =
|
||||
route?.params?.state == null &&
|
||||
route?.params?.initial !== false &&
|
||||
@@ -263,7 +270,7 @@ export default function useNavigationBuilder<
|
||||
>(
|
||||
(acc, curr) =>
|
||||
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)[])
|
||||
.concat(
|
||||
...routeNames.map((name) => {
|
||||
const { listeners } = screens[name];
|
||||
const { listeners } = screens[name][1];
|
||||
const map =
|
||||
typeof listeners === 'function'
|
||||
? listeners({ route: route as any, navigation })
|
||||
@@ -581,9 +588,14 @@ export default function useNavigationBuilder<
|
||||
descriptors,
|
||||
});
|
||||
|
||||
const NavigationContent = useComponent(NavigationHelpersContext.Provider, {
|
||||
value: navigation,
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
navigation,
|
||||
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 type { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import type { ParamListBase } from '@react-navigation/routers';
|
||||
import NavigationStateContext from './NavigationStateContext';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import type { NavigationProp } from './types';
|
||||
|
||||
type Options = {
|
||||
key?: string;
|
||||
navigation?: NavigationProp<ParamListBase, string, NavigationState, object>;
|
||||
navigation?: NavigationProp<ParamListBase>;
|
||||
options?: object | undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ import type { RouteProp } from './types';
|
||||
*
|
||||
* @returns Route prop of the parent screen.
|
||||
*/
|
||||
export default function useRoute<
|
||||
T extends RouteProp<ParamListBase, string>
|
||||
>(): T {
|
||||
export default function useRoute<T extends RouteProp<ParamListBase>>(): T {
|
||||
const route = React.useContext(NavigationRouteContext);
|
||||
|
||||
if (route === undefined) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
} from '@react-navigation/routers';
|
||||
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.
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/devtools",
|
||||
"description": "Developer tools for React Navigation",
|
||||
"version": "6.0.0-next.2",
|
||||
"version": "6.0.0-next.8",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -36,7 +36,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^6.0.0-next.2",
|
||||
"@react-navigation/core": "^6.0.0-next.8",
|
||||
"deep-equal": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -22,7 +22,7 @@ type DevToolsExtension = {
|
||||
declare const __REDUX_DEVTOOLS_EXTENSION__: DevToolsExtension | undefined;
|
||||
|
||||
export default function useReduxDevToolsExtension(
|
||||
ref: React.RefObject<NavigationContainerRef>
|
||||
ref: React.RefObject<NavigationContainerRef<any>>
|
||||
) {
|
||||
const devToolsRef = React.useRef<DevToolsConnection>();
|
||||
|
||||
|
||||
@@ -3,6 +3,69 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.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)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "6.0.0-next.4",
|
||||
"version": "6.0.0-next.10",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,15 +41,15 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.4",
|
||||
"@react-navigation/elements": "^1.0.0-next.10",
|
||||
"color": "^3.1.3",
|
||||
"warn-once": "^0.0.1"
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
|
||||
@@ -67,7 +67,12 @@ function DrawerNavigator({
|
||||
);
|
||||
}
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
NavigationContent,
|
||||
} = useNavigationBuilder<
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerRouterOptions,
|
||||
DrawerActionHelpers<ParamListBase>,
|
||||
@@ -83,12 +88,14 @@ function DrawerNavigator({
|
||||
});
|
||||
|
||||
return (
|
||||
<DrawerView
|
||||
{...rest}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<NavigationContent>
|
||||
<DrawerView
|
||||
{...rest}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</NavigationContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ export type DrawerHeaderProps = {
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
route: RouteProp<ParamListBase>;
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
@@ -252,7 +252,7 @@ export type DrawerNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type DrawerNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -264,7 +264,7 @@ export type DrawerNavigationProp<
|
||||
|
||||
export type DrawerScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = {
|
||||
navigation: DrawerNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -273,7 +273,7 @@ export type DrawerScreenProps<
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>;
|
||||
|
||||
export type DrawerDescriptorMap = Record<string, DrawerDescriptor>;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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 DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||
|
||||
@@ -16,14 +21,18 @@ export default function DrawerContentScrollView({
|
||||
const drawerPosition = React.useContext(DrawerPositionContext);
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const isRight = I18nManager.isRTL
|
||||
? drawerPosition === 'left'
|
||||
: drawerPosition === 'right';
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
{...rest}
|
||||
contentContainerStyle={[
|
||||
{
|
||||
paddingTop: insets.top + 4,
|
||||
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
|
||||
paddingRight: drawerPosition === 'right' ? insets.right : 0,
|
||||
paddingStart: !isRight ? insets.left : 0,
|
||||
paddingEnd: isRight ? insets.right : 0,
|
||||
},
|
||||
contentContainerStyle,
|
||||
]}
|
||||
|
||||
@@ -6,11 +6,9 @@ import {
|
||||
Platform,
|
||||
BackHandler,
|
||||
} from 'react-native';
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
DrawerNavigationState,
|
||||
DrawerActions,
|
||||
useTheme,
|
||||
@@ -22,9 +20,8 @@ import {
|
||||
SafeAreaProviderCompat,
|
||||
getHeaderTitle,
|
||||
} from '@react-navigation/elements';
|
||||
|
||||
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
|
||||
import { GestureHandlerRootView } from './GestureHandler';
|
||||
import ScreenFallback from './ScreenFallback';
|
||||
import DrawerToggleButton from './DrawerToggleButton';
|
||||
import DrawerContent from './DrawerContent';
|
||||
import DrawerStatusContext from '../utils/DrawerStatusContext';
|
||||
@@ -76,7 +73,9 @@ function DrawerViewBase({
|
||||
drawerContent = (props: DrawerContentComponentProps) => (
|
||||
<DrawerContent {...props} />
|
||||
),
|
||||
detachInactiveScreens = true,
|
||||
detachInactiveScreens = Platform.OS === 'web' ||
|
||||
Platform.OS === 'android' ||
|
||||
Platform.OS === 'ios',
|
||||
// Running in chrome debugger
|
||||
// @ts-expect-error
|
||||
useLegacyImplementation = !global.nativeCallSyncHook ||
|
||||
@@ -189,8 +188,10 @@ function DrawerViewBase({
|
||||
|
||||
const renderSceneContent = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
|
||||
<MaybeScreenContainer
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.content}
|
||||
>
|
||||
{state.routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { lazy = true, unmountOnBlur } = descriptor.options;
|
||||
@@ -221,7 +222,7 @@ function DrawerViewBase({
|
||||
} = descriptor.options;
|
||||
|
||||
return (
|
||||
<ScreenFallback
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
|
||||
visible={isFocused}
|
||||
@@ -243,10 +244,10 @@ function DrawerViewBase({
|
||||
>
|
||||
{descriptor.render()}
|
||||
</Screen>
|
||||
</ScreenFallback>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
</MaybeScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -294,13 +295,11 @@ function DrawerViewBase({
|
||||
|
||||
export default function DrawerView({ navigation, ...rest }: Props) {
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<DrawerViewBase navigation={navigation} {...rest} />
|
||||
</GestureHandlerWrapper>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
<SafeAreaProviderCompat>
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<DrawerViewBase navigation={navigation} {...rest} />
|
||||
</GestureHandlerWrapper>
|
||||
</SafeAreaProviderCompat>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
||||
import { ResourceSavingView } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
@@ -15,22 +9,35 @@ type Props = {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export default function ScreenFallback({ visible, children, ...rest }: Props) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
<Screen activityState={visible ? 2 : 0} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Screen active={visible ? 1 : 0} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
let Screens: typeof import('react-native-screens') | undefined;
|
||||
|
||||
try {
|
||||
Screens = require('react-native-screens');
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
export function MaybeScreen({ visible, children, ...rest }: Props) {
|
||||
if (Screens?.screensEnabled?.()) {
|
||||
return (
|
||||
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
|
||||
{children}
|
||||
</Screens.Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/elements
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/elements",
|
||||
"description": "UI Components for React Navigation",
|
||||
"version": "1.0.0-next.4",
|
||||
"version": "1.0.0-next.10",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -38,10 +38,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
|
||||
@@ -214,7 +214,7 @@ export default function Header(props: Props) {
|
||||
style={[
|
||||
styles.left,
|
||||
headerTitleAlign === 'center' && styles.expand,
|
||||
{ marginLeft: insets.left },
|
||||
{ marginStart: insets.left },
|
||||
leftContainerStyle,
|
||||
]}
|
||||
>
|
||||
@@ -236,7 +236,7 @@ export default function Header(props: Props) {
|
||||
style={[
|
||||
styles.right,
|
||||
styles.expand,
|
||||
{ marginRight: insets.right },
|
||||
{ marginEnd: insets.right },
|
||||
rightContainerStyle,
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
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';
|
||||
|
||||
export type Props = PressableProps & {
|
||||
export type Props = Omit<PressableProps, 'style'> & {
|
||||
pressColor?: string;
|
||||
pressOpacity?: number;
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
const ANDROID_SUPPORTS_RIPPLE =
|
||||
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.
|
||||
*/
|
||||
export default function PlatformPressable({
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
android_ripple,
|
||||
pressColor,
|
||||
pressOpacity,
|
||||
pressOpacity = 0.3,
|
||||
style,
|
||||
...rest
|
||||
}: Props) {
|
||||
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 (
|
||||
<Pressable
|
||||
<AnimatedPressable
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
android_ripple={
|
||||
ANDROID_SUPPORTS_RIPPLE
|
||||
? {
|
||||
@@ -39,10 +79,7 @@ export default function PlatformPressable({
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
style={({ pressed }) => [
|
||||
{ opacity: pressed && !ANDROID_SUPPORTS_RIPPLE ? pressOpacity : 1 },
|
||||
typeof style === 'function' ? style({ pressed }) : style,
|
||||
]}
|
||||
style={[{ opacity: !ANDROID_SUPPORTS_RIPPLE ? opacity : 1 }, style]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ import getDefaultHeaderHeight from './Header/getDefaultHeaderHeight';
|
||||
type Props = {
|
||||
focused: boolean;
|
||||
navigation: NavigationProp<ParamListBase>;
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
route: RouteProp<ParamListBase>;
|
||||
header: React.ReactNode;
|
||||
headerShown?: boolean;
|
||||
headerStatusBarHeight?: number;
|
||||
|
||||
@@ -3,6 +3,57 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "6.0.0-next.2",
|
||||
"version": "6.0.0-next.8",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,10 +41,10 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@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",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
|
||||
@@ -28,7 +28,12 @@ function MaterialBottomTabNavigator({
|
||||
screenOptions,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
NavigationContent,
|
||||
} = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
@@ -42,12 +47,14 @@ function MaterialBottomTabNavigator({
|
||||
});
|
||||
|
||||
return (
|
||||
<MaterialBottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
/>
|
||||
<NavigationContent>
|
||||
<MaterialBottomTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
/>
|
||||
</NavigationContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export type MaterialBottomTabNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type MaterialBottomTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -36,7 +36,7 @@ export type MaterialBottomTabNavigationProp<
|
||||
|
||||
export type MaterialBottomTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = {
|
||||
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -85,7 +85,7 @@ export type MaterialBottomTabNavigationOptions = {
|
||||
export type MaterialBottomTabDescriptor = Descriptor<
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>;
|
||||
|
||||
export type MaterialBottomTabDescriptorMap = Record<
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as React from 'react';
|
||||
import { Text, StyleSheet, Platform } from 'react-native';
|
||||
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
Route,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
@@ -75,7 +74,7 @@ try {
|
||||
};
|
||||
}
|
||||
|
||||
function MaterialBottomTabViewInner({
|
||||
export default function MaterialBottomTabView({
|
||||
state,
|
||||
navigation,
|
||||
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({
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "6.0.0-next.3",
|
||||
"version": "6.0.0-next.9",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -42,13 +42,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^3.1.3",
|
||||
"warn-once": "^0.0.1"
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
|
||||
@@ -71,7 +71,12 @@ function MaterialTopTabNavigator({
|
||||
);
|
||||
}
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
NavigationContent,
|
||||
} = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
@@ -85,12 +90,14 @@ function MaterialTopTabNavigator({
|
||||
});
|
||||
|
||||
return (
|
||||
<MaterialTopTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
/>
|
||||
<NavigationContent>
|
||||
<MaterialTopTabView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
/>
|
||||
</NavigationContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export type MaterialTopTabNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type MaterialTopTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -50,7 +50,7 @@ export type MaterialTopTabNavigationProp<
|
||||
|
||||
export type MaterialTopTabScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = {
|
||||
navigation: MaterialTopTabNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -189,7 +189,7 @@ export type MaterialTopTabNavigationOptions = {
|
||||
export type MaterialTopTabDescriptor = Descriptor<
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>;
|
||||
|
||||
export type MaterialTopTabDescriptorMap = Record<
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { TabView, SceneRendererProps } from 'react-native-tab-view';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
ParamListBase,
|
||||
@@ -43,29 +42,27 @@ export default function MaterialTopTabView({
|
||||
};
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<TabView<Route<string>>
|
||||
{...rest}
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
navigationState={state}
|
||||
renderTabBar={renderTabBar}
|
||||
renderLazyPlaceholder={({ route }) =>
|
||||
descriptors[route.key].options.lazyPlaceholder?.() ?? null
|
||||
}
|
||||
lazy={({ route }) => descriptors[route.key].options.lazy === true}
|
||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
/>
|
||||
</NavigationHelpersContext.Provider>
|
||||
<TabView<Route<string>>
|
||||
{...rest}
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
navigationState={state}
|
||||
renderTabBar={renderTabBar}
|
||||
renderLazyPlaceholder={({ route }) =>
|
||||
descriptors[route.key].options.lazyPlaceholder?.() ?? null
|
||||
}
|
||||
lazy={({ route }) => descriptors[route.key].options.lazy === true}
|
||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "6.0.0-next.2",
|
||||
"version": "6.0.0-next.8",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -37,7 +37,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^6.0.0-next.2",
|
||||
"@react-navigation/core": "^6.0.0-next.8",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.22"
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~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 type { NavigationAction } from '@react-navigation/core';
|
||||
import useLinkProps from './useLinkProps';
|
||||
import type { To } from './useLinkTo';
|
||||
|
||||
type Props = {
|
||||
to: string;
|
||||
type Props<ParamList extends ReactNavigation.RootParamList> = {
|
||||
to: To<ParamList>;
|
||||
action?: NavigationAction;
|
||||
target?: string;
|
||||
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.children Child elements to render the content.
|
||||
*/
|
||||
export default function Link({ to, action, ...rest }: Props) {
|
||||
const props = useLinkProps({ to, action });
|
||||
export default function Link<ParamList extends ReactNavigation.RootParamList>({
|
||||
to,
|
||||
action,
|
||||
...rest
|
||||
}: Props<ParamList>) {
|
||||
const props = useLinkProps<ParamList>({ to, action });
|
||||
|
||||
const onPress = (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import type { ParamListBase } from '@react-navigation/core';
|
||||
import type { LinkingOptions } from './types';
|
||||
|
||||
const LinkingContext = React.createContext<{
|
||||
options: LinkingOptions | undefined;
|
||||
options: LinkingOptions<ParamListBase> | undefined;
|
||||
}>({ options: undefined });
|
||||
|
||||
LinkingContext.displayName = 'LinkingContext';
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BaseNavigationContainer,
|
||||
NavigationContainerProps,
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import ThemeProvider from './theming/ThemeProvider';
|
||||
import DefaultTheme from './theming/DefaultTheme';
|
||||
@@ -13,9 +14,9 @@ import useDocumentTitle from './useDocumentTitle';
|
||||
import useBackButton from './useBackButton';
|
||||
import type { Theme, LinkingOptions, DocumentTitleOptions } from './types';
|
||||
|
||||
type Props = NavigationContainerProps & {
|
||||
type Props<ParamList extends {}> = NavigationContainerProps & {
|
||||
theme?: Theme;
|
||||
linking?: LinkingOptions;
|
||||
linking?: LinkingOptions<ParamList>;
|
||||
fallback?: React.ReactNode;
|
||||
documentTitle?: DocumentTitleOptions;
|
||||
onReady?: () => void;
|
||||
@@ -35,7 +36,7 @@ type Props = NavigationContainerProps & {
|
||||
* @param props.children Child elements to render the content.
|
||||
* @param props.ref Ref object which refers to the navigation object containing helper methods.
|
||||
*/
|
||||
const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||
function NavigationContainerInner(
|
||||
{
|
||||
theme = DefaultTheme,
|
||||
linking,
|
||||
@@ -43,12 +44,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||
documentTitle,
|
||||
onReady,
|
||||
...rest
|
||||
}: Props,
|
||||
ref?: React.Ref<NavigationContainerRef | null>
|
||||
}: Props<ParamListBase>,
|
||||
ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
|
||||
) {
|
||||
const isLinkingEnabled = linking ? linking.enabled !== false : false;
|
||||
|
||||
const refContainer = React.useRef<NavigationContainerRef>(null);
|
||||
const refContainer = React.useRef<NavigationContainerRef<ParamListBase>>(
|
||||
null
|
||||
);
|
||||
|
||||
useBackButton(refContainer);
|
||||
useDocumentTitle(refContainer, documentTitle);
|
||||
@@ -98,6 +101,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||
</ThemeProvider>
|
||||
</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;
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
createNavigatorFactory,
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
NavigationHelpersContext,
|
||||
NavigationContainerRef,
|
||||
createNavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import { act, render } from '@testing-library/react-native';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
@@ -21,36 +21,36 @@ it('integrates with the history API', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const createStackNavigator = createNavigatorFactory((props: any) => {
|
||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
||||
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||
StackRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<NavigationContent>
|
||||
{state.routes.map((route, i) => (
|
||||
<div key={route.key} aria-current={state.index === i || undefined}>
|
||||
{descriptors[route.key].render()}
|
||||
</div>
|
||||
))}
|
||||
</NavigationHelpersContext.Provider>
|
||||
</NavigationContent>
|
||||
);
|
||||
});
|
||||
|
||||
const createTabNavigator = createNavigatorFactory((props: any) => {
|
||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
||||
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||
TabRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<NavigationContent>
|
||||
{state.routes.map((route, i) => (
|
||||
<div key={route.key} aria-current={state.index === i || undefined}>
|
||||
{descriptors[route.key].render()}
|
||||
</div>
|
||||
))}
|
||||
</NavigationHelpersContext.Provider>
|
||||
</NavigationContent>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ it('integrates with the history API', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
render(
|
||||
<NavigationContainer ref={navigation} linking={linking}>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
createNavigatorFactory,
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
NavigationHelpersContext,
|
||||
NavigatorScreenParams,
|
||||
} from '@react-navigation/core';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
@@ -22,38 +22,51 @@ jest.mock('../useLinking', () => require('../useLinking.tsx').default);
|
||||
|
||||
it('renders correct state with location', () => {
|
||||
const createStackNavigator = createNavigatorFactory((props: any) => {
|
||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
||||
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||
StackRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<NavigationContent>
|
||||
{state.routes.map((route) => (
|
||||
<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 =>
|
||||
`${route.name} ${JSON.stringify(route.params)}`;
|
||||
|
||||
const NestedStack = () => {
|
||||
return (
|
||||
<Stack.Navigator initialRouteName="Feed">
|
||||
<Stack.Screen name="Profile" component={TestScreen} />
|
||||
<Stack.Screen name="Settings" component={TestScreen} />
|
||||
<Stack.Screen name="Feed" component={TestScreen} />
|
||||
<Stack.Screen name="Updates" component={TestScreen} />
|
||||
</Stack.Navigator>
|
||||
<StackB.Navigator initialRouteName="Feed">
|
||||
<StackB.Screen name="Profile" component={TestScreen} />
|
||||
<StackB.Screen name="Settings" component={TestScreen} />
|
||||
<StackB.Screen name="Feed" component={TestScreen} />
|
||||
<StackB.Screen name="Updates" component={TestScreen} />
|
||||
</StackB.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const element = (
|
||||
<NavigationContainer
|
||||
<NavigationContainer<StackAParamList>
|
||||
linking={{
|
||||
prefixes: [],
|
||||
config: {
|
||||
@@ -73,10 +86,10 @@ it('renders correct state with location', () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen name="Home" component={NestedStack} />
|
||||
<Stack.Screen name="Chat" component={TestScreen} />
|
||||
</Stack.Navigator>
|
||||
<StackA.Navigator>
|
||||
<StackA.Screen name="Home" component={NestedStack} />
|
||||
<StackA.Screen name="Chat" component={TestScreen} />
|
||||
</StackA.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
@@ -102,17 +115,17 @@ it('renders correct state with location', () => {
|
||||
|
||||
it('gets the current options', () => {
|
||||
const createTabNavigator = createNavigatorFactory((props: any) => {
|
||||
const { navigation, state, descriptors } = useNavigationBuilder(
|
||||
const { state, descriptors, NavigationContent } = useNavigationBuilder(
|
||||
TabRouter,
|
||||
props
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<NavigationContent>
|
||||
{state.routes.map((route) => (
|
||||
<div key={route.key}>{descriptors[route.key].render()}</div>
|
||||
))}
|
||||
</NavigationHelpersContext.Provider>
|
||||
</NavigationContent>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import * as React from 'react';
|
||||
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';
|
||||
|
||||
it('throws if multiple instances of useLinking are used', () => {
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const options = { prefixes: [] };
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ export * from '@react-navigation/core';
|
||||
|
||||
export { default as NavigationContainer } from './NavigationContainer';
|
||||
|
||||
export { default as useBackButton } from './useBackButton';
|
||||
export { default as useScrollToTop } from './useScrollToTop';
|
||||
|
||||
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.
|
||||
* 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.
|
||||
* Uses `Linking.getInitialURL()` by default.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { BackHandler } from 'react-native';
|
||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
||||
import type {
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
|
||||
export default function useBackButton(
|
||||
ref: React.RefObject<NavigationContainerRef>
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
const subscription = BackHandler.addEventListener(
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* Set the document title for the active screen
|
||||
*/
|
||||
export default function useDocumentTitle(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
formatter = (options, route) => options?.title ?? route?.name,
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
NavigationAction,
|
||||
NavigationHelpersContext,
|
||||
} from '@react-navigation/core';
|
||||
import useLinkTo from './useLinkTo';
|
||||
import useLinkTo, { To } from './useLinkTo';
|
||||
|
||||
type Props = {
|
||||
to: string;
|
||||
type Props<ParamList extends ReactNavigation.RootParamList> = {
|
||||
to: To<ParamList>;
|
||||
action?: NavigationAction;
|
||||
};
|
||||
|
||||
@@ -17,9 +17,11 @@ type Props = {
|
||||
* @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.
|
||||
*/
|
||||
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 linkTo = useLinkTo();
|
||||
const linkTo = useLinkTo<ParamList>();
|
||||
|
||||
const onPress = (
|
||||
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.");
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,37 +6,60 @@ import {
|
||||
} from '@react-navigation/core';
|
||||
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 linking = React.useContext(LinkingContext);
|
||||
|
||||
const linkTo = React.useCallback(
|
||||
(path: string) => {
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(`The path must start with '/' (${path}).`);
|
||||
}
|
||||
|
||||
(to: To<ParamList>) => {
|
||||
if (navigation === undefined) {
|
||||
throw new Error(
|
||||
"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 state = options?.getStateFromPath
|
||||
? options.getStateFromPath(path, options.config)
|
||||
: getStateFromPath(path, options?.config);
|
||||
? options.getStateFromPath(to, options.config)
|
||||
: getStateFromPath(to, options?.config);
|
||||
|
||||
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);
|
||||
|
||||
if (action !== undefined) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
getActionFromState,
|
||||
getStateFromPath as getStateFromPathDefault,
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import extractPathFromURL from './extractPathFromURL';
|
||||
import type { LinkingOptions } from './types';
|
||||
@@ -13,7 +14,7 @@ type ResultState = ReturnType<typeof getStateFromPathDefault>;
|
||||
let isUsingLinking = false;
|
||||
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
prefixes,
|
||||
@@ -44,7 +45,7 @@ export default function useLinking(
|
||||
};
|
||||
},
|
||||
getStateFromPath = getStateFromPathDefault,
|
||||
}: LinkingOptions
|
||||
}: LinkingOptions<ParamListBase>
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
if (enabled !== false && isUsingLinking) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
NavigationState,
|
||||
getActionFromState,
|
||||
findFocusedRoute,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import ServerContext from './ServerContext';
|
||||
@@ -288,13 +289,13 @@ const series = (cb: () => Promise<void>) => {
|
||||
let isUsingLinking = false;
|
||||
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
config,
|
||||
getStateFromPath = getStateFromPathDefault,
|
||||
getPathFromState = getPathFromStateDefault,
|
||||
}: LinkingOptions
|
||||
}: LinkingOptions<ParamListBase>
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
if (enabled !== false && isUsingLinking) {
|
||||
|
||||
@@ -3,6 +3,98 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [6.0.0-next.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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/stack",
|
||||
"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": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,17 +40,17 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.4",
|
||||
"@react-navigation/elements": "^1.0.0-next.10",
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0",
|
||||
"warn-once": "^0.0.1"
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@react-navigation/native": "^6.0.0-next.8",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"@types/react-native": "~0.64.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.4",
|
||||
|
||||
@@ -118,13 +118,15 @@ export function forModalPresentationIOS({
|
||||
: 0
|
||||
);
|
||||
|
||||
const isFirst = index === 0;
|
||||
|
||||
const translateY = multiply(
|
||||
progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [
|
||||
screen.height,
|
||||
index === 0 ? 0 : topOffset,
|
||||
(index === 0 ? statusBarHeight : 0) - topOffset * aspectRatio,
|
||||
isFirst ? 0 : topOffset,
|
||||
(isFirst ? statusBarHeight : 0) - topOffset * aspectRatio,
|
||||
],
|
||||
}),
|
||||
inverted
|
||||
@@ -148,7 +150,7 @@ export function forModalPresentationIOS({
|
||||
|
||||
const borderRadius = isLandscape
|
||||
? 0
|
||||
: index === 0
|
||||
: isFirst
|
||||
? progress.interpolate({
|
||||
inputRange: [0, 1, 1.0001, 2],
|
||||
outputRange: [0, 0, isIphoneX() ? 38 : 0, 10],
|
||||
@@ -164,8 +166,8 @@ export function forModalPresentationIOS({
|
||||
// But different border radius for corners improves animation perf
|
||||
borderBottomLeftRadius: isIphoneX() ? borderRadius : 0,
|
||||
borderBottomRightRadius: isIphoneX() ? borderRadius : 0,
|
||||
marginTop: index === 0 ? 0 : statusBarHeight,
|
||||
marginBottom: index === 0 ? 0 : topOffset,
|
||||
marginTop: isFirst ? 0 : statusBarHeight,
|
||||
marginBottom: isFirst ? 0 : topOffset,
|
||||
transform: [{ translateY }, { scale }],
|
||||
},
|
||||
overlayStyle: { opacity: overlayOpacity },
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
@@ -31,20 +30,33 @@ function StackNavigator({
|
||||
screenOptions,
|
||||
...rest
|
||||
}: Props) {
|
||||
// @ts-expect-error: mode is deprecated
|
||||
const mode = rest.mode as 'card' | 'modal' | undefined;
|
||||
|
||||
// @ts-expect-error: headerMode='none' is deprecated
|
||||
const headerMode = rest.headerMode as StackHeaderMode | 'none' | undefined;
|
||||
|
||||
warnOnce(
|
||||
mode != null,
|
||||
`Stack Navigator: 'mode="${mode}"' is deprecated. Use 'presentation: "${mode}"' in 'screenOptions' instead.`
|
||||
);
|
||||
|
||||
warnOnce(
|
||||
headerMode === 'none',
|
||||
`Stack Navigator: 'headerMode="none"' is deprecated. Use 'headerShown: false' in 'screenOptions' instead.`
|
||||
);
|
||||
|
||||
warnOnce(
|
||||
headerMode && headerMode !== 'none',
|
||||
headerMode != null && headerMode !== 'none',
|
||||
`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>,
|
||||
StackRouterOptions,
|
||||
StackActionHelpers<ParamListBase>,
|
||||
@@ -54,21 +66,10 @@ function StackNavigator({
|
||||
initialRouteName,
|
||||
children,
|
||||
screenOptions,
|
||||
defaultScreenOptions: ({ options }) => ({
|
||||
defaultScreenOptions: () => ({
|
||||
presentation: mode,
|
||||
headerShown: headerMode ? headerMode !== 'none' : true,
|
||||
headerMode:
|
||||
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',
|
||||
headerMode: headerMode && headerMode !== 'none' ? headerMode : undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -98,12 +99,14 @@ function StackNavigator({
|
||||
);
|
||||
|
||||
return (
|
||||
<StackView
|
||||
{...rest}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<NavigationContent>
|
||||
<StackView
|
||||
{...rest}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</NavigationContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export type StackNavigationHelpers = NavigationHelpers<
|
||||
|
||||
export type StackNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
@@ -58,7 +58,7 @@ export type StackNavigationProp<
|
||||
|
||||
export type StackScreenProps<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
RouteName extends keyof ParamList = keyof ParamList
|
||||
> = {
|
||||
navigation: StackNavigationProp<ParamList, RouteName>;
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
@@ -72,11 +72,21 @@ export type GestureDirection =
|
||||
| 'vertical'
|
||||
| 'vertical-inverted';
|
||||
|
||||
type SceneOptionsDefaults = TransitionPreset & {
|
||||
animationEnabled: boolean;
|
||||
gestureEnabled: boolean;
|
||||
cardOverlayEnabled: boolean;
|
||||
headerMode: StackHeaderMode;
|
||||
};
|
||||
|
||||
export type Scene = {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -102,7 +112,7 @@ export type SceneProgress = {
|
||||
|
||||
export type StackHeaderMode = 'float' | 'screen';
|
||||
|
||||
export type StackCardMode = 'card' | 'modal';
|
||||
export type StackPresentationMode = 'card' | 'modal';
|
||||
|
||||
export type StackHeaderOptions = HeaderOptions & {
|
||||
/**
|
||||
@@ -182,7 +192,7 @@ export type StackHeaderProps = {
|
||||
export type StackDescriptor = Descriptor<
|
||||
StackNavigationOptions,
|
||||
StackNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
RouteProp<ParamListBase>
|
||||
>;
|
||||
|
||||
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.
|
||||
* 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>;
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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`.
|
||||
* 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;
|
||||
/**
|
||||
* 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 = {
|
||||
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.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
* Defaults to `true` on Android, depends on the version of `react-native-screens` on iOS.
|
||||
* This will have no effect if you disable `react-native-screens`.
|
||||
*
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
@@ -309,7 +336,7 @@ export type StackCardInterpolationProps = {
|
||||
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.
|
||||
*/
|
||||
next?: {
|
||||
@@ -319,7 +346,7 @@ export type StackCardInterpolationProps = {
|
||||
progress: Animated.AnimatedInterpolation;
|
||||
};
|
||||
/**
|
||||
* The index of the card in the stack.
|
||||
* The index of the card with this interpolation in the stack.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
@@ -388,7 +415,7 @@ export type StackHeaderInterpolationProps = {
|
||||
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.
|
||||
*/
|
||||
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