mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
129 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8dd70a033 | ||
|
|
277fec481b | ||
|
|
3241190b19 | ||
|
|
ef7370b215 | ||
|
|
1149e718c1 | ||
|
|
d85a4fd8ed | ||
|
|
b89396888f | ||
|
|
c38906a7a0 | ||
|
|
d87857e5d9 | ||
|
|
5ae0badc44 | ||
|
|
84020a0b27 | ||
|
|
5473982859 | ||
|
|
de805a3ebf | ||
|
|
cbaabc1288 | ||
|
|
dd48fe9b15 | ||
|
|
48851c9ebd | ||
|
|
197c916a23 | ||
|
|
31caaf3071 | ||
|
|
5bcce9926a | ||
|
|
aacc1b525d | ||
|
|
3ad2bcbaf8 | ||
|
|
faee245d2e | ||
|
|
e1ab06d3d7 | ||
|
|
a204edd012 | ||
|
|
78b00bd814 | ||
|
|
923722cbb0 | ||
|
|
2c1adc9043 | ||
|
|
40439ccb42 | ||
|
|
24b3f739da | ||
|
|
2c8401d5cb | ||
|
|
6cc463f20d | ||
|
|
e6c6cc8331 | ||
|
|
8a6511c491 | ||
|
|
bfd6eb79c3 | ||
|
|
ebcd077626 | ||
|
|
5d0c7ea6d4 | ||
|
|
038eb87c42 | ||
|
|
72f90b50d2 | ||
|
|
0fce8eb2a1 | ||
|
|
040581af2d | ||
|
|
8e0ae5fc79 | ||
|
|
22610014b3 | ||
|
|
70ddcce76f | ||
|
|
cfd1c9b6ef | ||
|
|
9fce3a433c | ||
|
|
e127c84ae9 | ||
|
|
2bc4882692 | ||
|
|
36a9b4f866 | ||
|
|
13d85530ae | ||
|
|
86e64fdcd8 | ||
|
|
585ae7d7f6 | ||
|
|
7ac59e6109 | ||
|
|
7c92cd9ca9 | ||
|
|
c725ac3976 | ||
|
|
f49b0d3a3b | ||
|
|
205f297d07 | ||
|
|
0fc718b49d | ||
|
|
26351254fc | ||
|
|
d6466b7a4b | ||
|
|
02a031e46e | ||
|
|
61c6bb01b9 | ||
|
|
61e653d7c4 | ||
|
|
ca1c1622be | ||
|
|
9a0774ec35 | ||
|
|
55e6f3dd33 | ||
|
|
86a2b09a43 | ||
|
|
509ca49b64 | ||
|
|
07ba7a9687 | ||
|
|
c345ef1d0b | ||
|
|
865d8b3e51 | ||
|
|
af53dd6548 | ||
|
|
52dbe4bd66 | ||
|
|
3367ddf9df | ||
|
|
b735de153c | ||
|
|
fd034fea35 | ||
|
|
cda6397b89 | ||
|
|
79a85a431c | ||
|
|
22a8afac74 | ||
|
|
4954d6aae3 | ||
|
|
6c5e196459 | ||
|
|
e86ce7bcb7 | ||
|
|
6c3cccf877 | ||
|
|
db6bdb0430 | ||
|
|
c9d31bafdb | ||
|
|
52dd4e7ac9 | ||
|
|
15b8bb3458 | ||
|
|
802db004ae | ||
|
|
b376e9c5ed | ||
|
|
03ba1f2930 | ||
|
|
69d333f6c2 | ||
|
|
068a9a456c | ||
|
|
65d8b487f8 | ||
|
|
12b893d7ca | ||
|
|
73aad00f19 | ||
|
|
f137a84a80 | ||
|
|
8bdc6c6b9b | ||
|
|
7c1cd261bf | ||
|
|
c361795d97 | ||
|
|
5a9a1edae7 | ||
|
|
33b474ef64 | ||
|
|
ca50b172f9 | ||
|
|
d72a7bdea2 | ||
|
|
a93e1d85f0 | ||
|
|
14ac256af3 | ||
|
|
b82a9126bb | ||
|
|
b0416957bc | ||
|
|
15e5678037 | ||
|
|
852f2f0f19 | ||
|
|
c85f2ff47a | ||
|
|
6f326cf0c5 | ||
|
|
422dfc55dd | ||
|
|
2f66e5e773 | ||
|
|
f7ff1adee7 | ||
|
|
ddf27bf41a | ||
|
|
a97a43aa1d | ||
|
|
376db8be5e | ||
|
|
227f133536 | ||
|
|
8773dcb80f | ||
|
|
3ec1a862b2 | ||
|
|
5648e1a7b3 | ||
|
|
21a11543bf | ||
|
|
4cad132c2c | ||
|
|
d5c091cbcf | ||
|
|
8a63f193bf | ||
|
|
0c55803b32 | ||
|
|
0e13e8d23c | ||
|
|
366d0181dc | ||
|
|
ebab518352 | ||
|
|
b70ba66f24 |
@@ -8,6 +8,12 @@ executors:
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: "~/.cache/yarn"
|
||||
|
||||
playwright:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:bionic
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
|
||||
commands:
|
||||
attach_project:
|
||||
steps:
|
||||
@@ -61,18 +67,9 @@ jobs:
|
||||
destination: coverage
|
||||
|
||||
integration-tests:
|
||||
executor: default
|
||||
executor: playwright
|
||||
steps:
|
||||
- attach_project
|
||||
- run:
|
||||
name: Install Headless Chrome dependencies
|
||||
command: |
|
||||
sudo apt-get install -yq \
|
||||
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
|
||||
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
|
||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
- run:
|
||||
name: Build example for web
|
||||
command: yarn example expo build:web --no-pwa
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"@react-navigation/core",
|
||||
"@react-navigation/native",
|
||||
"@react-navigation/routers",
|
||||
"@react-navigation/compat",
|
||||
"@react-navigation/stack",
|
||||
"@react-navigation/drawer",
|
||||
"@react-navigation/bottom-tabs",
|
||||
"@react-navigation/material-top-tabs",
|
||||
"@react-navigation/material-bottom-tabs",
|
||||
"@react-navigation/elements",
|
||||
"@react-navigation/devtools"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { Assets as StackAssets } from '@react-navigation/stack';
|
||||
import { Assets } from '@react-navigation/elements';
|
||||
|
||||
import App from './src/index';
|
||||
|
||||
Asset.loadAsync(StackAssets);
|
||||
Asset.loadAsync(Assets);
|
||||
|
||||
registerRootComponent(App);
|
||||
|
||||
@@ -2,5 +2,6 @@ module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ it('loads the article page', async () => {
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/article/gandalf'
|
||||
);
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
|
||||
@@ -21,4 +21,8 @@ beforeEach(async () => {
|
||||
await page.goto('http://localhost:3579');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
export { browser, context, page };
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -80,13 +80,4 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
transformer: {
|
||||
getTransformOptions: () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,51 +13,52 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-async-storage/async-storage": "^1.13.1",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-masked-view/masked-view": "0.2.3",
|
||||
"color": "^3.1.3",
|
||||
"expo": "^39.0.0",
|
||||
"expo-asset": "~8.2.0",
|
||||
"expo-blur": "~8.2.0",
|
||||
"expo-linking": "^1.0.4",
|
||||
"expo-updates": "~0.3.5",
|
||||
"expo": "^41.0.0-beta.2",
|
||||
"expo-asset": "~8.3.1",
|
||||
"expo-blur": "~9.0.3",
|
||||
"expo-linking": "~2.2.1",
|
||||
"expo-updates": "~0.5.3",
|
||||
"koa": "^2.13.0",
|
||||
"react": "~16.13.1",
|
||||
"react-dom": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
|
||||
"react-native-appearance": "~0.3.3",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-paper": "^4.2.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"react-native-vector-icons": "^7.0.0",
|
||||
"react-native-web": "^0.13.16"
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-pager-view": "~5.0.12",
|
||||
"react-native-paper": "^4.7.2",
|
||||
"react-native-reanimated": "~2.1.0",
|
||||
"react-native-safe-area-context": "~3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"react-native-tab-view": "^3.0.1",
|
||||
"react-native-vector-icons": "^8.1.0",
|
||||
"react-native-web": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/node": "^7.12.1",
|
||||
"@expo/webpack-config": "^0.12.40",
|
||||
"@types/cheerio": "^0.22.22",
|
||||
"@babel/node": "^7.13.13",
|
||||
"@expo/webpack-config": "~0.12.63",
|
||||
"@types/cheerio": "^0.22.28",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/koa": "^2.11.6",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "~16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-native": "~0.63.30",
|
||||
"babel-loader": "^8.1.0",
|
||||
"@types/koa": "^2.13.1",
|
||||
"@types/node-fetch": "^2.5.9",
|
||||
"@types/react": "~16.9.35",
|
||||
"@types/react-dom": "~16.9.8",
|
||||
"@types/react-native": "~0.63.2",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-preset-expo": "^8.3.0",
|
||||
"babel-preset-expo": "8.3.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"expo-cli": "^3.28.2",
|
||||
"jest": "^26.6.1",
|
||||
"expo-cli": "^4.3.4",
|
||||
"jest": "^26.6.3",
|
||||
"jest-dev-server": "^4.4.0",
|
||||
"mock-require-assets": "^0.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^2.0.6",
|
||||
"playwright": "^0.14.0",
|
||||
"playwright": "^1.10.0",
|
||||
"serve": "^11.3.0",
|
||||
"typescript": "^4.0.3"
|
||||
"typescript": "~4.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ module.exports = {
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['..'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json'],
|
||||
alias: {
|
||||
'react-native': 'react-native-web',
|
||||
...alias,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import * as Linking from 'expo-linking';
|
||||
export default [Linking.makeUrl('/')];
|
||||
@@ -1 +0,0 @@
|
||||
export default ['rne://127.0.0.1:19000/--/'];
|
||||
@@ -4,9 +4,9 @@ import { Title, Button } from 'react-native-paper';
|
||||
import { useTheme, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
HeaderBackButton,
|
||||
StackScreenProps,
|
||||
} from '@react-navigation/stack';
|
||||
import { HeaderBackButton } from '@react-navigation/elements';
|
||||
|
||||
type AuthStackParams = {
|
||||
Splash: undefined;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { Platform } from 'react-native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {
|
||||
getFocusedRouteNameFromRoute,
|
||||
@@ -8,10 +7,8 @@ import {
|
||||
NavigatorScreenParams,
|
||||
} from '@react-navigation/native';
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
import {
|
||||
createBottomTabNavigator,
|
||||
BottomTabScreenProps,
|
||||
} from '@react-navigation/bottom-tabs';
|
||||
import { createBottomTabNavigator } 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';
|
||||
@@ -27,38 +24,10 @@ const getTabBarIcon = (name: string) => ({
|
||||
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
|
||||
|
||||
type BottomTabParams = {
|
||||
Article: NavigatorScreenParams<SimpleStackParams>;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: BottomTabScreenProps<BottomTabParams>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.setOptions({ tabBarVisible: false })}
|
||||
style={styles.button}
|
||||
>
|
||||
Hide tab bar
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.setOptions({ tabBarVisible: true })}
|
||||
style={styles.button}
|
||||
>
|
||||
Show tab bar
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
TabStack: NavigatorScreenParams<SimpleStackParams>;
|
||||
TabAlbums: undefined;
|
||||
TabContacts: undefined;
|
||||
TabChat: undefined;
|
||||
};
|
||||
|
||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||
@@ -71,6 +40,7 @@ export default function BottomTabsScreen({
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
title: routeName,
|
||||
});
|
||||
}, [navigation, routeName]);
|
||||
@@ -78,6 +48,9 @@ export default function BottomTabsScreen({
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
headerLeft: (props) => (
|
||||
<HeaderBackButton {...props} onPress={navigation.goBack} />
|
||||
),
|
||||
tabBarButton:
|
||||
Platform.OS === 'web'
|
||||
? undefined
|
||||
@@ -85,21 +58,15 @@ export default function BottomTabsScreen({
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
name="Article"
|
||||
name="TabStack"
|
||||
component={SimpleStackScreen}
|
||||
options={{
|
||||
title: 'Article',
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
tabBarIcon: getTabBarIcon('file-document'),
|
||||
}}
|
||||
>
|
||||
{(props) => (
|
||||
<SimpleStackScreen
|
||||
{...props}
|
||||
screenOptions={{ headerShown: false }}
|
||||
/>
|
||||
)}
|
||||
</BottomTabs.Screen>
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="Chat"
|
||||
name="TabChat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -108,7 +75,7 @@ export default function BottomTabsScreen({
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="Contacts"
|
||||
name="TabContacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
title: 'Contacts',
|
||||
@@ -116,8 +83,8 @@ export default function BottomTabsScreen({
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
name="TabAlbums"
|
||||
component={Albums}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
tabBarIcon: getTabBarIcon('image-album'),
|
||||
@@ -126,13 +93,3 @@ export default function BottomTabsScreen({
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import {
|
||||
createCompatNavigatorFactory,
|
||||
CompatScreenType,
|
||||
} from '@react-navigation/compat';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackNavigationProp,
|
||||
StackScreenProps,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
type CompatStackParams = {
|
||||
Albums: undefined;
|
||||
Nested: { author: string };
|
||||
};
|
||||
|
||||
type NestedStackParams = {
|
||||
Feed: undefined;
|
||||
Article: { author: string };
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push nested
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
NestedStackParams,
|
||||
'Article'
|
||||
>> = ({ navigation }) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push albums
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: navigation.getParam('author') }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
||||
title: `Article by ${navigation.getParam('author')}`,
|
||||
});
|
||||
|
||||
const createCompatStackNavigator = createCompatNavigatorFactory(
|
||||
createStackNavigator
|
||||
);
|
||||
|
||||
const CompatStack = createCompatStackNavigator<
|
||||
StackNavigationProp<CompatStackParams>
|
||||
>(
|
||||
{
|
||||
Albums: AlbumsScreen,
|
||||
Nested: {
|
||||
screen: createCompatStackNavigator<
|
||||
StackNavigationProp<NestedStackParams>
|
||||
>(
|
||||
{
|
||||
Feed: { getScreen: () => FeedScreen },
|
||||
Article: { getScreen: () => ArticleScreen },
|
||||
},
|
||||
{ navigationOptions: { headerShown: false } }
|
||||
),
|
||||
params: {
|
||||
author: 'Gandalf',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
}
|
||||
);
|
||||
|
||||
export default function CompatStackScreen({
|
||||
navigation,
|
||||
}: StackScreenProps<{}>) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return <CompatStack />;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DrawerScreenProps,
|
||||
DrawerContent,
|
||||
DrawerContentComponentProps,
|
||||
DrawerContentOptions,
|
||||
} from '@react-navigation/drawer';
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
@@ -91,9 +90,7 @@ const AlbumsScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const CustomDrawerContent = (
|
||||
props: DrawerContentComponentProps<DrawerContentOptions>
|
||||
) => {
|
||||
const CustomDrawerContent = (props: DrawerContentComponentProps) => {
|
||||
const { colors } = useTheme();
|
||||
const navigation = useNavigation();
|
||||
|
||||
@@ -126,10 +123,14 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
openByDefault
|
||||
drawerType={isLargeScreen ? 'permanent' : 'back'}
|
||||
drawerStyle={isLargeScreen ? null : { width: '100%' }}
|
||||
overlayColor="transparent"
|
||||
drawerContent={(props) => <CustomDrawerContent {...props} />}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
drawerType: isLargeScreen ? 'permanent' : 'back',
|
||||
drawerStyle: isLargeScreen ? null : { width: '100%' },
|
||||
drawerContentContainerStyle: { paddingTop: 4 },
|
||||
overlayColor: 'transparent',
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Drawer.Screen name="Article" component={ArticleScreen} />
|
||||
|
||||
@@ -8,24 +8,22 @@ import Chat from '../Shared/Chat';
|
||||
import SimpleStackScreen, { SimpleStackParams } from './SimpleStack';
|
||||
|
||||
type MaterialBottomTabParams = {
|
||||
Article: NavigatorScreenParams<SimpleStackParams>;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
TabStack: NavigatorScreenParams<SimpleStackParams>;
|
||||
TabAlbums: undefined;
|
||||
TabContacts: undefined;
|
||||
TabChat: undefined;
|
||||
};
|
||||
|
||||
const MaterialBottomTabs = createMaterialBottomTabNavigator<
|
||||
MaterialBottomTabParams
|
||||
>();
|
||||
const MaterialBottomTabs = createMaterialBottomTabNavigator<MaterialBottomTabParams>();
|
||||
|
||||
export default function MaterialBottomTabsScreen() {
|
||||
return (
|
||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Article"
|
||||
name="TabStack"
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: 'file-document-box',
|
||||
tabBarIcon: 'file-document',
|
||||
tabBarColor: '#C9E7F8',
|
||||
}}
|
||||
>
|
||||
@@ -37,7 +35,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
)}
|
||||
</MaterialBottomTabs.Screen>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Chat"
|
||||
name="TabChat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -47,7 +45,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Contacts"
|
||||
name="TabContacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
tabBarLabel: 'Contacts',
|
||||
@@ -56,7 +54,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Albums"
|
||||
name="TabAlbums"
|
||||
component={Albums}
|
||||
options={{
|
||||
tabBarLabel: 'Albums',
|
||||
|
||||
159
example/src/Screens/MixedHeaderMode.tsx
Normal file
159
example/src/Screens/MixedHeaderMode.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as React from 'react';
|
||||
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import type { ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
TransitionPresets,
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
export type SimpleStackParams = {
|
||||
Article: { author: string } | undefined;
|
||||
NewsFeed: { date: number };
|
||||
Albums: undefined;
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('NewsFeed', { date: Date.now() })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push feed
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: route.params?.author ?? 'Unknown' }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const NewsFeedScreen = ({
|
||||
route,
|
||||
navigation,
|
||||
}: StackScreenProps<SimpleStackParams, 'NewsFeed'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Navigate to album
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} date={route.params.date} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: StackScreenProps<SimpleStackParams, 'Albums'>) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.pop()}
|
||||
style={styles.button}
|
||||
>
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
export default function SimpleStackScreen({
|
||||
navigation,
|
||||
}: StackScreenProps<ParamListBase>) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator
|
||||
screenOptions={{
|
||||
...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.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
headerMode: 'screen',
|
||||
title: 'Albums',
|
||||
}}
|
||||
/>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -5,8 +5,6 @@ import type { ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
StackNavigationOptions,
|
||||
TransitionPresets,
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
@@ -72,13 +70,11 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<ModalStackParams>) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
|
||||
const ModalStack = createStackNavigator<ModalStackParams>();
|
||||
|
||||
type Props = StackScreenProps<ParamListBase> & {
|
||||
options?: StackNavigationOptions;
|
||||
};
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
|
||||
export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
export default function ModalStackScreen({ navigation }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -86,22 +82,8 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<ModalPresentationStack.Navigator
|
||||
mode="modal"
|
||||
screenOptions={({ route, navigation }) => ({
|
||||
...TransitionPresets.ModalPresentationIOS,
|
||||
cardOverlayEnabled: true,
|
||||
gestureEnabled: true,
|
||||
headerStatusBarHeight:
|
||||
navigation
|
||||
.dangerouslyGetState()
|
||||
.routes.findIndex((r: any) => r.key === route.key) > 0
|
||||
? 0
|
||||
: undefined,
|
||||
})}
|
||||
{...options}
|
||||
>
|
||||
<ModalPresentationStack.Screen
|
||||
<ModalStack.Navigator mode="modal">
|
||||
<ModalStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
@@ -109,12 +91,12 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<ModalPresentationStack.Screen
|
||||
<ModalStack.Screen
|
||||
name="Albums"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</ModalPresentationStack.Navigator>
|
||||
</ModalStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { Button } from 'react-native-paper';
|
||||
import type { StackScreenProps } from '@react-navigation/stack';
|
||||
|
||||
const NotFoundScreen = ({
|
||||
route,
|
||||
navigation,
|
||||
}: StackScreenProps<{ Home: undefined }>) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>404 Not Found</Text>
|
||||
<Text style={styles.title}>404 Not Found ({route.path})</Text>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
|
||||
@@ -13,11 +13,10 @@ import { useTheme, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
HeaderBackground,
|
||||
useHeaderHeight,
|
||||
Header,
|
||||
StackHeaderProps,
|
||||
} from '@react-navigation/stack';
|
||||
import { HeaderBackground, useHeaderHeight } from '@react-navigation/elements';
|
||||
import BlurView from '../Shared/BlurView';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
@@ -87,11 +86,10 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
|
||||
StackScreenProps<ParamListBase>;
|
||||
type Props = StackScreenProps<ParamListBase>;
|
||||
|
||||
function CustomHeader(props: StackHeaderProps) {
|
||||
const { current, next } = props.scene.progress;
|
||||
const { current, next } = props.progress;
|
||||
|
||||
const progress = Animated.add(current, next || 0);
|
||||
const opacity = progress.interpolate({
|
||||
@@ -109,7 +107,7 @@ function CustomHeader(props: StackHeaderProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
export default function HeaderCustomizationScreen({ navigation }: Props) {
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
@@ -117,19 +115,20 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
}, [navigation]);
|
||||
|
||||
const { colors, dark } = useTheme();
|
||||
const [headerTitleCentered, setHeaderTitleCentered] = React.useState(true);
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator {...rest}>
|
||||
<SimpleStack.Navigator screenOptions={{ headerMode: 'float' }}>
|
||||
<SimpleStack.Screen
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params?.author}`,
|
||||
header: CustomHeader,
|
||||
header: (props) => <CustomHeader {...props} />,
|
||||
headerTintColor: '#fff',
|
||||
headerStyle: { backgroundColor: '#ff005d' },
|
||||
headerBackTitleVisible: false,
|
||||
headerTitleAlign: 'center',
|
||||
headerTitleAlign: headerTitleCentered ? 'center' : 'left',
|
||||
headerBackImage: ({ tintColor }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="arrow-left-circle-outline"
|
||||
@@ -142,12 +141,13 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
<Appbar.Action
|
||||
color={tintColor}
|
||||
icon="dots-horizontal-circle-outline"
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
setHeaderTitleCentered((centered) => !centered);
|
||||
Alert.alert(
|
||||
'Never gonna give you up!',
|
||||
'Never gonna let you down! Never gonna run around and desert you!'
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
Divider,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import { createURL } from 'expo-linking';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {
|
||||
InitialState,
|
||||
NavigationContainer,
|
||||
@@ -35,13 +37,12 @@ import {
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
import { restartApp } from './Restart';
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||
import ModalStack from './Screens/ModalStack';
|
||||
import MixedHeaderMode from './Screens/MixedHeaderMode';
|
||||
import StackTransparent from './Screens/StackTransparent';
|
||||
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||
import BottomTabs from './Screens/BottomTabs';
|
||||
@@ -52,7 +53,6 @@ import DynamicTabs from './Screens/DynamicTabs';
|
||||
import MasterDetail from './Screens/MasterDetail';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import PreventRemove from './Screens/PreventRemove';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import LinkComponent from './Screens/LinkComponent';
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
@@ -67,9 +67,13 @@ type RootDrawerParamList = {
|
||||
|
||||
const SCREENS = {
|
||||
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
|
||||
ModalPresentationStack: {
|
||||
title: 'Modal Presentation Stack',
|
||||
component: ModalPresentationStack,
|
||||
ModalStack: {
|
||||
title: 'Modal Stack',
|
||||
component: ModalStack,
|
||||
},
|
||||
MixedHeaderMode: {
|
||||
title: 'Float + Screen Header Stack',
|
||||
component: MixedHeaderMode,
|
||||
},
|
||||
StackTransparent: {
|
||||
title: 'Transparent Stack',
|
||||
@@ -104,10 +108,6 @@ const SCREENS = {
|
||||
title: 'Prevent removing screen',
|
||||
component: PreventRemove,
|
||||
},
|
||||
CompatAPI: {
|
||||
title: 'Compat Layer',
|
||||
component: CompatAPI,
|
||||
},
|
||||
LinkComponent: {
|
||||
title: '<Link />',
|
||||
component: LinkComponent,
|
||||
@@ -141,7 +141,7 @@ export default function App() {
|
||||
const initialUrl = await Linking.getInitialURL();
|
||||
|
||||
if (Platform.OS !== 'web' || initialUrl === null) {
|
||||
const savedState = await AsyncStorage.getItem(
|
||||
const savedState = await AsyncStorage?.getItem(
|
||||
NAVIGATION_PERSISTENCE_KEY
|
||||
);
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function App() {
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY);
|
||||
const themeName = await AsyncStorage?.getItem(THEME_PERSISTENCE_KEY);
|
||||
|
||||
setTheme(themeName === 'dark' ? DarkTheme : DefaultTheme);
|
||||
} catch (e) {
|
||||
@@ -212,7 +212,7 @@ export default function App() {
|
||||
ref={navigationRef}
|
||||
initialState={initialState}
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage.setItem(
|
||||
AsyncStorage?.setItem(
|
||||
NAVIGATION_PERSISTENCE_KEY,
|
||||
JSON.stringify(state)
|
||||
)
|
||||
@@ -225,7 +225,7 @@ export default function App() {
|
||||
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
|
||||
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
|
||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||
prefixes: LinkingPrefixes,
|
||||
prefixes: [createURL('/')],
|
||||
config: {
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||
@@ -291,8 +291,9 @@ export default function App() {
|
||||
>
|
||||
{() => (
|
||||
<Drawer.Navigator
|
||||
drawerType={isLargeScreen ? 'permanent' : undefined}
|
||||
screenOptions={{ headerShown: true }}
|
||||
screenOptions={{
|
||||
drawerType: isLargeScreen ? 'permanent' : undefined,
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="Examples"
|
||||
@@ -321,7 +322,7 @@ export default function App() {
|
||||
label="Dark theme"
|
||||
value={theme.dark}
|
||||
onValueChange={() => {
|
||||
AsyncStorage.setItem(
|
||||
AsyncStorage?.setItem(
|
||||
THEME_PERSISTENCE_KEY,
|
||||
theme.dark ? 'light' : 'dark'
|
||||
);
|
||||
|
||||
18
example/tsconfig.json
Normal file
18
example/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../packages/bottom-tabs" },
|
||||
{ "path": "../packages/core" },
|
||||
{ "path": "../packages/devtools" },
|
||||
{ "path": "../packages/drawer" },
|
||||
{ "path": "../packages/elements" },
|
||||
{ "path": "../packages/material-bottom-tabs" },
|
||||
{ "path": "../packages/material-top-tabs" },
|
||||
{ "path": "../packages/native" },
|
||||
{ "path": "../packages/routers" },
|
||||
{ "path": "../packages/stack" },
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Avoid expo-cli auto-generating a tsconfig
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"allowBranch": "main",
|
||||
"conventionalCommits": true,
|
||||
"createRelease": "github",
|
||||
"distTag": "next",
|
||||
"message": "chore: publish",
|
||||
"ignoreChanges": [
|
||||
"**/__fixtures__/**",
|
||||
|
||||
28
package.json
28
package.json
@@ -8,7 +8,7 @@
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -25,19 +25,19 @@
|
||||
"example": "yarn --cwd example"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"babel-jest": "^26.6.1",
|
||||
"codecov": "^3.8.0",
|
||||
"commitlint": "^11.0.0",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-config-satya164": "^3.1.8",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.1",
|
||||
"lerna": "^3.22.1",
|
||||
"metro-react-native-babel-preset": "^0.63.0",
|
||||
"prettier": "^2.1.2",
|
||||
"typescript": "^4.0.3"
|
||||
"@commitlint/config-conventional": "^12.1.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"babel-jest": "^26.6.3",
|
||||
"codecov": "^3.8.1",
|
||||
"commitlint": "^12.1.1",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint-config-satya164": "^3.1.10",
|
||||
"husky": "^4.3.6",
|
||||
"jest": "^26.6.3",
|
||||
"lerna": "^4.0.0",
|
||||
"metro-react-native-babel-preset": "^0.65.2",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "~16.13.1",
|
||||
|
||||
@@ -3,29 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.11.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.6...@react-navigation/bottom-tabs@5.11.7) (2021-01-25)
|
||||
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.3...@react-navigation/bottom-tabs@6.0.0-next.4) (2021-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix drawer screen content not being interactable on Android ([87b5147](https://github.com/react-navigation/react-navigation/commit/87b51476d0bce8f2dae793416c2976da30a1a5f7))
|
||||
* remove calls to removed Keyboard.removeListener in useIsKeyboardShown ([#9457](https://github.com/react-navigation/react-navigation/issues/9457)) ([d87857e](https://github.com/react-navigation/react-navigation/commit/d87857e5d93c19ebee2fd84eb4910e36001ec2a3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.5...@react-navigation/bottom-tabs@5.11.6) (2021-01-22)
|
||||
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.2...@react-navigation/bottom-tabs@6.0.0-next.3) (2021-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix pointerEvents in ResourceSavingScene ([60fe0db](https://github.com/react-navigation/react-navigation/commit/60fe0dbb0ae443fdb21016d368c919b933cb64e7)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
* use tab role on Android for accessibility ([de805a3](https://github.com/react-navigation/react-navigation/commit/de805a3ebf35db81cb7b7bcbf5cfd4a03e69c567))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a Background component ([cbaabc1](https://github.com/react-navigation/react-navigation/commit/cbaabc1288e780698e499a00b9ca06ab9746a0da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.4...@react-navigation/bottom-tabs@5.11.5) (2021-01-22)
|
||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.1...@react-navigation/bottom-tabs@6.0.0-next.2) (2021-03-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -33,31 +38,51 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
## [5.11.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.3...@react-navigation/bottom-tabs@5.11.4) (2021-01-21)
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0...@react-navigation/bottom-tabs@6.0.0-next.1) (2021-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([d88cbcb](https://github.com/react-navigation/react-navigation/commit/d88cbcb52d46de26edaa9ce6bfb06badb1b1de64))
|
||||
* fix peer dep versions ([72f90b5](https://github.com/react-navigation/react-navigation/commit/72f90b50d27eda1315bb750beca8a36f26dafe17))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.2...@react-navigation/bottom-tabs@5.11.3) (2021-01-14)
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@6.0.0-next.0) (2021-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enable detachInactiveScreens by default on web for better a11y ([dd87fa4](https://github.com/react-navigation/react-navigation/commit/dd87fa49a43ad8db105a62418243339e4150fadf))
|
||||
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
|
||||
* drop usage of Dimensions in favor of metrics from safe-area-context ([12b893d](https://github.com/react-navigation/react-navigation/commit/12b893d7ca8cdb726b973972797658ac9c7d17d7))
|
||||
* enable detachInactiveScreens by default on web for better a11y ([4954d6a](https://github.com/react-navigation/react-navigation/commit/4954d6aae3cdbb5855d44ff17d80d16b81fb224e))
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([b735de1](https://github.com/react-navigation/react-navigation/commit/b735de153ca650240625dba6d8b5c8d16b913bac))
|
||||
* fix drawer screen content not being interactable on Android ([865d8b3](https://github.com/react-navigation/react-navigation/commit/865d8b3e51e117a01243966c160b7cd147d236ac))
|
||||
* fix initial metrics on server ([69d333f](https://github.com/react-navigation/react-navigation/commit/69d333f6c23e0c37eaf4d3f8b413e8f96d6827f8))
|
||||
* fix pointerEvents in ResourceSavingScene ([af53dd6](https://github.com/react-navigation/react-navigation/commit/af53dd6548630124f831446e0eee468da5d9bf5e)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
* show a missing icon symbol instead of empty area in bottom tab bar ([2bc4882](https://github.com/react-navigation/react-navigation/commit/2bc4882692be9f02d781639892e1f98b891811c4))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* don't use deprecated APIs from react-native-safe-area-context ([ddf27bf](https://github.com/react-navigation/react-navigation/commit/ddf27bf41a2efc5d1573aad0f8fe6c27a98c32b3))
|
||||
* drop support for tabBarVisible option ([a97a43a](https://github.com/react-navigation/react-navigation/commit/a97a43aa1d2a615074ade93f1addebcee1dbfb65))
|
||||
* move `tabBarOptions` to `options` for bottom tabs ([f7ff1ad](https://github.com/react-navigation/react-navigation/commit/f7ff1adee7654b2d624dee4ae8844be217f23026))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
## [5.11.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@5.11.2) (2020-11-20)
|
||||
* initial implementation of @react-navigation/elements ([07ba7a9](https://github.com/react-navigation/react-navigation/commit/07ba7a96870efdb8acf99eb82ba0b1d3eac90bab))
|
||||
* move lazy to options for bottom-tabs and drawer ([068a9a4](https://github.com/react-navigation/react-navigation/commit/068a9a456c31a08104097f2a8434c66c30a5be99))
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
|
||||
* This commit moves options from `tabBarOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the tab bar based on a per screen basis.
|
||||
* We now require newer versions of safe area context library.
|
||||
* We need to add support for specifying style for tab bar in options to support the use cases which need this.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Bottom tab navigator for React Navigation following iOS design guidelines.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/bottom-tab-navigator/).
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/6.x/bottom-tab-navigator/).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.11.7",
|
||||
"version": "6.0.0-next.4",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,29 +36,30 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.4",
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
"warn-once": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^5.9.2",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"typescript": "^4.0.3"
|
||||
"react-native": "~0.63.4",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"react-native-safe-area-context": "~3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
"react-native-safe-area-context": ">= 3.0.0",
|
||||
"react-native-screens": ">= 3.0.0"
|
||||
},
|
||||
"react-native-builder-bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -24,6 +24,5 @@ export type {
|
||||
BottomTabNavigationProp,
|
||||
BottomTabScreenProps,
|
||||
BottomTabBarProps,
|
||||
BottomTabBarOptions,
|
||||
BottomTabBarButtonProps,
|
||||
} from './types';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import warnOnce from 'warn-once';
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
@@ -26,8 +27,49 @@ function BottomTabNavigator({
|
||||
children,
|
||||
screenOptions,
|
||||
sceneContainerStyle,
|
||||
// @ts-expect-error: lazy is deprecated
|
||||
lazy,
|
||||
// @ts-expect-error: tabBarOptions is deprecated
|
||||
tabBarOptions,
|
||||
...rest
|
||||
}: Props) {
|
||||
let defaultScreenOptions: BottomTabNavigationOptions = {};
|
||||
|
||||
if (tabBarOptions) {
|
||||
Object.assign(defaultScreenOptions, {
|
||||
tabBarHideOnKeyboard: tabBarOptions.keyboardHidesTabBar,
|
||||
tabBarActiveTintColor: tabBarOptions.activeTintColor,
|
||||
tabBarInactiveTintColor: tabBarOptions.inactiveTintColor,
|
||||
tabBarActiveBackgroundColor: tabBarOptions.activeBackgroundColor,
|
||||
tabBarInactiveBackgroundColor: tabBarOptions.inactiveBackgroundColor,
|
||||
tabBarAllowFontScaling: tabBarOptions.allowFontScaling,
|
||||
tabBarShowLabel: tabBarOptions.showLabel,
|
||||
tabBarLabelStyle: tabBarOptions.labelStyle,
|
||||
tabBarIconStyle: tabBarOptions.iconStyle,
|
||||
tabBarItemStyle: tabBarOptions.tabStyle,
|
||||
tabBarLabelPosition: tabBarOptions.labelPosition,
|
||||
tabBarAdaptive: tabBarOptions.adaptive,
|
||||
});
|
||||
|
||||
warnOnce(
|
||||
tabBarOptions,
|
||||
`Bottom Tab Navigator: 'tabBarOptions' is deprecated. Migrate the options to 'screenOptions' instead.\n\nPlace the following in 'screenOptions' in your code to keep current behavior:\n\n${JSON.stringify(
|
||||
defaultScreenOptions,
|
||||
null,
|
||||
2
|
||||
)}\n\nSee https://reactnavigation.org/docs/6.x/bottom-tab-navigator#options for more details.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof lazy === 'boolean') {
|
||||
defaultScreenOptions.lazy = lazy;
|
||||
|
||||
warnOnce(
|
||||
true,
|
||||
`Bottom Tab Navigator: 'lazy' in props is deprecated. Move it to 'screenOptions' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
@@ -39,6 +81,7 @@ function BottomTabNavigator({
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
defaultScreenOptions,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,10 @@ import type {
|
||||
TabActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { HeaderOptions } from '@react-navigation/elements';
|
||||
|
||||
export type Layout = { width: number; height: number };
|
||||
|
||||
export type BottomTabNavigationEventMap = {
|
||||
/**
|
||||
@@ -76,16 +80,34 @@ export type TabBarVisibilityAnimationConfig =
|
||||
| TimingKeyboardAnimationConfig
|
||||
| SpringKeyboardAnimationConfig;
|
||||
|
||||
export type BottomTabNavigationOptions = {
|
||||
export type BottomTabNavigationOptions = HeaderOptions & {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Whether this screens should render the first time it's accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render the screen on initial render.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
|
||||
/**
|
||||
* Function that given returns a React Element to display as a header.
|
||||
*/
|
||||
header?: (props: BottomTabHeaderProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether to show the header. Setting this to `false` hides the header.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
headerShown?: boolean;
|
||||
|
||||
/**
|
||||
* Title string of a tab displayed in the tab bar
|
||||
* or a function that given { focused: boolean, color: string, position: 'below-icon' | 'beside-icon' } returns a React.Node to display in tab bar.
|
||||
* When undefined, scene title is used. To hide, see tabBarOptions.showLabel in the previous section.
|
||||
*
|
||||
* When undefined, scene title is used. Use `tabBarShowLabel` to hide the label.
|
||||
*/
|
||||
tabBarLabel?:
|
||||
| string
|
||||
@@ -126,11 +148,6 @@ export type BottomTabNavigationOptions = {
|
||||
*/
|
||||
tabBarTestID?: string;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether the tab bar is visible when this screen is active.
|
||||
*/
|
||||
tabBarVisible?: boolean;
|
||||
|
||||
/**
|
||||
* Animation config for showing and hiding the tab bar.
|
||||
*/
|
||||
@@ -145,6 +162,73 @@ export type BottomTabNavigationOptions = {
|
||||
*/
|
||||
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the active tab.
|
||||
*/
|
||||
tabBarActiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the inactive tabs.
|
||||
*/
|
||||
tabBarInactiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Background color for the active tab.
|
||||
*/
|
||||
tabBarActiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* background color for the inactive tabs.
|
||||
*/
|
||||
tabBarInactiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Whether label font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
tabBarAllowFontScaling?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the tab label should be visible. Defaults to `true`.
|
||||
*/
|
||||
tabBarShowLabel?: boolean;
|
||||
|
||||
/**
|
||||
* Style object for the tab label.
|
||||
*/
|
||||
tabBarLabelStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the tab icon.
|
||||
*/
|
||||
tabBarIconStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the tab item container.
|
||||
*/
|
||||
tabBarItemStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Whether the label is rendered below the icon or beside the icon.
|
||||
* By default, the position is chosen automatically based on device width.
|
||||
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
|
||||
*/
|
||||
tabBarLabelPosition?: LabelPosition;
|
||||
|
||||
/**
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
tabBarAdaptive?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
|
||||
*/
|
||||
tabBarHideOnKeyboard?: boolean;
|
||||
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
|
||||
/**
|
||||
* Whether this screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
@@ -153,30 +237,28 @@ export type BottomTabNavigationOptions = {
|
||||
};
|
||||
|
||||
export type BottomTabDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
TabNavigationState<ParamListBase>,
|
||||
BottomTabNavigationOptions
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
>;
|
||||
|
||||
export type BottomTabDescriptorMap = {
|
||||
[key: string]: BottomTabDescriptor;
|
||||
};
|
||||
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
|
||||
|
||||
export type BottomTabNavigationConfig<T = BottomTabBarOptions> = {
|
||||
/**
|
||||
* Whether the screens should render the first time they are accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render all screens on initial render.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
export type BottomTabNavigationConfig = {
|
||||
/**
|
||||
* Function that returns a React element to display as the tab bar.
|
||||
*/
|
||||
tabBar?: (props: BottomTabBarProps<T>) => React.ReactNode;
|
||||
tabBar?: (props: BottomTabBarProps) => React.ReactNode;
|
||||
/**
|
||||
* Options for the tab bar which will be passed as props to the tab bar component.
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
*/
|
||||
tabBarOptions?: T;
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* 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.
|
||||
@@ -189,77 +271,30 @@ export type BottomTabNavigationConfig<T = BottomTabBarOptions> = {
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type BottomTabBarOptions = {
|
||||
export type BottomTabHeaderProps = {
|
||||
/**
|
||||
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
|
||||
* Layout of the screen.
|
||||
*/
|
||||
keyboardHidesTabBar?: boolean;
|
||||
layout: Layout;
|
||||
/**
|
||||
* Color for the icon and label in the active tab.
|
||||
* Options for the current screen.
|
||||
*/
|
||||
activeTintColor?: string;
|
||||
options: BottomTabNavigationOptions;
|
||||
/**
|
||||
* Color for the icon and label in the inactive tabs.
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
inactiveTintColor?: string;
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
/**
|
||||
* Background color for the active tab.
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
activeBackgroundColor?: string;
|
||||
/**
|
||||
* background color for the inactive tabs.
|
||||
*/
|
||||
inactiveBackgroundColor?: string;
|
||||
/**
|
||||
* Whether label font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
allowFontScaling?: boolean;
|
||||
/**
|
||||
* Whether the tab label should be visible. Defaults to `true`.
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
/**
|
||||
* Style object for the tab label.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the tab icon.
|
||||
*/
|
||||
iconStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the tab container.
|
||||
*/
|
||||
tabStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Whether the label is rendered below the icon or beside the icon.
|
||||
* By default, the position is chosen automatically based on device width.
|
||||
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
|
||||
*/
|
||||
labelPosition?: LabelPosition;
|
||||
/**
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
adaptive?: boolean;
|
||||
/**
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
navigation: BottomTabNavigationProp<ParamListBase>;
|
||||
};
|
||||
|
||||
export type BottomTabBarProps<T = BottomTabBarOptions> = T & {
|
||||
export type BottomTabBarProps = {
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
insets: EdgeInsets;
|
||||
};
|
||||
|
||||
export type BottomTabBarButtonProps = Omit<
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Keyboard, Platform } from 'react-native';
|
||||
import { Keyboard, Platform, EmitterSubscription } from 'react-native';
|
||||
|
||||
export default function useIsKeyboardShown() {
|
||||
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
|
||||
@@ -8,22 +8,22 @@ export default function useIsKeyboardShown() {
|
||||
const handleKeyboardShow = () => setIsKeyboardShown(true);
|
||||
const handleKeyboardHide = () => setIsKeyboardShown(false);
|
||||
|
||||
let subscriptions: EmitterSubscription[];
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
|
||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
|
||||
subscriptions = [
|
||||
Keyboard.addListener('keyboardWillShow', handleKeyboardShow),
|
||||
Keyboard.addListener('keyboardWillHide', handleKeyboardHide),
|
||||
];
|
||||
} else {
|
||||
Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
|
||||
Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
|
||||
subscriptions = [
|
||||
Keyboard.addListener('keyboardDidShow', handleKeyboardShow),
|
||||
Keyboard.addListener('keyboardDidHide', handleKeyboardHide),
|
||||
];
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Keyboard.removeListener('keyboardWillShow', handleKeyboardShow);
|
||||
Keyboard.removeListener('keyboardWillHide', handleKeyboardHide);
|
||||
} else {
|
||||
Keyboard.removeListener('keyboardDidShow', handleKeyboardShow);
|
||||
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
|
||||
}
|
||||
subscriptions.forEach((s) => s.remove());
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { ScaledSize, Dimensions } from 'react-native';
|
||||
|
||||
// This is similar to the new useWindowDimensions hook in react-native
|
||||
// However, we have a custom implementation to support older RN versions
|
||||
export default function useWindowDimensions() {
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
// `height` and `width` maybe undefined during SSR, so we initialize them
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const onChange = ({ window }: { window: ScaledSize }) => {
|
||||
const { width, height } = window;
|
||||
|
||||
setDimensions((d) => {
|
||||
if (width === d.width && height === d.height) {
|
||||
return d;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
});
|
||||
};
|
||||
|
||||
// We might have missed an update before the listener was added
|
||||
// So make sure to update the dimensions
|
||||
onChange({ window: Dimensions.get('window') });
|
||||
|
||||
Dimensions.addEventListener('change', onChange);
|
||||
|
||||
return () => Dimensions.removeEventListener('change', onChange);
|
||||
}, []);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
@@ -17,17 +17,16 @@ import {
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import { useSafeArea, EdgeInsets } from 'react-native-safe-area-context';
|
||||
import { MissingIcon } from '@react-navigation/elements';
|
||||
import { EdgeInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
|
||||
import BottomTabItem from './BottomTabItem';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
|
||||
import type { BottomTabBarProps, LabelPosition } from '../types';
|
||||
import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
|
||||
|
||||
type Props = BottomTabBarProps & {
|
||||
activeTintColor?: string;
|
||||
inactiveTintColor?: string;
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
};
|
||||
|
||||
const DEFAULT_TABBAR_HEIGHT = 49;
|
||||
@@ -38,44 +37,47 @@ const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
type Options = {
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
layout: { height: number; width: number };
|
||||
dimensions: { height: number; width: number };
|
||||
tabStyle: StyleProp<ViewStyle>;
|
||||
labelPosition: LabelPosition | undefined;
|
||||
adaptive: boolean | undefined;
|
||||
};
|
||||
|
||||
const shouldUseHorizontalLabels = ({
|
||||
state,
|
||||
descriptors,
|
||||
layout,
|
||||
dimensions,
|
||||
adaptive = true,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
}: Options) => {
|
||||
if (labelPosition) {
|
||||
return labelPosition === 'beside-icon';
|
||||
const { tabBarLabelPosition, tabBarAdaptive = true } = descriptors[
|
||||
state.routes[state.index].key
|
||||
].options;
|
||||
|
||||
if (tabBarLabelPosition) {
|
||||
return tabBarLabelPosition === 'beside-icon';
|
||||
}
|
||||
|
||||
if (!adaptive) {
|
||||
if (!tabBarAdaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout.width >= 768) {
|
||||
// Screen size matches a tablet
|
||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
const maxTabWidth = state.routes.reduce((acc, route) => {
|
||||
const { tabBarItemStyle } = descriptors[route.key].options;
|
||||
const flattenedStyle = StyleSheet.flatten(tabBarItemStyle);
|
||||
|
||||
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
||||
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.maxWidth;
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
return acc + flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
return acc + flattenedStyle.maxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state.routes.length * maxTabItemWidth <= layout.width;
|
||||
return acc + DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
}, 0);
|
||||
|
||||
return maxTabWidth <= layout.width;
|
||||
} else {
|
||||
return dimensions.width > dimensions.height;
|
||||
}
|
||||
@@ -85,13 +87,15 @@ const getPaddingBottom = (insets: EdgeInsets) =>
|
||||
Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);
|
||||
|
||||
export const getTabBarHeight = ({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
insets,
|
||||
style,
|
||||
...rest
|
||||
}: Options & {
|
||||
insets: EdgeInsets;
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>> | undefined;
|
||||
}) => {
|
||||
// @ts-ignore
|
||||
const customHeight = StyleSheet.flatten(style)?.height;
|
||||
@@ -101,7 +105,12 @@ export const getTabBarHeight = ({
|
||||
}
|
||||
|
||||
const isLandscape = dimensions.width > dimensions.height;
|
||||
const horizontalLabels = shouldUseHorizontalLabels({ dimensions, ...rest });
|
||||
const horizontalLabels = shouldUseHorizontalLabels({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
...rest,
|
||||
});
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
|
||||
if (
|
||||
@@ -120,20 +129,8 @@ export default function BottomTabBar({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
activeBackgroundColor,
|
||||
activeTintColor,
|
||||
adaptive,
|
||||
allowFontScaling,
|
||||
inactiveBackgroundColor,
|
||||
inactiveTintColor,
|
||||
keyboardHidesTabBar = false,
|
||||
labelPosition,
|
||||
labelStyle,
|
||||
iconStyle,
|
||||
safeAreaInsets,
|
||||
showLabel,
|
||||
insets,
|
||||
style,
|
||||
tabStyle,
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
const buildLink = useLinkBuilder();
|
||||
@@ -142,22 +139,26 @@ export default function BottomTabBar({
|
||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||
const focusedOptions = focusedDescriptor.options;
|
||||
|
||||
const dimensions = useWindowDimensions();
|
||||
const {
|
||||
tabBarShowLabel,
|
||||
tabBarHideOnKeyboard = false,
|
||||
tabBarVisibilityAnimationConfig,
|
||||
tabBarStyle,
|
||||
} = focusedOptions;
|
||||
|
||||
const dimensions = useSafeAreaFrame();
|
||||
const isKeyboardShown = useIsKeyboardShown();
|
||||
|
||||
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
||||
|
||||
const shouldShowTabBar =
|
||||
focusedOptions.tabBarVisible !== false &&
|
||||
!(keyboardHidesTabBar && isKeyboardShown);
|
||||
const shouldShowTabBar = !(tabBarHideOnKeyboard && isKeyboardShown);
|
||||
|
||||
const visibilityAnimationConfigRef = React.useRef(
|
||||
focusedOptions.tabBarVisibilityAnimationConfig
|
||||
tabBarVisibilityAnimationConfig
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
visibilityAnimationConfigRef.current =
|
||||
focusedOptions.tabBarVisibilityAnimationConfig;
|
||||
visibilityAnimationConfigRef.current = tabBarVisibilityAnimationConfig;
|
||||
});
|
||||
|
||||
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
|
||||
@@ -212,7 +213,7 @@ export default function BottomTabBar({
|
||||
|
||||
const topBorderWidth =
|
||||
// @ts-ignore
|
||||
StyleSheet.flatten([styles.tabBar, style])?.borderTopWidth;
|
||||
StyleSheet.flatten([styles.tabBar, tabBarStyle])?.borderTopWidth;
|
||||
|
||||
onHeightChange?.(
|
||||
height +
|
||||
@@ -234,34 +235,21 @@ export default function BottomTabBar({
|
||||
|
||||
const { routes } = state;
|
||||
|
||||
const defaultInsets = useSafeArea();
|
||||
|
||||
const insets = {
|
||||
top: safeAreaInsets?.top ?? defaultInsets.top,
|
||||
right: safeAreaInsets?.right ?? defaultInsets.right,
|
||||
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||
};
|
||||
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
state,
|
||||
descriptors,
|
||||
insets,
|
||||
dimensions,
|
||||
layout,
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
style,
|
||||
style: [tabBarStyle, style],
|
||||
});
|
||||
|
||||
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
layout,
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -293,11 +281,15 @@ export default function BottomTabBar({
|
||||
paddingBottom,
|
||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||
},
|
||||
style,
|
||||
tabBarStyle,
|
||||
]}
|
||||
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
<View
|
||||
accessibilityRole="tablist"
|
||||
style={styles.content}
|
||||
onLayout={handleLayout}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
@@ -334,7 +326,7 @@ export default function BottomTabBar({
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string'
|
||||
: typeof label === 'string' && Platform.OS === 'ios'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
|
||||
@@ -353,20 +345,27 @@ export default function BottomTabBar({
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
to={buildLink(route.name, route.params)}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
allowFontScaling={options.tabBarAllowFontScaling}
|
||||
activeTintColor={options.tabBarActiveTintColor}
|
||||
inactiveTintColor={options.tabBarInactiveTintColor}
|
||||
activeBackgroundColor={options.tabBarActiveBackgroundColor}
|
||||
inactiveBackgroundColor={
|
||||
options.tabBarInactiveBackgroundColor
|
||||
}
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
icon={
|
||||
options.tabBarIcon ??
|
||||
(({ color, size }) => (
|
||||
<MissingIcon color={color} size={size} />
|
||||
))
|
||||
}
|
||||
badge={options.tabBarBadge}
|
||||
badgeStyle={options.tabBarBadgeStyle}
|
||||
label={label}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
iconStyle={iconStyle}
|
||||
style={tabStyle}
|
||||
showLabel={tabBarShowLabel}
|
||||
labelStyle={options.tabBarLabelStyle}
|
||||
iconStyle={options.tabBarIconStyle}
|
||||
style={options.tabBarItemStyle}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
StyleProp,
|
||||
@@ -38,7 +37,7 @@ type Props = {
|
||||
/**
|
||||
* Icon to display for the tab.
|
||||
*/
|
||||
icon?: (props: {
|
||||
icon: (props: {
|
||||
focused: boolean;
|
||||
size: number;
|
||||
color: string;
|
||||
@@ -159,13 +158,14 @@ export default function BottomTabBarItem({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
<Pressable
|
||||
{...rest}
|
||||
accessibilityRole={accessibilityRole}
|
||||
onPress={onPress}
|
||||
style={style}
|
||||
>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
{children}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -263,7 +263,8 @@ export default function BottomTabBarItem({
|
||||
onLongPress,
|
||||
testID,
|
||||
accessibilityLabel,
|
||||
accessibilityRole: 'button',
|
||||
// FIXME: accessibilityRole: 'tab' doesn't seem to work as expected on iOS
|
||||
accessibilityRole: Platform.select({ ios: 'button', default: 'tab' }),
|
||||
accessibilityState: { selected: focused },
|
||||
// @ts-expect-error: keep for compatibility with older React Native versions
|
||||
accessibilityStates: focused ? ['selected'] : [],
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
ParamListBase,
|
||||
TabNavigationState,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import {
|
||||
Header,
|
||||
Screen,
|
||||
SafeAreaProviderCompat,
|
||||
getHeaderTitle,
|
||||
} from '@react-navigation/elements';
|
||||
|
||||
import SafeAreaProviderCompat, {
|
||||
initialSafeAreaInsets,
|
||||
} from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import ScreenFallback from './ScreenFallback';
|
||||
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||
@@ -27,6 +23,8 @@ import type {
|
||||
BottomTabDescriptorMap,
|
||||
BottomTabNavigationHelpers,
|
||||
BottomTabBarProps,
|
||||
BottomTabHeaderProps,
|
||||
BottomTabNavigationProp,
|
||||
} from '../types';
|
||||
|
||||
type Props = BottomTabNavigationConfig & {
|
||||
@@ -35,160 +33,130 @@ type Props = BottomTabNavigationConfig & {
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loaded: string[];
|
||||
tabBarHeight: number;
|
||||
};
|
||||
export default function BottomTabView(props: Props) {
|
||||
const {
|
||||
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
safeAreaInsets,
|
||||
detachInactiveScreens = true,
|
||||
sceneContainerStyle,
|
||||
} = props;
|
||||
|
||||
function SceneContent({
|
||||
isFocused,
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
isFocused: boolean;
|
||||
children: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}) {
|
||||
const { colors } = useTheme();
|
||||
const focusedRouteKey = state.routes[state.index].key;
|
||||
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
|
||||
|
||||
return (
|
||||
<View
|
||||
accessibilityElementsHidden={!isFocused}
|
||||
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
|
||||
style={[styles.content, { backgroundColor: colors.background }, style]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default class BottomTabView extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
lazy: true,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const focusedRouteKey = nextProps.state.routes[nextProps.state.index].key;
|
||||
|
||||
return {
|
||||
// Set the current tab to be loaded if it was not loaded before
|
||||
loaded: prevState.loaded.includes(focusedRouteKey)
|
||||
? prevState.loaded
|
||||
: [...prevState.loaded, focusedRouteKey],
|
||||
};
|
||||
if (!loaded.includes(focusedRouteKey)) {
|
||||
setLoaded([...loaded, focusedRouteKey]);
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { state, tabBarOptions } = this.props;
|
||||
|
||||
const dimensions = Dimensions.get('window');
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
|
||||
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
|
||||
getTabBarHeight({
|
||||
state,
|
||||
descriptors,
|
||||
dimensions,
|
||||
layout: { width: dimensions.width, height: 0 },
|
||||
insets: initialSafeAreaInsets,
|
||||
adaptive: tabBarOptions?.adaptive,
|
||||
labelPosition: tabBarOptions?.labelPosition,
|
||||
tabStyle: tabBarOptions?.tabStyle,
|
||||
style: tabBarOptions?.style,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
loaded: [state.routes[state.index].key],
|
||||
tabBarHeight: tabBarHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private renderTabBar = () => {
|
||||
const {
|
||||
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
||||
tabBarOptions,
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
} = this.props;
|
||||
return tabBar({
|
||||
...tabBarOptions,
|
||||
state: state,
|
||||
descriptors: descriptors,
|
||||
navigation: navigation,
|
||||
});
|
||||
};
|
||||
|
||||
private handleTabBarHeightChange = (height: number) => {
|
||||
this.setState((state) => {
|
||||
if (state.tabBarHeight !== height) {
|
||||
return { tabBarHeight: height };
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
lazy,
|
||||
detachInactiveScreens = true,
|
||||
sceneContainerStyle,
|
||||
} = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded, tabBarHeight } = this.state;
|
||||
insets: {
|
||||
...SafeAreaProviderCompat.initialMetrics.insets,
|
||||
...props.safeAreaInsets,
|
||||
},
|
||||
style: descriptors[state.routes[state.index].key].options.tabBarStyle,
|
||||
})
|
||||
);
|
||||
|
||||
const renderTabBar = () => {
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<ScreenContainer
|
||||
// @ts-ignore
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
<SafeAreaInsetsContext.Consumer>
|
||||
{(insets) =>
|
||||
tabBar({
|
||||
state: state,
|
||||
descriptors: descriptors,
|
||||
navigation: navigation,
|
||||
insets: {
|
||||
top: safeAreaInsets?.top ?? insets?.top ?? 0,
|
||||
right: safeAreaInsets?.right ?? insets?.right ?? 0,
|
||||
bottom: safeAreaInsets?.bottom ?? insets?.bottom ?? 0,
|
||||
left: safeAreaInsets?.left ?? insets?.left ?? 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
</SafeAreaInsetsContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
const { routes } = state;
|
||||
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<SceneContent
|
||||
isFocused={isFocused}
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a lazy screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
header = ({ layout, options }: BottomTabHeaderProps) => (
|
||||
<Header
|
||||
{...options}
|
||||
layout={layout}
|
||||
title={getHeaderTitle(options, route.name)}
|
||||
/>
|
||||
),
|
||||
} = descriptor.options;
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
{descriptor.render()}
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider
|
||||
value={this.handleTabBarHeightChange}
|
||||
>
|
||||
{this.renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
{descriptor.render()}
|
||||
</Screen>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</ScreenFallback>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
||||
{renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -196,7 +164,4 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default function ResourceSavingScene({
|
||||
isVisible,
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Props) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen activityState={isVisible ? 2 : 0} style={style} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen active={isVisible ? 1 : 0} style={style} {...rest}>
|
||||
{children}
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
return (
|
||||
<View
|
||||
// @ts-expect-error: hidden exists on web, but not in React Native
|
||||
hidden={!isVisible}
|
||||
style={[
|
||||
{ display: isVisible ? 'flex' : 'none' },
|
||||
styles.container,
|
||||
style,
|
||||
]}
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style]}
|
||||
// box-none doesn't seem to work properly on Android
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
>
|
||||
<View
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
// This is an workaround for a bug where the clipped view never re-appears
|
||||
Platform.OS === 'ios' ? !isVisible : true
|
||||
}
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
style={isVisible ? styles.attached : styles.detached}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
attached: {
|
||||
flex: 1,
|
||||
},
|
||||
detached: {
|
||||
flex: 1,
|
||||
top: FAR_FAR_AWAY,
|
||||
},
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaConsumer,
|
||||
initialWindowSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context';
|
||||
import {
|
||||
getStatusBarHeight,
|
||||
getBottomSpace,
|
||||
} from 'react-native-iphone-x-helper';
|
||||
|
||||
// The provider component for safe area initializes asynchornously
|
||||
// Until the insets are available, there'll be blank screen
|
||||
// To avoid the blank screen, we specify some initial values
|
||||
export const initialSafeAreaInsets = {
|
||||
// Approximate values which are good enough for most cases
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
right: 0,
|
||||
left: 0,
|
||||
// If we are on a newer version of the library, we can get the correct window insets
|
||||
// The component might not be filling the window, but this is good enough for most cases
|
||||
...initialWindowSafeAreaInsets,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
// https://github.com/react-navigation/react-navigation/issues/174
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}}
|
||||
</SafeAreaConsumer>
|
||||
);
|
||||
}
|
||||
41
packages/bottom-tabs/src/views/ScreenFallback.tsx
Normal file
41
packages/bottom-tabs/src/views/ScreenFallback.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
import { ResourceSavingView } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSavingView visible={visible} {...rest}>
|
||||
{children}
|
||||
</ResourceSavingView>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" }
|
||||
{ "path": "../native" },
|
||||
{ "path": "../elements" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
|
||||
@@ -1,810 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.13](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.12...@react-navigation/compat@5.3.13) (2021-01-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.12](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.11...@react-navigation/compat@5.3.12) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.10...@react-navigation/compat@5.3.11) (2021-01-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.9...@react-navigation/compat@5.3.10) (2020-11-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.8...@react-navigation/compat@5.3.9) (2020-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.7...@react-navigation/compat@5.3.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.6...@react-navigation/compat@5.3.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.5...@react-navigation/compat@5.3.6) (2020-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.4...@react-navigation/compat@5.3.5) (2020-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.3...@react-navigation/compat@5.3.4) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.2...@react-navigation/compat@5.3.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.1...@react-navigation/compat@5.3.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.0...@react-navigation/compat@5.3.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.8...@react-navigation/compat@5.3.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.7...@react-navigation/compat@5.2.8) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.6...@react-navigation/compat@5.2.7) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.5...@react-navigation/compat@5.2.6) (2020-09-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.4...@react-navigation/compat@5.2.5) (2020-08-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.3...@react-navigation/compat@5.2.4) (2020-07-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.3) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix false warning due to change in Object.assign in metro preset ([5e358b3](https://github.com/react-navigation/react-navigation/commit/5e358b3aadac7bb186521872d515fff2e571a940)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.2) (2020-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix false warning due to change in Object.assign in metro preset ([240a706](https://github.com/react-navigation/react-navigation/commit/240a706a56220b63d603a52407a738c2872349dd)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.0...@react-navigation/compat@5.2.1) (2020-07-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.28...@react-navigation/compat@5.2.0) (2020-07-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a getComponent prop to lazily specify components ([f418029](https://github.com/react-navigation/react-navigation/commit/f4180295bf22e32c65f6a7ab7089523cb2de58fb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.28](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.27...@react-navigation/compat@5.1.28) (2020-06-25)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.27](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.26...@react-navigation/compat@5.1.27) (2020-06-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* more improvements to types ([d244488](https://github.com/react-navigation/react-navigation/commit/d2444887be227bbbdcfcb13a7f26a8ebb344043e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.26](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.25...@react-navigation/compat@5.1.26) (2020-06-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.25](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.24...@react-navigation/compat@5.1.25) (2020-05-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.24](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.23...@react-navigation/compat@5.1.24) (2020-05-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.23](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.22...@react-navigation/compat@5.1.23) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.22](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.21...@react-navigation/compat@5.1.22) (2020-05-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.21](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.20...@react-navigation/compat@5.1.21) (2020-05-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.17](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.16...@react-navigation/compat@5.1.17) (2020-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.15...@react-navigation/compat@5.1.16) (2020-05-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.14...@react-navigation/compat@5.1.15) (2020-05-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in navigationOptions ([8cbb201](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
|
||||
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6785)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.5...@react-navigation/compat@5.0.6) (2020-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add NavigationEvents ([d69b0db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/d69b0db60455b8789276822ba73f5349db8842d7)), closes [/github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/issuecomment-588268512)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.4...@react-navigation/compat@5.0.5) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.3...@react-navigation/compat@5.0.4) (2020-02-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.2...@react-navigation/compat@5.0.3) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.1...@react-navigation/compat@5.0.2) (2020-02-11)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.34...@react-navigation/compat@5.0.1) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.34](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.33...@react-navigation/compat@5.0.0-alpha.34) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.33](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.32...@react-navigation/compat@5.0.0-alpha.33) (2020-02-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.32](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.31...@react-navigation/compat@5.0.0-alpha.32) (2020-02-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.31](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.31) (2020-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.29) (2020-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.28](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.27...@react-navigation/compat@5.0.0-alpha.28) (2020-01-24)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.27](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.26...@react-navigation/compat@5.0.0-alpha.27) (2020-01-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensure re-render on isFirstRouteInParent change in compat layer ([14ae373](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/14ae3738cf46088e082bd1c60b9dcc6dacacd1bf))
|
||||
* improvements to the compat layer ([2a76dc4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/2a76dc4d3c4cc0365a3afcff6ac321145efed026))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.26](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.25](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.24...@react-navigation/compat@5.0.0-alpha.25) (2020-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/265)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.24](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.23...@react-navigation/compat@5.0.0-alpha.24) (2020-01-13)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.23](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.23) (2020-01-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.22](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.22) (2020-01-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.17...@react-navigation/compat@5.0.0-alpha.18) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.15...@react-navigation/compat@5.0.0-alpha.16) (2019-12-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.14...@react-navigation/compat@5.0.0-alpha.15) (2019-11-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.13...@react-navigation/compat@5.0.0-alpha.14) (2019-11-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.12...@react-navigation/compat@5.0.0-alpha.13) (2019-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.11...@react-navigation/compat@5.0.0-alpha.12) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.10...@react-navigation/compat@5.0.0-alpha.11) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop isFirstRouteInParent method ([#145](https://github.com/react-navigation/navigation-ex/issues/145)) ([3a77107](https://github.com/react-navigation/navigation-ex/commit/3a77107))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.9...@react-navigation/compat@5.0.0-alpha.10) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.8...@react-navigation/compat@5.0.0-alpha.9) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.7...@react-navigation/compat@5.0.0-alpha.8) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.2...@react-navigation/compat@5.0.0-alpha.3) (2019-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw when wrapping a compat navigatofor compat ([8920da6](https://github.com/react-navigation/navigation-ex/commit/8920da6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.1...@react-navigation/compat@5.0.0-alpha.2) (2019-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add createSwitchNavigator ([ff50282](https://github.com/react-navigation/navigation-ex/commit/ff50282))
|
||||
* add withNavigation and withNavigationFocus ([114a5dc](https://github.com/react-navigation/navigation-ex/commit/114a5dc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 5.0.0-alpha.1 (2019-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix dispatching compat actions ([88a560a](https://github.com/react-navigation/navigation-ex/commit/88a560a))
|
||||
* support legacy goBack method ([d83d3de](https://github.com/react-navigation/navigation-ex/commit/d83d3de))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))
|
||||
@@ -1,5 +0,0 @@
|
||||
# `@react-navigation/compat`
|
||||
|
||||
Compatibility layer to write navigator definitions in static configuration format.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/compatibility/).
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import useCompatNavigation from './useCompatNavigation';
|
||||
|
||||
type Props = {
|
||||
getComponent: () => React.ComponentType<any>;
|
||||
};
|
||||
|
||||
function CompatScreen({ getComponent }: Props) {
|
||||
const navigation = useCompatNavigation();
|
||||
const screenProps = React.useContext(ScreenPropsContext);
|
||||
const ScreenComponent = getComponent();
|
||||
|
||||
return <ScreenComponent navigation={navigation} screenProps={screenProps} />;
|
||||
}
|
||||
|
||||
export default React.memo(CompatScreen);
|
||||
@@ -1,13 +0,0 @@
|
||||
import { DrawerActions, DrawerActionType } from '@react-navigation/native';
|
||||
|
||||
export function openDrawer(): DrawerActionType {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer(): DrawerActionType {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer(): DrawerActionType {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { CommonActions, NavigationState } from '@react-navigation/native';
|
||||
|
||||
export function navigate({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
action?: never;
|
||||
}): CommonActions.Action {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `navigate`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return CommonActions.navigate({
|
||||
name: routeName,
|
||||
key: key,
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function back(options?: { key?: null | string }) {
|
||||
return options?.key != null
|
||||
? (state: NavigationState) => ({
|
||||
...CommonActions.goBack(),
|
||||
source: options.key,
|
||||
target: state.key,
|
||||
})
|
||||
: CommonActions.goBack();
|
||||
}
|
||||
|
||||
export function setParams({
|
||||
params,
|
||||
key,
|
||||
}: {
|
||||
params: object;
|
||||
key?: string;
|
||||
}): CommonActions.Action {
|
||||
return {
|
||||
...CommonActions.setParams(params),
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
type Props = {
|
||||
onWillFocus?: () => void;
|
||||
onDidFocus?: () => void;
|
||||
onWillBlur?: () => void;
|
||||
onDidBlur?: () => void;
|
||||
};
|
||||
|
||||
export default function NavigationEvents(props: Props) {
|
||||
const navigation = useNavigation();
|
||||
const propsRef = React.useRef(props);
|
||||
|
||||
React.useEffect(() => {
|
||||
propsRef.current = props;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubFocus = navigation.addListener('focus', () => {
|
||||
propsRef.current.onWillFocus?.();
|
||||
});
|
||||
|
||||
const unsubBlur = navigation.addListener('blur', () => {
|
||||
propsRef.current.onWillBlur?.();
|
||||
});
|
||||
|
||||
// @ts-expect-error: transitionEnd may not exist on this navigator
|
||||
const unsubTransitionEnd = navigation.addListener('transitionEnd', () => {
|
||||
if (navigation.isFocused()) {
|
||||
propsRef.current.onDidFocus?.();
|
||||
} else {
|
||||
propsRef.current.onDidBlur?.();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubFocus();
|
||||
unsubBlur();
|
||||
unsubTransitionEnd();
|
||||
};
|
||||
}, [navigation]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<unknown>(undefined);
|
||||
@@ -1,71 +0,0 @@
|
||||
import {
|
||||
CommonActions,
|
||||
StackActions,
|
||||
StackActionType,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export function reset(): CommonActions.Action {
|
||||
throw new Error(
|
||||
'The legacy `reset` action is not supported. Use the new reset API by accessing the original navigation object at `navigation.original`.'
|
||||
);
|
||||
}
|
||||
|
||||
export function replace({
|
||||
routeName,
|
||||
params,
|
||||
key,
|
||||
newKey,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
key?: string;
|
||||
newKey?: string;
|
||||
action?: never;
|
||||
}): StackActionType {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'REPLACE',
|
||||
payload: {
|
||||
name: routeName,
|
||||
key: newKey,
|
||||
params,
|
||||
},
|
||||
...(key !== undefined ? { source: key } : null),
|
||||
};
|
||||
}
|
||||
|
||||
export function push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
}: {
|
||||
routeName: string;
|
||||
params?: object;
|
||||
action?: never;
|
||||
}): StackActionType {
|
||||
if (action !== undefined) {
|
||||
throw new Error(
|
||||
'Sub-actions are not supported for `push`. Remove the `action` key from the options.'
|
||||
);
|
||||
}
|
||||
|
||||
return StackActions.push(routeName, params);
|
||||
}
|
||||
|
||||
export function pop({ n = 1 }: { n: number }): StackActionType {
|
||||
return StackActions.pop(n);
|
||||
}
|
||||
|
||||
export function popToTop(): StackActionType {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss(): CommonActions.Action {
|
||||
throw new Error('The legacy `dismiss` action is not supported.');
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { TabActions, TabActionType } from '@react-navigation/native';
|
||||
|
||||
export function jumpTo({
|
||||
routeName,
|
||||
key,
|
||||
}: {
|
||||
routeName: string;
|
||||
key?: string;
|
||||
}): TabActionType {
|
||||
if (key === undefined) {
|
||||
return TabActions.jumpTo(routeName);
|
||||
} else {
|
||||
return {
|
||||
...TabActions.jumpTo(routeName),
|
||||
target: key,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
import type {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import * as helpers from './helpers';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
type EventName =
|
||||
| 'action'
|
||||
| 'willFocus'
|
||||
| 'willBlur'
|
||||
| 'didFocus'
|
||||
| 'didBlur'
|
||||
| 'refocus';
|
||||
|
||||
export default function createCompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
>(
|
||||
navigation: NavigationPropType,
|
||||
state:
|
||||
| (RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
})
|
||||
| NavigationState
|
||||
| PartialState<NavigationState>,
|
||||
context: Record<string, any>,
|
||||
isFirstRouteInParent?: boolean
|
||||
): CompatNavigationProp<NavigationPropType> {
|
||||
context.parent = context.parent || {};
|
||||
context.subscriptions = context.subscriptions || {
|
||||
didFocus: new Map<() => void, () => void>(),
|
||||
didBlur: new Map<() => void, () => void>(),
|
||||
refocus: new Map<() => void, () => void>(),
|
||||
};
|
||||
|
||||
return {
|
||||
...navigation,
|
||||
...Object.entries(helpers).reduce<{
|
||||
[key: string]: (...args: any[]) => void;
|
||||
}>((acc, [name, method]: [string, Function]) => {
|
||||
if (name in navigation) {
|
||||
acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
original: navigation,
|
||||
addListener(type: EventName, callback: () => void) {
|
||||
let unsubscribe: () => void;
|
||||
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
unsubscribe = navigation.addListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
unsubscribe = navigation.addListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
context.subscriptions.didFocus.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const listener = () => {
|
||||
if (!navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||
context.subscriptions.didBlur.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const listener = () => {
|
||||
if (navigation.isFocused()) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error: this event may not exist in this navigator
|
||||
unsubscribe = navigation.addListener('tabPress', listener);
|
||||
context.subscriptions.refocus.set(callback, unsubscribe);
|
||||
break;
|
||||
}
|
||||
case 'action':
|
||||
throw new Error("Listening to 'action' events is not supported.");
|
||||
default:
|
||||
unsubscribe = navigation.addListener(type, callback);
|
||||
}
|
||||
|
||||
const subscription = () => unsubscribe();
|
||||
|
||||
subscription.remove = unsubscribe;
|
||||
|
||||
return subscription;
|
||||
},
|
||||
removeListener(type: EventName, callback: () => void) {
|
||||
context.subscriptions = context.subscriptions || {};
|
||||
|
||||
switch (type) {
|
||||
case 'willFocus':
|
||||
navigation.removeListener('focus', callback);
|
||||
break;
|
||||
case 'willBlur':
|
||||
navigation.removeListener('blur', callback);
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const unsubscribe = context.subscriptions.didFocus.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const unsubscribe = context.subscriptions.didBlur.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const unsubscribe = context.subscriptions.refocus.get(callback);
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'action':
|
||||
throw new Error("Listening to 'action' events is not supported.");
|
||||
default:
|
||||
navigation.removeListener(type, callback);
|
||||
}
|
||||
},
|
||||
state: {
|
||||
key: state.key,
|
||||
// @ts-expect-error
|
||||
routeName: state.name,
|
||||
// @ts-expect-error
|
||||
params: state.params ?? {},
|
||||
get index() {
|
||||
// @ts-expect-error
|
||||
if (state.index !== undefined) {
|
||||
// @ts-expect-error
|
||||
return state.index;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Looks like you are using 'navigation.state.index' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'index' property in the child navigation state."
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return state.state?.index;
|
||||
},
|
||||
get routes() {
|
||||
// @ts-expect-error
|
||||
if (state.routes !== undefined) {
|
||||
// @ts-expect-error
|
||||
return state.routes;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Looks like you are using 'navigation.state.routes' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'routes' property in the child navigation state."
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return state.state?.routes;
|
||||
},
|
||||
},
|
||||
getParam<T extends keyof ParamList>(
|
||||
paramName: T,
|
||||
defaultValue: ParamList[T]
|
||||
): ParamList[T] {
|
||||
// @ts-expect-error
|
||||
const params = state.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
return params[paramName];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
isFirstRouteInParent(): boolean {
|
||||
if (typeof isFirstRouteInParent === 'boolean') {
|
||||
return isFirstRouteInParent;
|
||||
}
|
||||
|
||||
const { routes } = navigation.dangerouslyGetState();
|
||||
|
||||
return routes[0].key === state.key;
|
||||
},
|
||||
dangerouslyGetParent() {
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
if (parent) {
|
||||
return createCompatNavigationProp(
|
||||
parent,
|
||||
navigation.dangerouslyGetState(),
|
||||
context.parent
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
ParamListBase,
|
||||
TypedNavigator,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
NavigationRouteContext,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import type { CompatScreenType, CompatRouteConfig } from './types';
|
||||
|
||||
export default function createCompatNavigatorFactory<
|
||||
CreateNavigator extends () => TypedNavigator<
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
{},
|
||||
any,
|
||||
React.ComponentType<any>
|
||||
>
|
||||
>(createNavigator: CreateNavigator) {
|
||||
// @ts-expect-error: isCompat may or may not exist
|
||||
if (createNavigator.isCompat) {
|
||||
throw new Error(
|
||||
`The navigator is already in compat mode. You don't need to wrap it in 'createCompatNavigatorFactory'.`
|
||||
);
|
||||
}
|
||||
|
||||
const createCompatNavigator = <
|
||||
NavigationPropType extends NavigationProp<any, any, any, any, any>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {},
|
||||
NavigationConfig extends {} = React.ComponentProps<
|
||||
ReturnType<CreateNavigator>['Navigator']
|
||||
>
|
||||
>(
|
||||
routeConfig: CompatRouteConfig<NavigationPropType>,
|
||||
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
|
||||
order?: Extract<keyof ParamList, string>[];
|
||||
defaultNavigationOptions?: ScreenOptions;
|
||||
navigationOptions?: Record<string, any>;
|
||||
} = {}
|
||||
) => {
|
||||
const Pair = createNavigator();
|
||||
|
||||
const {
|
||||
order,
|
||||
defaultNavigationOptions,
|
||||
navigationOptions: parentNavigationOptions,
|
||||
...restConfig
|
||||
} = navigationConfig;
|
||||
|
||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const parentRouteParams = React.useContext(NavigationRouteContext)
|
||||
?.params;
|
||||
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map((name) => {
|
||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||
|
||||
let initialParams;
|
||||
|
||||
const routeConfigItem = routeConfig[name];
|
||||
|
||||
if ('getScreen' in routeConfigItem) {
|
||||
getScreenComponent = routeConfigItem.getScreen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else if ('screen' in routeConfigItem) {
|
||||
getScreenComponent = () => routeConfigItem.screen;
|
||||
initialParams = routeConfigItem.params;
|
||||
} else {
|
||||
getScreenComponent = () => routeConfigItem;
|
||||
}
|
||||
|
||||
const screenOptions = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: NavigationPropType;
|
||||
route: RouteProp<ParamList, keyof ParamList> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
}) => {
|
||||
// @ts-expect-error: navigationOptions may exists on the component, but TS is dumb
|
||||
const routeNavigationOptions = routeConfigItem.navigationOptions;
|
||||
const screenNavigationOptions = getScreenComponent()
|
||||
.navigationOptions;
|
||||
|
||||
if (
|
||||
routeNavigationOptions == null &&
|
||||
screenNavigationOptions == null
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options =
|
||||
typeof routeNavigationOptions === 'function' ||
|
||||
typeof screenNavigationOptions === 'function'
|
||||
? {
|
||||
navigation: createCompatNavigationProp<
|
||||
NavigationPropType,
|
||||
ParamList
|
||||
>(navigation, route, {}),
|
||||
navigationOptions: defaultNavigationOptions || {},
|
||||
screenProps,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...(typeof routeNavigationOptions === 'function'
|
||||
? routeNavigationOptions(options)
|
||||
: routeNavigationOptions),
|
||||
...(typeof screenNavigationOptions === 'function'
|
||||
? (screenNavigationOptions as (o: any) => ScreenOptions)(
|
||||
options
|
||||
)
|
||||
: screenNavigationOptions),
|
||||
} as ScreenOptions;
|
||||
};
|
||||
|
||||
return (
|
||||
<Pair.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
initialParams={{ ...parentRouteParams, ...initialParams }}
|
||||
options={screenOptions}
|
||||
>
|
||||
{() => <CompatScreen getComponent={getScreenComponent} />}
|
||||
</Pair.Screen>
|
||||
);
|
||||
}),
|
||||
[parentRouteParams, screenProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenPropsContext.Provider value={screenProps}>
|
||||
<Pair.Navigator
|
||||
{...(restConfig as NavigationConfig)}
|
||||
screenOptions={defaultNavigationOptions}
|
||||
>
|
||||
{screens}
|
||||
</Pair.Navigator>
|
||||
</ScreenPropsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.navigationOptions = parentNavigationOptions;
|
||||
|
||||
return Navigator;
|
||||
};
|
||||
|
||||
Object.defineProperties(createCompatNavigator, {
|
||||
isCompat: {
|
||||
get() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
router: {
|
||||
get() {
|
||||
throw new Error(
|
||||
"It's no longer possible to access the router with the 'router' property."
|
||||
);
|
||||
},
|
||||
set() {
|
||||
throw new Error(
|
||||
"It's no longer possible to override the router by assigning the 'router' property."
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return createCompatNavigator;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
|
||||
|
||||
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
|
||||
|
||||
function SwitchNavigator(props: Props) {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
{},
|
||||
{},
|
||||
TabActionHelpers<ParamListBase>
|
||||
>(TabRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigatorFactory<
|
||||
TabNavigationState<ParamListBase>,
|
||||
{},
|
||||
{},
|
||||
typeof SwitchNavigator
|
||||
>(SwitchNavigator)
|
||||
);
|
||||
@@ -1,88 +0,0 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
|
||||
type NavigateActionPayload = Parameters<typeof NavigationActions.navigate>['0'];
|
||||
|
||||
type NavigateActionType = ReturnType<typeof NavigationActions.navigate>;
|
||||
|
||||
export function navigate(
|
||||
routeName: string,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function navigate(options: NavigateActionPayload): NavigateActionType;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function navigate(
|
||||
options: string | NavigateActionPayload,
|
||||
params?: object,
|
||||
action?: never
|
||||
): NavigateActionType {
|
||||
if (typeof options === 'string') {
|
||||
return NavigationActions.navigate({
|
||||
routeName: options,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
return NavigationActions.navigate(options);
|
||||
}
|
||||
|
||||
export function goBack(fromKey?: null | string) {
|
||||
return NavigationActions.back({ key: fromKey });
|
||||
}
|
||||
|
||||
export function setParams(params: object) {
|
||||
return NavigationActions.setParams({ params });
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
return StackActions.reset();
|
||||
}
|
||||
|
||||
export function replace(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.replace({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function push(routeName: string, params?: object, action?: never) {
|
||||
return StackActions.push({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
export function pop(n: number = 1) {
|
||||
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
||||
}
|
||||
|
||||
export function popToTop() {
|
||||
return StackActions.popToTop();
|
||||
}
|
||||
|
||||
export function dismiss() {
|
||||
return StackActions.dismiss();
|
||||
}
|
||||
|
||||
export function jumpTo(routeName: string) {
|
||||
return SwitchActions.jumpTo({ routeName });
|
||||
}
|
||||
|
||||
export function openDrawer() {
|
||||
return DrawerActions.openDrawer();
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
return DrawerActions.closeDrawer();
|
||||
}
|
||||
|
||||
export function toggleDrawer() {
|
||||
return DrawerActions.toggleDrawer();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as NavigationActions from './NavigationActions';
|
||||
import * as StackActions from './StackActions';
|
||||
import * as DrawerActions from './DrawerActions';
|
||||
import * as SwitchActions from './SwitchActions';
|
||||
|
||||
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
|
||||
|
||||
export { default as createCompatNavigatorFactory } from './createCompatNavigatorFactory';
|
||||
|
||||
export { default as createCompatNavigationProp } from './createCompatNavigationProp';
|
||||
|
||||
export { default as createSwitchNavigator } from './createSwitchNavigator';
|
||||
|
||||
export { default as withNavigation } from './withNavigation';
|
||||
export { default as withNavigationFocus } from './withNavigationFocus';
|
||||
|
||||
export { default as NavigationEvents } from './NavigationEvents';
|
||||
|
||||
export * from './types';
|
||||
@@ -1,92 +0,0 @@
|
||||
import type {
|
||||
ParamListBase,
|
||||
NavigationProp,
|
||||
Route,
|
||||
} from '@react-navigation/native';
|
||||
import type * as helpers from './helpers';
|
||||
|
||||
export type CompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
RouteName extends Extract<keyof ParamList, string> = Extract<
|
||||
NavigationPropType extends NavigationProp<any, infer R> ? R : string,
|
||||
string
|
||||
>
|
||||
> = Omit<NavigationPropType, keyof typeof helpers> &
|
||||
{
|
||||
[method in Extract<keyof NavigationPropType, keyof typeof helpers>]: (
|
||||
...args: Parameters<typeof helpers[method]>
|
||||
) => void;
|
||||
} & {
|
||||
state: Route<RouteName> & {
|
||||
routeName: RouteName;
|
||||
};
|
||||
getParam<T extends keyof ParamList[RouteName]>(
|
||||
paramName: T,
|
||||
defaultValue?: ParamList[RouteName][T]
|
||||
): ParamList[RouteName][T];
|
||||
isFirstRouteInParent(): boolean;
|
||||
dangerouslyGetParent<
|
||||
T = NavigationProp<ParamListBase> | undefined
|
||||
>(): T extends NavigationProp<ParamListBase>
|
||||
? CompatNavigationProp<T>
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export type CompatNavigationOptions<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
infer O
|
||||
>
|
||||
? O
|
||||
: {}
|
||||
> =
|
||||
| ((options: {
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
navigationOptions: Partial<ScreenOptions>;
|
||||
screenProps: unknown;
|
||||
}) => ScreenOptions)
|
||||
| ScreenOptions;
|
||||
|
||||
export type CompatScreenType<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>
|
||||
> = React.ComponentType<{
|
||||
navigation: CompatNavigationProp<NavigationPropType>;
|
||||
screenProps: unknown;
|
||||
}> & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
};
|
||||
|
||||
export type CompatRouteConfig<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
> = {
|
||||
[RouteName in keyof ParamList]:
|
||||
| React.ComponentType<any>
|
||||
| ((
|
||||
| { screen: React.ComponentType<any> }
|
||||
| { getScreen(): React.ComponentType<any> }
|
||||
) & {
|
||||
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
|
||||
params?: ParamList[RouteName];
|
||||
});
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
useNavigation,
|
||||
useRoute,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
useNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
export default function useCompatNavigation<
|
||||
T extends NavigationProp<ParamListBase>
|
||||
>() {
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
(state) => state.routes[0].key === route.key
|
||||
);
|
||||
|
||||
const context = React.useRef<Record<string, any>>({});
|
||||
|
||||
return React.useMemo(
|
||||
() =>
|
||||
createCompatNavigationProp(
|
||||
navigation,
|
||||
route as any,
|
||||
context.current,
|
||||
isFirstRouteInParent
|
||||
) as CompatNavigationProp<T>,
|
||||
[isFirstRouteInParent, navigation, route]
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import type { NavigationProp, ParamListBase } from '@react-navigation/native';
|
||||
import useCompatNavigation from './useCompatNavigation';
|
||||
import type { CompatNavigationProp } from './types';
|
||||
|
||||
type InjectedProps<T extends NavigationProp<ParamListBase>> = {
|
||||
navigation: CompatNavigationProp<T>;
|
||||
};
|
||||
|
||||
export default function withNavigation<
|
||||
T extends NavigationProp<ParamListBase>,
|
||||
P extends InjectedProps<T>,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps<T>> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const navigation = useCompatNavigation<T>();
|
||||
|
||||
// @ts-expect-error: type checking HOC is hard
|
||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigation(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
type InjectedProps = {
|
||||
isFocused: boolean;
|
||||
};
|
||||
|
||||
export default function withNavigationFocus<
|
||||
P extends InjectedProps,
|
||||
C extends React.ComponentType<P>
|
||||
>(Comp: C) {
|
||||
const WrappedComponent = ({
|
||||
onRef,
|
||||
...rest
|
||||
}: Exclude<P, InjectedProps> & {
|
||||
onRef?: C extends React.ComponentClass<any>
|
||||
? React.Ref<InstanceType<C>>
|
||||
: never;
|
||||
}): React.ReactElement => {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
// @ts-expect-error: type checking HOC is hard
|
||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigationFocus(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
@@ -3,7 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.15.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.15.0...@react-navigation/core@5.15.1) (2021-01-21)
|
||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.1...@react-navigation/core@6.0.0-next.2) (2021-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly resolve initialRouteNames ([c38906a](https://github.com/react-navigation/react-navigation/commit/c38906a7a09b997f37ce56734ea823c75ea744db))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve useNavigationState typing ([#9464](https://github.com/react-navigation/react-navigation/issues/9464)) ([84020a0](https://github.com/react-navigation/react-navigation/commit/84020a0b27ebae50d3037438a51d95eb31b02424))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0...@react-navigation/core@6.0.0-next.1) (2021-03-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
@@ -11,28 +27,50 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
# [5.15.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.4...@react-navigation/core@5.15.0) (2021-01-14)
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.3...@react-navigation/core@6.0.0-next.0) (2021-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* print an error when passing a second argument to useFocusEffect ([2317633](https://github.com/react-navigation/react-navigation/commit/23176336528f98924d19f321d41cb70f13300edd))
|
||||
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
|
||||
* check duplicate names only for immediate nested screens ([36a9b4f](https://github.com/react-navigation/react-navigation/commit/36a9b4f866c49d6f7350405c54b86bd77e374eb5))
|
||||
* don't merge params on navigation ([366d018](https://github.com/react-navigation/react-navigation/commit/366d0181dc02597b8d11e343f37fc77bee164c70))
|
||||
* drop dangerously prefix from getState and getParent ([227f133](https://github.com/react-navigation/react-navigation/commit/227f133536af85dc5ff85eeb269b76ed80cd3f05))
|
||||
* drop support for legacy linking config ([0e13e8d](https://github.com/react-navigation/react-navigation/commit/0e13e8d23cc2ea74f3b0fce9334ee5c8be2484f4))
|
||||
* fix default screen options not being respected ([03ba1f2](https://github.com/react-navigation/react-navigation/commit/03ba1f2930cb731266e6bc3044c67fb267837ed1))
|
||||
* fix incorrect state change events in independent nested container ([b82a912](https://github.com/react-navigation/react-navigation/commit/b82a9126bb91a84e21473723ce40da0cce732a39)), closes [#9080](https://github.com/react-navigation/react-navigation/issues/9080)
|
||||
* print an error when passing a second argument to useFocusEffect ([c361795](https://github.com/react-navigation/react-navigation/commit/c361795d97eb20150913ddc3fbf139e095d25830))
|
||||
* remove the state property from route prop ([ebab518](https://github.com/react-navigation/react-navigation/commit/ebab5183522f5ae03f50f88289c0e7acc208dc02))
|
||||
* show redbox instead of crash if navigation isn't initialized ([13d8553](https://github.com/react-navigation/react-navigation/commit/13d85530ae684eb956d3c4df25919572caf0ec1a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a way to specify an unique ID for screens ([b19f76b](https://github.com/react-navigation/react-navigation/commit/b19f76bfffe623759e67d925bfd067c753a453bf))
|
||||
* add a way to specify an unique ID for screens ([15b8bb3](https://github.com/react-navigation/react-navigation/commit/15b8bb34584db3cb166f6aafd45f0b95f14fde62))
|
||||
* add an option to specify default options for the navigator ([c85f2ff](https://github.com/react-navigation/react-navigation/commit/c85f2ff47a2b3d403a3cbe993b46d04914358ba5))
|
||||
* allow returning null or undefined to skip actions with dispatch ([d6466b7](https://github.com/react-navigation/react-navigation/commit/d6466b7a4b5d3087981dbd50a5f5f56a6092edb3))
|
||||
* associate path with the route it opens when deep linking ([#9384](https://github.com/react-navigation/react-navigation/issues/9384)) ([86e64fd](https://github.com/react-navigation/react-navigation/commit/86e64fdcd81a57cf3f3bdab4c9035b52984e7009)), closes [#9102](https://github.com/react-navigation/react-navigation/issues/9102)
|
||||
* warn on duplicate screen names across navigators ([02a031e](https://github.com/react-navigation/react-navigation/commit/02a031e46eac432fc3e26c5de30c7bdf0a81ce49))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit drops support for legacy linking config which allowed screens to be specified without the screens property in the config.
|
||||
* Previous versions of React Navigation merged params on navigation which caused confusion. This commit changes params not to be merged.
|
||||
|
||||
The old behaviour can still be achieved by passing `merge: true` explicitly:
|
||||
|
||||
## [5.14.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.3...@react-navigation/core@5.14.4) (2020-11-20)
|
||||
```js
|
||||
CommonActions.navigate({
|
||||
name: 'bar',
|
||||
params: { fruit: 'orange' },
|
||||
merge: true,
|
||||
})
|
||||
```
|
||||
`initialParams` specified for the screen are always merged.
|
||||
* any code which relies on `route.state` will break.
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix incorrect state change events in independent nested container ([95b2599](https://github.com/react-navigation/react-navigation/commit/95b2599877f5ceedf753e399e0586bb4af54cb87)), closes [#9080](https://github.com/react-navigation/react-navigation/issues/9080)
|
||||
Previous versions printed a warning on accessing `route.state`. This commit removes the property entirely. Accessing this property isn't safe since child navigator state isn't gurranteed to be in sync with parent navigator state and cause subtle bugs in apps.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.15.1",
|
||||
"version": "6.0.0-next.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -35,21 +35,22 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.7.1",
|
||||
"@react-navigation/routers": "^6.0.0-next.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.15",
|
||||
"query-string": "^6.13.6",
|
||||
"nanoid": "^3.1.22",
|
||||
"query-string": "^7.0.0",
|
||||
"react-is": "^16.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"del-cli": "^3.0.1",
|
||||
"immer": "^9.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"react-test-renderer": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
||||
@@ -20,6 +20,8 @@ import useOptionsGetters from './useOptionsGetters';
|
||||
import useEventEmitter from './useEventEmitter';
|
||||
import useSyncState from './useSyncState';
|
||||
import checkSerializable from './checkSerializable';
|
||||
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import type {
|
||||
NavigationContainerEventMap,
|
||||
NavigationContainerRef,
|
||||
@@ -32,6 +34,7 @@ const NOT_INITIALIZED_ERROR =
|
||||
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
|
||||
|
||||
const serializableWarnings: string[] = [];
|
||||
const duplicateNameWarnings: string[] = [];
|
||||
|
||||
try {
|
||||
/**
|
||||
@@ -106,7 +109,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
|
||||
if (!parent.isDefault && !independent) {
|
||||
throw new Error(
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
|
||||
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,10 +141,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationAction)
|
||||
) => {
|
||||
if (listeners.focus[0] == null) {
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
listeners.focus[0]((navigation) => navigation.dispatch(action));
|
||||
}
|
||||
|
||||
listeners.focus[0]((navigation) => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
@@ -165,15 +168,15 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const target = state?.key ?? keyedListeners.getState.root?.().key;
|
||||
|
||||
if (target == null) {
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
listeners.focus[0]((navigation) =>
|
||||
navigation.dispatch({
|
||||
...CommonActions.reset(state),
|
||||
target,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
listeners.focus[0]((navigation) =>
|
||||
navigation.dispatch({
|
||||
...CommonActions.reset(state),
|
||||
target,
|
||||
})
|
||||
);
|
||||
},
|
||||
[keyedListeners.getState, listeners.focus]
|
||||
);
|
||||
@@ -183,14 +186,15 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
}, [keyedListeners.getState]);
|
||||
|
||||
const getCurrentRoute = React.useCallback(() => {
|
||||
let state = getRootState();
|
||||
if (state === undefined) {
|
||||
const state = getRootState();
|
||||
|
||||
if (state == null) {
|
||||
return undefined;
|
||||
}
|
||||
while (state.routes[state.index].state !== undefined) {
|
||||
state = state.routes[state.index].state as NavigationState;
|
||||
}
|
||||
return state.routes[state.index];
|
||||
|
||||
const route = findFocusedRoute(state);
|
||||
|
||||
return route as Route<string> | undefined;
|
||||
}, [getRootState]);
|
||||
|
||||
const emitter = useEventEmitter<NavigationContainerEventMap>();
|
||||
@@ -198,9 +202,9 @@ 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
|
||||
) as (keyof typeof CommonActions)[]).reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
dispatch(
|
||||
CommonActions[name](
|
||||
@@ -215,8 +219,8 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
dispatch,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
dangerouslyGetState: () => state,
|
||||
dangerouslyGetParent: () => undefined,
|
||||
getState: () => state,
|
||||
getParent: () => undefined,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
}));
|
||||
@@ -294,15 +298,17 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (state !== undefined) {
|
||||
const result = checkSerializable(state);
|
||||
const hydratedState = getRootState();
|
||||
|
||||
if (!result.serializable) {
|
||||
const { location, reason } = result;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (hydratedState !== undefined) {
|
||||
const serializableResult = checkSerializable(hydratedState);
|
||||
|
||||
if (!serializableResult.serializable) {
|
||||
const { location, reason } = serializableResult;
|
||||
|
||||
let path = '';
|
||||
let pointer: Record<any, any> = state;
|
||||
let pointer: Record<any, any> = hydratedState;
|
||||
let params = false;
|
||||
|
||||
for (let i = 0; i < location.length; i++) {
|
||||
@@ -344,13 +350,28 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateRouteNamesResult = checkDuplicateRouteNames(
|
||||
hydratedState
|
||||
);
|
||||
|
||||
if (duplicateRouteNamesResult.length) {
|
||||
const message = `Found screens with the same name nested inside one another. Check:\n${duplicateRouteNamesResult.map(
|
||||
(locations) => `\n${locations.join(', ')}`
|
||||
)}\n\nThis can cause confusing behavior during navigation. Consider using unique names for each screen instead.`;
|
||||
|
||||
if (!duplicateNameWarnings.includes(message)) {
|
||||
duplicateNameWarnings.push(message);
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitter.emit({ type: 'state', data: { state } });
|
||||
|
||||
if (!isFirstMountRef.current && onStateChangeRef.current) {
|
||||
onStateChangeRef.current(getRootState());
|
||||
onStateChangeRef.current(hydratedState);
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
|
||||
@@ -141,10 +141,10 @@ it('handle dispatching with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routes: [
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -163,8 +163,8 @@ it('handle dispatching with ref', () => {
|
||||
<Screen name="foo2">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
<Screen name="qux1">{() => null}</Screen>
|
||||
<Screen name="lex1">{() => null}</Screen>
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -172,8 +172,8 @@ it('handle dispatching with ref', () => {
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
<Screen name="qux2">{() => null}</Screen>
|
||||
<Screen name="lex2">{() => null}</Screen>
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -203,10 +203,10 @@ it('handle dispatching with ref', () => {
|
||||
type: 'test',
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routes: [
|
||||
{ key: 'lex', name: 'lex' },
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -237,8 +237,8 @@ it('handle resetting state with ref', () => {
|
||||
<Screen name="foo2">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
<Screen name="qux1">{() => null}</Screen>
|
||||
<Screen name="lex1">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -246,8 +246,8 @@ it('handle resetting state with ref', () => {
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
<Screen name="lex">{() => null}</Screen>
|
||||
<Screen name="qux2">{() => null}</Screen>
|
||||
<Screen name="lex2">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
@@ -266,10 +266,10 @@ it('handle resetting state with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routes: [
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -293,10 +293,10 @@ it('handle resetting state with ref', () => {
|
||||
state: {
|
||||
index: 0,
|
||||
key: '6',
|
||||
routeNames: ['qux', 'lex'],
|
||||
routeNames: ['qux2', 'lex2'],
|
||||
routes: [
|
||||
{ key: 'qux', name: 'qux' },
|
||||
{ key: 'lex', name: 'lex' },
|
||||
{ key: 'qux2', name: 'qux2' },
|
||||
{ key: 'lex2', name: 'lex2' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
@@ -683,11 +683,15 @@ it('throws if there is no navigator rendered', () => {
|
||||
|
||||
render(element);
|
||||
|
||||
act(() => {
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
});
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
ref.current?.dispatch({ type: 'WHATEVER' });
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("throws if the ref hasn't finished initializing", () => {
|
||||
@@ -703,9 +707,15 @@ it("throws if the ref hasn't finished initializing", () => {
|
||||
|
||||
const TestScreen = () => {
|
||||
React.useEffect(() => {
|
||||
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
ref.current?.dispatch({ type: 'WHATEVER' });
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The 'navigation' object hasn't been initialized yet."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
@@ -821,3 +831,85 @@ it('works with state change events in independent nested container', () => {
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('warns for duplicate route names nested inside each other', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{descriptors[state.routes[state.index].key].render()}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = () => <></>;
|
||||
|
||||
const spy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
'Found screens with the same name nested inside one another.'
|
||||
);
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="qux">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy.mock.calls[1][0]).toMatch(
|
||||
'Found screens with the same name nested inside one another.'
|
||||
);
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator initialRouteName="bar">
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="baz" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ it('gets navigate action from state', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -35,6 +36,7 @@ it('gets navigate action from state', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
path: '/foo/bar',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
@@ -51,6 +53,7 @@ it('gets navigate action from state for top-level screen', () => {
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -59,6 +62,7 @@ it('gets navigate action from state for top-level screen', () => {
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -80,6 +84,7 @@ it('gets reset action from state with 1 route with key at root', () => {
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -102,7 +107,12 @@ it('gets reset action from state with 1 route with key at root', () => {
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{ key: 'test', name: 'qux', params: { author: 'jane' } },
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -125,6 +135,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -139,6 +150,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -197,6 +209,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
|
||||
name: 'bar',
|
||||
key: 'test',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -219,6 +232,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
|
||||
name: 'bar',
|
||||
key: 'test',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -236,6 +250,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -251,6 +266,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -267,6 +283,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'baz' },
|
||||
],
|
||||
@@ -283,6 +300,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -303,7 +321,7 @@ it('gets navigate action from state with 2 screens', () => {
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -328,7 +346,7 @@ it('gets navigate action from state with 2 screens', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -353,6 +371,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
@@ -376,6 +395,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
path: '/foo/bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -450,6 +470,7 @@ it('gets navigate action from state with config', () => {
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -483,6 +504,7 @@ it('gets navigate action from state with config', () => {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
path: '/foo/bar',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
@@ -499,6 +521,7 @@ it('gets navigate action from state for top-level screen with config', () => {
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -519,6 +542,7 @@ it('gets navigate action from state for top-level screen with config', () => {
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
@@ -539,7 +563,7 @@ it('gets navigate action from state with 2 screens including initial route and w
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -571,6 +595,7 @@ it('gets navigate action from state with 2 screens including initial route and w
|
||||
params: {
|
||||
screen: 'quz',
|
||||
initial: false,
|
||||
path: '/foo/bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -593,7 +618,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -631,7 +656,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'quz', path: '/foo/bar' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -856,6 +881,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
path: '/foo/bar',
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
@@ -889,6 +915,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
|
||||
params: {
|
||||
screen: 'qux',
|
||||
initial: false,
|
||||
path: '/foo/bar',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import getFocusedRouteNameFromRoute from '../getFocusedRouteNameFromRoute';
|
||||
import { CHILD_STATE } from '../useRouteCache';
|
||||
|
||||
it('gets undefined if there is no nested state', () => {
|
||||
expect(getFocusedRouteNameFromRoute({ name: 'Home' })).toBe(undefined);
|
||||
@@ -8,6 +9,7 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
routes: [{ name: 'Article' }],
|
||||
},
|
||||
@@ -17,6 +19,7 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
|
||||
@@ -27,6 +30,7 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
@@ -36,6 +40,7 @@ it('gets focused route name from nested state', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
state: {
|
||||
type: 'tab',
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
@@ -44,6 +49,50 @@ it('gets focused route name from nested state', () => {
|
||||
).toBe('Article');
|
||||
});
|
||||
|
||||
it('gets focused route name from nested state with symbol', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
routes: [{ name: 'Article' }],
|
||||
},
|
||||
})
|
||||
).toBe('Article');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
index: 1,
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
|
||||
},
|
||||
})
|
||||
).toBe('Chat');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
})
|
||||
).toBe('Chat');
|
||||
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
name: 'Home',
|
||||
// @ts-expect-error: this isn't in the type defs
|
||||
[CHILD_STATE]: {
|
||||
type: 'tab',
|
||||
routes: [{ name: 'Article' }, { name: 'Chat' }],
|
||||
},
|
||||
})
|
||||
).toBe('Article');
|
||||
});
|
||||
|
||||
it('gets nested screen in params if present', () => {
|
||||
expect(
|
||||
getFocusedRouteNameFromRoute({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -550,7 +550,7 @@ it('updates route params with setParams applied to parent', () => {
|
||||
let setParams: (params: object) => void = () => undefined;
|
||||
|
||||
const FooScreen = (props: any) => {
|
||||
const parent = props.navigation.dangerouslyGetParent();
|
||||
const parent = props.navigation.getParent();
|
||||
if (parent) {
|
||||
setParams = parent.setParams;
|
||||
}
|
||||
@@ -1306,7 +1306,7 @@ it('gives access to internal state', () => {
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
state = navigation.dangerouslyGetState();
|
||||
state = navigation.getState();
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -600,14 +600,14 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
||||
const root = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="baz">
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux" component={TestScreen} />
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="qux">
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<OverrodeNavigator>
|
||||
<Screen name="qux">{() => null}</Screen>
|
||||
|
||||
@@ -44,7 +44,7 @@ it("gets navigation's parent from context", () => {
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
expect(navigation.dangerouslyGetParent()).toBeDefined();
|
||||
expect(navigation.getParent()).toBeDefined();
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -75,7 +75,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
|
||||
const Test = () => {
|
||||
const navigation = useNavigation();
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
const parent = navigation.getParent();
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
if (parent !== undefined) {
|
||||
@@ -91,7 +91,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="quo" component={Test} />
|
||||
|
||||
33
packages/core/src/checkDuplicateRouteNames.tsx
Normal file
33
packages/core/src/checkDuplicateRouteNames.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { NavigationState, PartialState } from '@react-navigation/routers';
|
||||
|
||||
export default function checkDuplicateRouteNames(state: NavigationState) {
|
||||
const duplicates: string[][] = [];
|
||||
|
||||
const getRouteNames = (
|
||||
location: string,
|
||||
state: NavigationState | PartialState<NavigationState>
|
||||
) => {
|
||||
state.routes.forEach((route: typeof state.routes[0]) => {
|
||||
const currentLocation = location
|
||||
? `${location} > ${route.name}`
|
||||
: route.name;
|
||||
|
||||
route.state?.routeNames?.forEach((routeName) => {
|
||||
if (routeName === route.name) {
|
||||
duplicates.push([
|
||||
currentLocation,
|
||||
`${currentLocation} > ${route.name}`,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if (route.state) {
|
||||
getRouteNames(currentLocation, route.state);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getRouteNames('', state);
|
||||
|
||||
return duplicates;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { PathConfigMap } from './types';
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
screens: PathConfigMap;
|
||||
};
|
||||
|
||||
export default function checkLegacyPathConfig(
|
||||
config?: Options
|
||||
): [boolean, Options | undefined] {
|
||||
let legacy = false;
|
||||
|
||||
if (config) {
|
||||
// Assume legacy configuration if config has any other keys except `screens` and `initialRouteName`
|
||||
legacy = Object.keys(config).some(
|
||||
(key) => key !== 'screens' && key !== 'initialRouteName'
|
||||
);
|
||||
|
||||
if (
|
||||
legacy &&
|
||||
(config.hasOwnProperty('screens') ||
|
||||
config.hasOwnProperty('initialRouteName'))
|
||||
) {
|
||||
throw new Error(
|
||||
'Found invalid keys in the configuration object. See https://reactnavigation.org/docs/configuring-links/ for more details on the shape of the configuration object.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (legacy) {
|
||||
// @ts-expect-error: we have incorrect type for config since we don't type legacy config
|
||||
return [legacy, { screens: config }];
|
||||
}
|
||||
|
||||
return [legacy, config];
|
||||
}
|
||||
13
packages/core/src/findFocusedRoute.tsx
Normal file
13
packages/core/src/findFocusedRoute.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { InitialState } from '@react-navigation/routers';
|
||||
|
||||
export default function findFocusedRoute(state: InitialState) {
|
||||
let current: InitialState | undefined = state;
|
||||
|
||||
while (current?.routes[current.index ?? 0].state != null) {
|
||||
current = current.routes[current.index ?? 0].state;
|
||||
}
|
||||
|
||||
const route = current?.routes[current?.index ?? 0];
|
||||
|
||||
return route;
|
||||
}
|
||||
13
packages/core/src/fromEntries.tsx
Normal file
13
packages/core/src/fromEntries.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// Object.fromEntries is not available in older iOS versions
|
||||
export default function fromEntries<K extends string, V>(
|
||||
entries: (readonly [K, V])[]
|
||||
) {
|
||||
return entries.reduce((acc, [k, v]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ type NavigateAction<State extends NavigationState> = {
|
||||
payload: {
|
||||
name: string;
|
||||
params?: NavigatorScreenParams<State>;
|
||||
path?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -61,7 +62,9 @@ export default function getActionFromState(
|
||||
NavigationState
|
||||
>;
|
||||
|
||||
let payload = route ? { name: route.name, params } : undefined;
|
||||
let payload = route
|
||||
? { name: route.name, path: route.path, params }
|
||||
: undefined;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 0) {
|
||||
@@ -107,6 +110,7 @@ export default function getActionFromState(
|
||||
NavigationState
|
||||
>;
|
||||
} else {
|
||||
params.path = route.path;
|
||||
params.params = route.params;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import type {
|
||||
Route,
|
||||
PartialState,
|
||||
NavigationState,
|
||||
} from '@react-navigation/routers';
|
||||
import { SUPPRESS_STATE_ACCESS_WARNING } from './useRouteCache';
|
||||
import type { Route } from '@react-navigation/routers';
|
||||
import { CHILD_STATE } from './useRouteCache';
|
||||
|
||||
export default function getFocusedRouteNameFromRoute(
|
||||
route: Partial<Route<string>> & { state?: PartialState<NavigationState> }
|
||||
route: Partial<Route<string>>
|
||||
): string | undefined {
|
||||
SUPPRESS_STATE_ACCESS_WARNING.value = true;
|
||||
|
||||
const state = route.state;
|
||||
|
||||
SUPPRESS_STATE_ACCESS_WARNING.value = false;
|
||||
|
||||
// @ts-expect-error: this isn't in type definitions coz we want this private
|
||||
const state = route[CHILD_STATE] ?? route.state;
|
||||
const params = route.params as { screen?: unknown } | undefined;
|
||||
|
||||
const routeName = state
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import checkLegacyPathConfig from './checkLegacyPathConfig';
|
||||
import fromEntries from './fromEntries';
|
||||
import type { PathConfig, PathConfigMap } from './types';
|
||||
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
@@ -71,11 +71,9 @@ export default function getPathFromState(
|
||||
);
|
||||
}
|
||||
|
||||
const [legacy, compatOptions] = checkLegacyPathConfig(options);
|
||||
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const configs: Record<string, ConfigItem> = compatOptions
|
||||
? createNormalizedConfigs(legacy, compatOptions.screens)
|
||||
const configs: Record<string, ConfigItem> = options?.screens
|
||||
? createNormalizedConfigs(options?.screens)
|
||||
: {};
|
||||
|
||||
let path = '/';
|
||||
@@ -177,12 +175,6 @@ export default function getPathFromState(
|
||||
// Showing the route name seems ok, though whatever we show here will be incorrect
|
||||
// Since the page doesn't actually exist
|
||||
if (p === '*') {
|
||||
if (legacy) {
|
||||
throw new Error(
|
||||
"Please update your config to the new format to use wildcard pattern ('*'). https://reactnavigation.org/docs/configuring-links/#updating-config"
|
||||
);
|
||||
}
|
||||
|
||||
return route.name;
|
||||
}
|
||||
|
||||
@@ -219,7 +211,7 @@ export default function getPathFromState(
|
||||
}
|
||||
}
|
||||
|
||||
const query = queryString.stringify(focusedParams);
|
||||
const query = queryString.stringify(focusedParams, { sort: false });
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
@@ -236,17 +228,6 @@ export default function getPathFromState(
|
||||
return path;
|
||||
}
|
||||
|
||||
// Object.fromEntries is not available in older iOS versions
|
||||
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
||||
entries.reduce((acc, [k, v]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
|
||||
const getParamName = (pattern: string) =>
|
||||
pattern.replace(/^:/, '').replace(/\?$/, '');
|
||||
|
||||
@@ -257,7 +238,6 @@ const joinPaths = (...paths: string[]): string =>
|
||||
.join('/');
|
||||
|
||||
const createConfigItem = (
|
||||
legacy: boolean,
|
||||
config: PathConfig | string,
|
||||
parentPattern?: string
|
||||
): ConfigItem => {
|
||||
@@ -272,26 +252,19 @@ const createConfigItem = (
|
||||
// It can have `path` property and `screens` prop which has nested configs
|
||||
let pattern: string | undefined;
|
||||
|
||||
if (legacy) {
|
||||
pattern =
|
||||
config.exact !== true && parentPattern && config.path
|
||||
? joinPaths(parentPattern, config.path)
|
||||
: config.path;
|
||||
} else {
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
|
||||
const screens = config.screens
|
||||
? createNormalizedConfigs(legacy, config.screens, pattern)
|
||||
? createNormalizedConfigs(config.screens, pattern)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
@@ -303,13 +276,12 @@ const createConfigItem = (
|
||||
};
|
||||
|
||||
const createNormalizedConfigs = (
|
||||
legacy: boolean,
|
||||
options: PathConfigMap,
|
||||
pattern?: string
|
||||
): Record<string, ConfigItem> =>
|
||||
fromEntries(
|
||||
Object.entries(options).map(([name, c]) => {
|
||||
const result = createConfigItem(legacy, c, pattern);
|
||||
const result = createConfigItem(c, pattern);
|
||||
|
||||
return [name, result];
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
PartialState,
|
||||
InitialState,
|
||||
} from '@react-navigation/routers';
|
||||
import checkLegacyPathConfig from './checkLegacyPathConfig';
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import type { PathConfigMap } from './types';
|
||||
|
||||
type Options = {
|
||||
@@ -26,7 +26,7 @@ type RouteConfig = {
|
||||
|
||||
type InitialRouteConfig = {
|
||||
initialRouteName: string;
|
||||
connectedRoutes: string[];
|
||||
parentScreens: string[];
|
||||
};
|
||||
|
||||
type ResultState = PartialState<NavigationState> & {
|
||||
@@ -35,6 +35,7 @@ type ResultState = PartialState<NavigationState> & {
|
||||
|
||||
type ParsedRoute = {
|
||||
name: string;
|
||||
path?: string;
|
||||
params?: Record<string, any> | undefined;
|
||||
};
|
||||
|
||||
@@ -63,18 +64,16 @@ export default function getStateFromPath(
|
||||
path: string,
|
||||
options?: Options
|
||||
): ResultState | undefined {
|
||||
const [legacy, compatOptions] = checkLegacyPathConfig(options);
|
||||
|
||||
let initialRoutes: InitialRouteConfig[] = [];
|
||||
|
||||
if (compatOptions?.initialRouteName) {
|
||||
if (options?.initialRouteName) {
|
||||
initialRoutes.push({
|
||||
initialRouteName: compatOptions.initialRouteName,
|
||||
connectedRoutes: Object.keys(compatOptions.screens),
|
||||
initialRouteName: options.initialRouteName,
|
||||
parentScreens: [],
|
||||
});
|
||||
}
|
||||
|
||||
const screens = compatOptions?.screens;
|
||||
const screens = options?.screens;
|
||||
|
||||
let remaining = path
|
||||
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
||||
@@ -89,18 +88,13 @@ export default function getStateFromPath(
|
||||
const routes = remaining
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.map((segment, i, self) => {
|
||||
.map((segment) => {
|
||||
const name = decodeURIComponent(segment);
|
||||
|
||||
if (i === self.length - 1) {
|
||||
return { name, params: parseQueryParams(path) };
|
||||
}
|
||||
|
||||
return { name };
|
||||
});
|
||||
|
||||
if (routes.length) {
|
||||
return createNestedStateObject(routes, initialRoutes);
|
||||
return createNestedStateObject(path, routes, initialRoutes);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -111,11 +105,11 @@ export default function getStateFromPath(
|
||||
.concat(
|
||||
...Object.keys(screens).map((key) =>
|
||||
createNormalizedConfigs(
|
||||
legacy,
|
||||
key,
|
||||
screens as PathConfigMap,
|
||||
[],
|
||||
initialRoutes
|
||||
initialRoutes,
|
||||
[]
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -209,14 +203,10 @@ export default function getStateFromPath(
|
||||
|
||||
if (match) {
|
||||
return createNestedStateObject(
|
||||
match.routeNames.map((name, i, self) => {
|
||||
if (i === self.length - 1) {
|
||||
return { name, params: parseQueryParams(path, match.parse) };
|
||||
}
|
||||
|
||||
return { name };
|
||||
}),
|
||||
initialRoutes
|
||||
path,
|
||||
match.routeNames.map((name) => ({ name })),
|
||||
initialRoutes,
|
||||
configs
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,75 +216,28 @@ export default function getStateFromPath(
|
||||
let result: PartialState<NavigationState> | undefined;
|
||||
let current: PartialState<NavigationState> | undefined;
|
||||
|
||||
if (legacy === false) {
|
||||
// If we're not in legacy mode,, we match the whole path against the regex instead of segments
|
||||
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
||||
const { routes, remainingPath } = matchAgainstConfigs(
|
||||
remaining,
|
||||
configs.map((c) => ({
|
||||
...c,
|
||||
// Add `$` to the regex to make sure it matches till end of the path and not just beginning
|
||||
regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
|
||||
}))
|
||||
);
|
||||
// We match the whole path against the regex instead of segments
|
||||
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
||||
const { routes, remainingPath } = matchAgainstConfigs(
|
||||
remaining,
|
||||
configs.map((c) => ({
|
||||
...c,
|
||||
// Add `$` to the regex to make sure it matches till end of the path and not just beginning
|
||||
regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
|
||||
}))
|
||||
);
|
||||
|
||||
if (routes !== undefined) {
|
||||
// This will always be empty if full path matched
|
||||
current = createNestedStateObject(routes, initialRoutes);
|
||||
remaining = remainingPath;
|
||||
result = current;
|
||||
}
|
||||
} else {
|
||||
// In legacy mode, we divide the path into segments and match piece by piece
|
||||
// This preserves the legacy behaviour, but we should remove it in next major
|
||||
while (remaining) {
|
||||
let { routes, remainingPath } = matchAgainstConfigs(remaining, configs);
|
||||
|
||||
remaining = remainingPath;
|
||||
|
||||
// If we hadn't matched any segments earlier, use the path as route name
|
||||
if (routes === undefined) {
|
||||
const segments = remaining.split('/');
|
||||
|
||||
routes = [{ name: decodeURIComponent(segments[0]) }];
|
||||
segments.shift();
|
||||
remaining = segments.join('/');
|
||||
}
|
||||
|
||||
const state = createNestedStateObject(routes, initialRoutes);
|
||||
|
||||
if (current) {
|
||||
// The state should be nested inside the deepest route we parsed before
|
||||
while (current?.routes[current.index || 0].state) {
|
||||
current = current.routes[current.index || 0].state;
|
||||
}
|
||||
|
||||
(current as PartialState<NavigationState>).routes[
|
||||
current?.index || 0
|
||||
].state = state;
|
||||
} else {
|
||||
result = state;
|
||||
}
|
||||
|
||||
current = state;
|
||||
}
|
||||
if (routes !== undefined) {
|
||||
// This will always be empty if full path matched
|
||||
current = createNestedStateObject(path, routes, initialRoutes, configs);
|
||||
remaining = remainingPath;
|
||||
result = current;
|
||||
}
|
||||
|
||||
if (current == null || result == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const route = findFocusedRoute(current);
|
||||
const params = parseQueryParams(
|
||||
path,
|
||||
findParseConfigForRoute(route.name, configs)
|
||||
);
|
||||
|
||||
if (params) {
|
||||
// @ts-expect-error: params should be treated as read-only, but we're creating the state here so it doesn't matter
|
||||
route.params = { ...route.params, ...params };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -363,24 +306,26 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
||||
};
|
||||
|
||||
const createNormalizedConfigs = (
|
||||
legacy: boolean,
|
||||
screen: string,
|
||||
routeConfig: PathConfigMap,
|
||||
routeNames: string[] = [],
|
||||
initials: InitialRouteConfig[],
|
||||
parentScreens: string[],
|
||||
parentPattern?: string
|
||||
): RouteConfig[] => {
|
||||
const configs: RouteConfig[] = [];
|
||||
|
||||
routeNames.push(screen);
|
||||
|
||||
parentScreens.push(screen);
|
||||
|
||||
const config = routeConfig[screen];
|
||||
|
||||
if (typeof config === 'string') {
|
||||
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||
|
||||
configs.push(createConfigItem(legacy, screen, routeNames, pattern, config));
|
||||
configs.push(createConfigItem(screen, routeNames, pattern, config));
|
||||
} else if (typeof config === 'object') {
|
||||
let pattern: string | undefined;
|
||||
|
||||
@@ -388,33 +333,19 @@ const createNormalizedConfigs = (
|
||||
// it can have `path` property and
|
||||
// it could have `screens` prop which has nested configs
|
||||
if (typeof config.path === 'string') {
|
||||
if (legacy) {
|
||||
pattern =
|
||||
config.exact !== true && parentPattern
|
||||
? joinPaths(parentPattern, config.path)
|
||||
: config.path;
|
||||
} else {
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
if (config.exact && config.path === undefined) {
|
||||
throw new Error(
|
||||
"A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
|
||||
);
|
||||
}
|
||||
|
||||
pattern =
|
||||
config.exact !== true
|
||||
? joinPaths(parentPattern || '', config.path || '')
|
||||
: config.path || '';
|
||||
|
||||
configs.push(
|
||||
createConfigItem(
|
||||
legacy,
|
||||
screen,
|
||||
routeNames,
|
||||
pattern,
|
||||
config.path,
|
||||
config.parse
|
||||
)
|
||||
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -423,17 +354,17 @@ const createNormalizedConfigs = (
|
||||
if (config.initialRouteName) {
|
||||
initials.push({
|
||||
initialRouteName: config.initialRouteName,
|
||||
connectedRoutes: Object.keys(config.screens),
|
||||
parentScreens,
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(config.screens).forEach((nestedConfig) => {
|
||||
const result = createNormalizedConfigs(
|
||||
legacy,
|
||||
nestedConfig,
|
||||
config.screens as PathConfigMap,
|
||||
routeNames,
|
||||
initials,
|
||||
[...parentScreens],
|
||||
pattern ?? parentPattern
|
||||
);
|
||||
|
||||
@@ -448,7 +379,6 @@ const createNormalizedConfigs = (
|
||||
};
|
||||
|
||||
const createConfigItem = (
|
||||
legacy: boolean,
|
||||
screen: string,
|
||||
routeNames: string[],
|
||||
pattern: string,
|
||||
@@ -463,12 +393,6 @@ const createConfigItem = (
|
||||
`^(${pattern
|
||||
.split('/')
|
||||
.map((it) => {
|
||||
if (legacy && it === '*') {
|
||||
throw new Error(
|
||||
"Please update your config to the new format to use wildcard pattern ('*'). https://reactnavigation.org/docs/configuring-links/#updating-config"
|
||||
);
|
||||
}
|
||||
|
||||
if (it.startsWith(':')) {
|
||||
return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
|
||||
}
|
||||
@@ -506,13 +430,23 @@ const findParseConfigForRoute = (
|
||||
// Try to find an initial route connected with the one passed
|
||||
const findInitialRoute = (
|
||||
routeName: string,
|
||||
parentScreens: string[],
|
||||
initialRoutes: InitialRouteConfig[]
|
||||
): string | undefined => {
|
||||
for (const config of initialRoutes) {
|
||||
if (config.connectedRoutes.includes(routeName)) {
|
||||
return config.initialRouteName === routeName
|
||||
? undefined
|
||||
: config.initialRouteName;
|
||||
if (parentScreens.length === config.parentScreens.length) {
|
||||
let sameParents = true;
|
||||
for (let i = 0; i < parentScreens.length; i++) {
|
||||
if (parentScreens[i].localeCompare(config.parentScreens[i]) !== 0) {
|
||||
sameParents = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sameParents) {
|
||||
return routeName !== config.initialRouteName
|
||||
? config.initialRouteName
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -551,12 +485,18 @@ const createStateObject = (
|
||||
};
|
||||
|
||||
const createNestedStateObject = (
|
||||
path: string,
|
||||
routes: ParsedRoute[],
|
||||
initialRoutes: InitialRouteConfig[]
|
||||
initialRoutes: InitialRouteConfig[],
|
||||
flatConfig?: RouteConfig[]
|
||||
) => {
|
||||
let state: InitialState;
|
||||
let route = routes.shift() as ParsedRoute;
|
||||
let initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||
const parentScreens: string[] = [];
|
||||
|
||||
let initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
||||
|
||||
parentScreens.push(route.name);
|
||||
|
||||
state = createStateObject(initialRoute, route, routes.length === 0);
|
||||
|
||||
@@ -564,7 +504,7 @@ const createNestedStateObject = (
|
||||
let nestedState = state;
|
||||
|
||||
while ((route = routes.shift() as ParsedRoute)) {
|
||||
initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||
initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
||||
|
||||
const nestedStateIndex =
|
||||
nestedState.index || nestedState.routes.length - 1;
|
||||
@@ -579,25 +519,24 @@ const createNestedStateObject = (
|
||||
nestedState = nestedState.routes[nestedStateIndex]
|
||||
.state as InitialState;
|
||||
}
|
||||
|
||||
parentScreens.push(route.name);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
route = findFocusedRoute(state) as ParsedRoute;
|
||||
route.path = path;
|
||||
|
||||
const findFocusedRoute = (state: InitialState) => {
|
||||
let current: InitialState | undefined = state;
|
||||
const params = parseQueryParams(
|
||||
path,
|
||||
flatConfig ? findParseConfigForRoute(route.name, flatConfig) : undefined
|
||||
);
|
||||
|
||||
while (current?.routes[current.index || 0].state) {
|
||||
// The query params apply to the deepest route
|
||||
current = current.routes[current.index || 0].state;
|
||||
if (params) {
|
||||
route.params = { ...route.params, ...params };
|
||||
}
|
||||
|
||||
const route = (current as PartialState<NavigationState>).routes[
|
||||
current?.index || 0
|
||||
];
|
||||
|
||||
return route;
|
||||
return state;
|
||||
};
|
||||
|
||||
const parseQueryParams = (
|
||||
|
||||
@@ -20,6 +20,8 @@ export { default as getStateFromPath } from './getStateFromPath';
|
||||
export { default as getPathFromState } from './getPathFromState';
|
||||
export { default as getActionFromState } from './getActionFromState';
|
||||
|
||||
export { default as findFocusedRoute } from './findFocusedRoute';
|
||||
|
||||
export { default as getFocusedRouteNameFromRoute } from './getFocusedRouteNameFromRoute';
|
||||
|
||||
export * from './types';
|
||||
|
||||
@@ -27,6 +27,17 @@ export type DefaultNavigatorOptions<
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
/**
|
||||
* Default options specified by the navigator.
|
||||
* It receives the custom options in the arguments if a function is specified.
|
||||
*/
|
||||
defaultScreenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
navigation: any;
|
||||
options: ScreenOptions;
|
||||
}) => ScreenOptions);
|
||||
};
|
||||
|
||||
export type EventMapBase = Record<
|
||||
@@ -157,8 +168,8 @@ type NavigationHelpersCommon<
|
||||
*/
|
||||
navigate<RouteName extends keyof ParamList>(
|
||||
...args: undefined extends ParamList[RouteName]
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
? [screen: RouteName] | [screen: RouteName, params: ParamList[RouteName]]
|
||||
: [screen: RouteName, params: ParamList[RouteName]]
|
||||
): void;
|
||||
|
||||
/**
|
||||
@@ -167,7 +178,7 @@ type NavigationHelpersCommon<
|
||||
* @param route Object with `key` or `name` for the route to navigate to, and a `params` object.
|
||||
*/
|
||||
navigate<RouteName extends keyof ParamList>(
|
||||
route:
|
||||
options:
|
||||
| { key: string; params?: ParamList[RouteName] }
|
||||
| { name: RouteName; key?: string; params: ParamList[RouteName] }
|
||||
): void;
|
||||
@@ -199,18 +210,15 @@ type NavigationHelpersCommon<
|
||||
canGoBack(): boolean;
|
||||
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
* Returns the navigation prop from the parent navigator,
|
||||
*/
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
getParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
* dangerouslyGetState is to discourage developers to use internal navigation's state.
|
||||
* Returns the navigator's state.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
dangerouslyGetState(): State;
|
||||
getState(): State;
|
||||
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
|
||||
|
||||
export type NavigationHelpers<
|
||||
@@ -318,11 +326,9 @@ export type CompositeNavigationProp<
|
||||
>;
|
||||
|
||||
export type Descriptor<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string,
|
||||
State extends NavigationState = NavigationState,
|
||||
ScreenOptions extends {} = {},
|
||||
EventMap extends EventMapBase = {}
|
||||
ScreenOptions extends {},
|
||||
Navigation extends NavigationProp<any, any, any, any, any>,
|
||||
Route extends RouteProp<any, any>
|
||||
> = {
|
||||
/**
|
||||
* Render the component associated with this route.
|
||||
@@ -334,16 +340,15 @@ export type Descriptor<
|
||||
*/
|
||||
options: ScreenOptions;
|
||||
|
||||
/**
|
||||
* Route object for the screen
|
||||
*/
|
||||
route: Route;
|
||||
|
||||
/**
|
||||
* Navigation object for the screen
|
||||
*/
|
||||
navigation: NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>;
|
||||
navigation: Navigation;
|
||||
};
|
||||
|
||||
export type ScreenListeners<
|
||||
@@ -520,6 +525,7 @@ export type NavigatorScreenParams<
|
||||
screen?: never;
|
||||
params?: never;
|
||||
initial?: never;
|
||||
path?: string;
|
||||
state: PartialState<State> | State | undefined;
|
||||
}
|
||||
| {
|
||||
@@ -528,12 +534,14 @@ export type NavigatorScreenParams<
|
||||
screen: RouteName;
|
||||
params?: ParamList[RouteName];
|
||||
initial?: boolean;
|
||||
path?: string;
|
||||
state?: never;
|
||||
}
|
||||
: {
|
||||
screen: RouteName;
|
||||
params: ParamList[RouteName];
|
||||
initial?: boolean;
|
||||
path?: string;
|
||||
state?: never;
|
||||
};
|
||||
}[keyof ParamList];
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import CurrentRenderContext from './CurrentRenderContext';
|
||||
import type { Descriptor, NavigationHelpers } from './types';
|
||||
import type {
|
||||
Descriptor,
|
||||
NavigationHelpers,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
} from './types';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
descriptors: {
|
||||
[key: string]: Descriptor<ParamListBase, string, NavigationState, object>;
|
||||
};
|
||||
descriptors: Record<
|
||||
string,
|
||||
Descriptor<
|
||||
object,
|
||||
NavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
>
|
||||
>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
RouteConfig,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
NavigationProp,
|
||||
} from './types';
|
||||
|
||||
type Options<
|
||||
@@ -40,6 +41,13 @@ type Options<
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
defaultScreenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
navigation: any;
|
||||
options: ScreenOptions;
|
||||
}) => ScreenOptions);
|
||||
onAction: (
|
||||
action: NavigationAction,
|
||||
visitedNavigators?: Set<string>
|
||||
@@ -50,7 +58,7 @@ type Options<
|
||||
addKeyedListener: AddKeyedListener;
|
||||
onRouteFocus: (key: string) => void;
|
||||
router: Router<State, NavigationAction>;
|
||||
emitter: NavigationEventEmitter<any>;
|
||||
emitter: NavigationEventEmitter<EventMap>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -63,6 +71,7 @@ type Options<
|
||||
*/
|
||||
export default function useDescriptors<
|
||||
State extends NavigationState,
|
||||
ActionHelpers extends Record<string, () => void>,
|
||||
ScreenOptions extends {},
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
@@ -70,6 +79,7 @@ export default function useDescriptors<
|
||||
screens,
|
||||
navigation,
|
||||
screenOptions,
|
||||
defaultScreenOptions,
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
@@ -105,7 +115,7 @@ export default function useDescriptors<
|
||||
]
|
||||
);
|
||||
|
||||
const navigations = useNavigationCache<State, ScreenOptions>({
|
||||
const navigations = useNavigationCache<State, ScreenOptions, EventMap>({
|
||||
state,
|
||||
getState,
|
||||
navigation,
|
||||
@@ -117,12 +127,20 @@ export default function useDescriptors<
|
||||
const routes = useRouteCache(state.routes);
|
||||
|
||||
return routes.reduce<
|
||||
Record<string, Descriptor<ParamListBase, string, State, ScreenOptions>>
|
||||
Record<
|
||||
string,
|
||||
Descriptor<
|
||||
ScreenOptions,
|
||||
NavigationProp<ParamListBase, string, State, ScreenOptions, EventMap> &
|
||||
ActionHelpers,
|
||||
RouteProp<ParamListBase, string>
|
||||
>
|
||||
>
|
||||
>((acc, route, i) => {
|
||||
const screen = screens[route.name];
|
||||
const navigation = navigations[route.key];
|
||||
|
||||
const routeOptions = {
|
||||
const customOptions = {
|
||||
// The default `screenOptions` passed to the navigator
|
||||
...(typeof screenOptions === 'object' || screenOptions == null
|
||||
? screenOptions
|
||||
@@ -143,7 +161,21 @@ export default function useDescriptors<
|
||||
...options[route.key],
|
||||
};
|
||||
|
||||
const mergedOptions = {
|
||||
...(typeof defaultScreenOptions === 'function'
|
||||
? // @ts-expect-error: ts gives incorrect error here
|
||||
defaultScreenOptions({
|
||||
route,
|
||||
navigation,
|
||||
options: customOptions,
|
||||
})
|
||||
: defaultScreenOptions),
|
||||
...customOptions,
|
||||
};
|
||||
|
||||
acc[route.key] = {
|
||||
route,
|
||||
// @ts-expect-error: it's missing action helpers, fix later
|
||||
navigation,
|
||||
render() {
|
||||
return (
|
||||
@@ -157,14 +189,14 @@ export default function useDescriptors<
|
||||
routeState={state.routes[i].state}
|
||||
getState={getState}
|
||||
setState={setState}
|
||||
options={routeOptions}
|
||||
options={mergedOptions}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
},
|
||||
options: routeOptions as ScreenOptions,
|
||||
options: mergedOptions as ScreenOptions,
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
||||
@@ -366,7 +366,7 @@ export default function useNavigationBuilder<
|
||||
if (
|
||||
typeof route.params.state === 'object' &&
|
||||
route.params.state != null &&
|
||||
route.params.state !== previousParams?.state
|
||||
route.params !== previousParams
|
||||
) {
|
||||
// If the route was updated with new state, we should reset to it
|
||||
action = CommonActions.reset(route.params.state);
|
||||
@@ -375,9 +375,12 @@ export default function useNavigationBuilder<
|
||||
((route.params.initial === false && isFirstStateInitialization) ||
|
||||
route.params !== previousParams)
|
||||
) {
|
||||
// FIXME: Since params are merged, `route.params.params` might contain params from an older route
|
||||
// If the route was updated with new screen name and/or params, we should navigate there
|
||||
action = CommonActions.navigate(route.params.screen, route.params.params);
|
||||
action = CommonActions.navigate({
|
||||
name: route.params.screen,
|
||||
params: route.params.params,
|
||||
path: route.params.path,
|
||||
});
|
||||
}
|
||||
|
||||
// The update should be limited to current navigator only, so we call the router manually
|
||||
@@ -550,11 +553,17 @@ export default function useNavigationBuilder<
|
||||
getStateListeners: keyedListeners.getState,
|
||||
});
|
||||
|
||||
const descriptors = useDescriptors<State, ScreenOptions, EventMap>({
|
||||
const descriptors = useDescriptors<
|
||||
State,
|
||||
ActionHelpers,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>({
|
||||
state,
|
||||
screens,
|
||||
navigation,
|
||||
screenOptions: options.screenOptions,
|
||||
defaultScreenOptions: options.defaultScreenOptions,
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
@@ -562,6 +571,7 @@ export default function useNavigationBuilder<
|
||||
addListener,
|
||||
addKeyedListener,
|
||||
router,
|
||||
// @ts-expect-error: this should have both core and custom events, but too much work right now
|
||||
emitter,
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ import {
|
||||
} from '@react-navigation/routers';
|
||||
import type { NavigationEventEmitter } from './useEventEmitter';
|
||||
|
||||
import type { EventMapBase, NavigationHelpers, NavigationProp } from './types';
|
||||
import type { NavigationHelpers, NavigationProp } from './types';
|
||||
|
||||
type Options<State extends NavigationState> = {
|
||||
type Options<
|
||||
State extends NavigationState,
|
||||
EventMap extends Record<string, any>
|
||||
> = {
|
||||
state: State;
|
||||
getState: () => State;
|
||||
navigation: NavigationHelpers<ParamListBase> &
|
||||
@@ -19,15 +22,17 @@ type Options<State extends NavigationState> = {
|
||||
cb: (options: Record<string, object>) => Record<string, object>
|
||||
) => void;
|
||||
router: Router<State, NavigationAction>;
|
||||
emitter: NavigationEventEmitter<EventMapBase>;
|
||||
emitter: NavigationEventEmitter<EventMap>;
|
||||
};
|
||||
|
||||
type NavigationCache<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {}
|
||||
> = {
|
||||
[key: string]: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||
};
|
||||
ScreenOptions extends {},
|
||||
EventMap extends Record<string, any>
|
||||
> = Record<
|
||||
string,
|
||||
NavigationProp<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Hook to cache navigation objects for each screen in the navigator.
|
||||
@@ -36,7 +41,8 @@ type NavigationCache<
|
||||
*/
|
||||
export default function useNavigationCache<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends {}
|
||||
ScreenOptions extends {},
|
||||
EventMap extends Record<string, any>
|
||||
>({
|
||||
state,
|
||||
getState,
|
||||
@@ -44,12 +50,12 @@ export default function useNavigationCache<
|
||||
setOptions,
|
||||
router,
|
||||
emitter,
|
||||
}: Options<State>) {
|
||||
}: Options<State, EventMap>) {
|
||||
// Cache object which holds navigation objects for each screen
|
||||
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
||||
// In reality, these deps will rarely change, if ever
|
||||
const cache = React.useMemo(
|
||||
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
|
||||
() => ({ current: {} as NavigationCache<State, ScreenOptions, EventMap> }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[getState, navigation, setOptions, router, emitter]
|
||||
);
|
||||
@@ -59,67 +65,66 @@ export default function useNavigationCache<
|
||||
...CommonActions,
|
||||
};
|
||||
|
||||
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
||||
(acc, route) => {
|
||||
const previous = cache.current[route.key];
|
||||
cache.current = state.routes.reduce<
|
||||
NavigationCache<State, ScreenOptions, EventMap>
|
||||
>((acc, route) => {
|
||||
const previous = cache.current[route.key];
|
||||
|
||||
if (previous) {
|
||||
// If a cached navigation object already exists, reuse it
|
||||
acc[route.key] = previous;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { emit, ...rest } = navigation;
|
||||
if (previous) {
|
||||
// If a cached navigation object already exists, reuse it
|
||||
acc[route.key] = previous;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { emit, ...rest } = navigation;
|
||||
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: State) => NavigationAction)
|
||||
) => {
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
const dispatch = (
|
||||
thunk:
|
||||
| NavigationAction
|
||||
| ((state: State) => NavigationAction | null | undefined)
|
||||
) => {
|
||||
const action = typeof thunk === 'function' ? thunk(getState()) : thunk;
|
||||
|
||||
navigation.dispatch(
|
||||
typeof payload === 'object' && payload != null
|
||||
? { source: route.key, ...payload }
|
||||
: payload
|
||||
);
|
||||
};
|
||||
if (action != null) {
|
||||
navigation.dispatch({ source: route.key, ...action });
|
||||
}
|
||||
};
|
||||
|
||||
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
||||
(acc, name) => {
|
||||
// @ts-expect-error: name is a valid key, but TypeScript is dumb
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
||||
(acc, name) => {
|
||||
// @ts-expect-error: name is a valid key, but TypeScript is dumb
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
acc[route.key] = {
|
||||
...rest,
|
||||
...helpers,
|
||||
...emitter.create(route.key),
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions((o) => ({
|
||||
...o,
|
||||
[route.key]: { ...o[route.key], ...options },
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
acc[route.key] = {
|
||||
...rest,
|
||||
...helpers,
|
||||
// FIXME: too much work to fix the types for now
|
||||
...(emitter.create(route.key) as any),
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions((o) => ({
|
||||
...o,
|
||||
[route.key]: { ...o[route.key], ...options },
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
|
||||
if (state.routes[state.index].key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
if (state.routes[state.index].key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the current screen is focused, we also need to check if parent navigator is focused
|
||||
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
||||
return navigation ? navigation.isFocused() : true;
|
||||
},
|
||||
};
|
||||
}
|
||||
// If the current screen is focused, we also need to check if parent navigator is focused
|
||||
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
||||
return navigation ? navigation.isFocused() : true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return cache.current;
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ export default function useNavigationHelpers<
|
||||
false
|
||||
);
|
||||
},
|
||||
dangerouslyGetParent: () => parentNavigationHelpers as any,
|
||||
dangerouslyGetState: getState,
|
||||
getParent: () => parentNavigationHelpers as any,
|
||||
getState: getState,
|
||||
} as NavigationHelpers<ParamListBase, EventMap> &
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined) &
|
||||
ActionHelpers;
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import type { NavigationState } from '@react-navigation/routers';
|
||||
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import useNavigation from './useNavigation';
|
||||
import type { NavigationProp } from './types';
|
||||
|
||||
type Selector<T> = (state: NavigationState) => T;
|
||||
type Selector<ParamList extends ParamListBase, T> = (
|
||||
state: NavigationState<ParamList>
|
||||
) => T;
|
||||
|
||||
/**
|
||||
* Hook to get a value from the current navigation state using a selector.
|
||||
*
|
||||
* @param selector Selector function to get a value from the state.
|
||||
*/
|
||||
export default function useNavigationState<T>(selector: Selector<T>): T {
|
||||
const navigation = useNavigation();
|
||||
export default function useNavigationState<ParamList extends ParamListBase, T>(
|
||||
selector: Selector<ParamList, T>
|
||||
): T {
|
||||
const navigation = useNavigation<NavigationProp<ParamList>>();
|
||||
|
||||
// We don't care about the state value, we run the selector again at the end
|
||||
// The state is only to make sure that there's a re-render when we have a new value
|
||||
const [, setResult] = React.useState(() =>
|
||||
selector(navigation.dangerouslyGetState())
|
||||
);
|
||||
const [, setResult] = React.useState(() => selector(navigation.getState()));
|
||||
|
||||
// We store the selector in a ref to avoid re-subscribing listeners every render
|
||||
const selectorRef = React.useRef(selector);
|
||||
@@ -33,5 +36,5 @@ export default function useNavigationState<T>(selector: Selector<T>): T {
|
||||
return unsubscribe;
|
||||
}, [navigation]);
|
||||
|
||||
return selector(navigation.dangerouslyGetState());
|
||||
return selector(navigation.getState());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type RouteCache = Map<Route<string>, RouteProp<ParamListBase, string>>;
|
||||
* So we need a way to suppress the warning for those use cases.
|
||||
* This is fine since they are internal utilities and this is not public API.
|
||||
*/
|
||||
export const SUPPRESS_STATE_ACCESS_WARNING = { value: false };
|
||||
export const CHILD_STATE = Symbol('CHILD_STATE');
|
||||
|
||||
/**
|
||||
* Hook to cache route props for each screen in the navigator.
|
||||
@@ -37,18 +37,11 @@ export default function useRouteCache<State extends NavigationState>(
|
||||
// If a cached route object already exists, reuse it
|
||||
acc.set(route, previous);
|
||||
} else {
|
||||
const proxy = { ...route };
|
||||
const { state, ...proxy } = route;
|
||||
|
||||
Object.defineProperty(proxy, 'state', {
|
||||
get() {
|
||||
if (!SUPPRESS_STATE_ACCESS_WARNING.value) {
|
||||
console.warn(
|
||||
"Accessing the 'state' property of the 'route' object is not supported. If you want to get the focused route name, use the 'getFocusedRouteNameFromRoute' helper instead: https://reactnavigation.org/docs/screen-options-resolution/#setting-parent-screen-options-based-on-child-navigators-state"
|
||||
);
|
||||
}
|
||||
|
||||
return route.state;
|
||||
},
|
||||
Object.defineProperty(proxy, CHILD_STATE, {
|
||||
enumerable: false,
|
||||
value: state,
|
||||
});
|
||||
|
||||
acc.set(route, proxy);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.20](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.19...@react-navigation/devtools@5.1.20) (2021-01-21)
|
||||
# [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
|
||||
|
||||
@@ -11,7 +11,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
## [5.1.19](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.18...@react-navigation/devtools@5.1.19) (2021-01-14)
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0...@react-navigation/devtools@6.0.0-next.1) (2021-03-10)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
@@ -19,7 +19,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
|
||||
|
||||
## [5.1.18](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.17...@react-navigation/devtools@5.1.18) (2020-11-20)
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.17...@react-navigation/devtools@6.0.0-next.0) (2021-03-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Developer tools for React Navigation.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/devtools).
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/6.x/devtools).
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/devtools",
|
||||
"description": "Developer tools for React Navigation",
|
||||
"version": "5.1.20",
|
||||
"version": "6.0.0-next.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -36,17 +36,17 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.15.1",
|
||||
"deep-equal": "^2.0.4"
|
||||
"@react-navigation/core": "^6.0.0-next.2",
|
||||
"deep-equal": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"typescript": "^4.0.3"
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
||||
@@ -3,77 +3,97 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.12.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.12.2...@react-navigation/drawer@5.12.3) (2021-01-25)
|
||||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix drawer screen content not being interactable on Android ([87b5147](https://github.com/react-navigation/react-navigation/commit/87b51476d0bce8f2dae793416c2976da30a1a5f7))
|
||||
* don't handle back button with permanent drawer ([b893968](https://github.com/react-navigation/react-navigation/commit/b89396888f46ba79af3cfd84be55fba79d8387d2))
|
||||
* fix drawer overlay on web ([3241190](https://github.com/react-navigation/react-navigation/commit/3241190b19946c1cd0a744fb09a19d79ba683d74))
|
||||
* only handle back button in drawer when focused ([5ae0bad](https://github.com/react-navigation/react-navigation/commit/5ae0badc44b576d464f8841822a911b18a698403))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.12.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.12.1...@react-navigation/drawer@5.12.2) (2021-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix pointerEvents in ResourceSavingScene ([60fe0db](https://github.com/react-navigation/react-navigation/commit/60fe0dbb0ae443fdb21016d368c919b933cb64e7)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.12.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.12.0...@react-navigation/drawer@5.12.1) (2021-01-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.12.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.5...@react-navigation/drawer@5.12.0) (2021-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([d88cbcb](https://github.com/react-navigation/react-navigation/commit/d88cbcb52d46de26edaa9ce6bfb06badb1b1de64))
|
||||
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.2...@react-navigation/drawer@6.0.0-next.3) (2021-03-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add pressColor and pressOpacity props to drawerItem ([#8834](https://github.com/react-navigation/react-navigation/issues/8834)) ([bae4019](https://github.com/react-navigation/react-navigation/commit/bae4019995062c682f0213c121b7927ab8006c1e))
|
||||
* add a Background component ([cbaabc1](https://github.com/react-navigation/react-navigation/commit/cbaabc1288e780698e499a00b9ca06ab9746a0da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.4...@react-navigation/drawer@5.11.5) (2021-01-14)
|
||||
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.1...@react-navigation/drawer@6.0.0-next.2) (2021-03-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export drawer button ([2c8401d](https://github.com/react-navigation/react-navigation/commit/2c8401d5cb347d37c96e5b30f8ad05c17fd22ea4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0...@react-navigation/drawer@6.0.0-next.1) (2021-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enable detachInactiveScreens by default on web for better a11y ([dd87fa4](https://github.com/react-navigation/react-navigation/commit/dd87fa49a43ad8db105a62418243339e4150fadf))
|
||||
* fix peer dep versions ([72f90b5](https://github.com/react-navigation/react-navigation/commit/72f90b50d27eda1315bb750beca8a36f26dafe17))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.3...@react-navigation/drawer@5.11.4) (2020-11-20)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.2...@react-navigation/drawer@5.11.3) (2020-11-16)
|
||||
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.2...@react-navigation/drawer@6.0.0-next.0) (2021-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hide drawer's header by default ([794339e](https://github.com/react-navigation/react-navigation/commit/794339eeed7c0d3b0e8b1752e494fbb4608ddfad))
|
||||
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
|
||||
* drop usage of Dimensions in favor of metrics from safe-area-context ([12b893d](https://github.com/react-navigation/react-navigation/commit/12b893d7ca8cdb726b973972797658ac9c7d17d7))
|
||||
* enable detachInactiveScreens by default on web for better a11y ([4954d6a](https://github.com/react-navigation/react-navigation/commit/4954d6aae3cdbb5855d44ff17d80d16b81fb224e))
|
||||
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([b735de1](https://github.com/react-navigation/react-navigation/commit/b735de153ca650240625dba6d8b5c8d16b913bac))
|
||||
* fix drawer screen content not being interactable on Android ([865d8b3](https://github.com/react-navigation/react-navigation/commit/865d8b3e51e117a01243966c160b7cd147d236ac))
|
||||
* fix initial metrics on server ([69d333f](https://github.com/react-navigation/react-navigation/commit/69d333f6c23e0c37eaf4d3f8b413e8f96d6827f8))
|
||||
* fix pointerEvents in ResourceSavingScene ([af53dd6](https://github.com/react-navigation/react-navigation/commit/af53dd6548630124f831446e0eee468da5d9bf5e)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
|
||||
* fix typo for default prop in drawer ([b376e9c](https://github.com/react-navigation/react-navigation/commit/b376e9c5ed7097cea25c957d1de75f84cc9ea9f0))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* don't use a boolean for drawer status ([cda6397](https://github.com/react-navigation/react-navigation/commit/cda6397b8989c552824eca4175577527c9d72f93))
|
||||
* don't use absolute position for header ([79a85a4](https://github.com/react-navigation/react-navigation/commit/79a85a431ce0859ae35a13858b23c3919795e560))
|
||||
* don't use deprecated APIs from react-native-safe-area-context ([ddf27bf](https://github.com/react-navigation/react-navigation/commit/ddf27bf41a2efc5d1573aad0f8fe6c27a98c32b3))
|
||||
* drop drawerOpen and drawerClose events ([5648e1a](https://github.com/react-navigation/react-navigation/commit/5648e1a7b355068d105a2da79861d0add4717ff4))
|
||||
* move drawerContentOptions to options ([15e5678](https://github.com/react-navigation/react-navigation/commit/15e5678037bc6656d891724b4262cb542d6aad0d))
|
||||
* simplify props for stack and drawer headers ([4cad132](https://github.com/react-navigation/react-navigation/commit/4cad132c2c3daa6370a6916977f1f1db0036d4e4))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add pressColor and pressOpacity props to drawerItem ([#8834](https://github.com/react-navigation/react-navigation/issues/8834)) ([52dbe4b](https://github.com/react-navigation/react-navigation/commit/52dbe4bd6663430745b07ea379d44d4d4f2944a0))
|
||||
* initial implementation of @react-navigation/elements ([07ba7a9](https://github.com/react-navigation/react-navigation/commit/07ba7a96870efdb8acf99eb82ba0b1d3eac90bab))
|
||||
* move lazy to options for bottom-tabs and drawer ([068a9a4](https://github.com/react-navigation/react-navigation/commit/068a9a456c31a08104097f2a8434c66c30a5be99))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Drawer status is now a union ('open', 'closed') instead of a boolean. This will let us implement more types of status in future.
|
||||
|
||||
Following this the following exports have been renamed as well:
|
||||
- getIsDrawerOpenFromState -> getDrawerStatusFromState
|
||||
- useIsDrawerOpen -> useDrawerStatus
|
||||
* We now use flexbox for header elements which could break some existing style code which relied on absolute positioning.
|
||||
* The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
|
||||
* This commit moves options from `drawerContentOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the drawer on a per screen basis.
|
||||
* We now require newer versions of safe area context library.
|
||||
* drawer's status can be queried through the isDrawerOpen hook. no need for the events
|
||||
* Previously, the stack header accepted scene and previous scene which contained things such as descriptor, navigation prop, progress etc. The commit simplifies them to pass `route`, `navigation`, `options` and `progress` directly to the header. Similaryly, the `previous` argument now contains `options`, `route` and `progress`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# `@react-navigation/drawer`
|
||||
|
||||
Drawer navigator for React Navigation following Material Design guidelines.
|
||||
Drawer navigator for React Navigation following Material Design guidelines.
|
||||
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/drawer-navigator/).
|
||||
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/6.x/drawer-navigator/).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.12.3",
|
||||
"version": "6.0.0-next.4",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,32 +41,33 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.0.0-next.4",
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
"warn-once": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-navigation/native": "^5.9.2",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@react-navigation/native": "^6.0.0-next.2",
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"@types/react-native": "~0.63.51",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-builder-bob": "^0.17.0",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"typescript": "^4.0.3"
|
||||
"react-native": "~0.63.4",
|
||||
"react-native-builder-bob": "^0.18.1",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-reanimated": "~2.1.0",
|
||||
"react-native-safe-area-context": "~3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": ">= 1.0.0",
|
||||
"react-native-reanimated": ">= 1.0.0",
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
"react-native-safe-area-context": ">= 3.0.0",
|
||||
"react-native-screens": ">= 3.0.0"
|
||||
},
|
||||
"react-native-builder-bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -11,14 +11,18 @@ export { default as DrawerItem } from './views/DrawerItem';
|
||||
export { default as DrawerItemList } from './views/DrawerItemList';
|
||||
export { default as DrawerContent } from './views/DrawerContent';
|
||||
export { default as DrawerContentScrollView } from './views/DrawerContentScrollView';
|
||||
export { default as DrawerToggleButton } from './views/DrawerToggleButton';
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
|
||||
|
||||
export { default as getIsDrawerOpenFromState } from './utils/getIsDrawerOpenFromState';
|
||||
export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
||||
export { default as DrawerProgressContext } from './utils/DrawerProgressContext';
|
||||
export { default as useDrawerProgress } from './utils/useDrawerProgress';
|
||||
|
||||
export { default as getDrawerStatusFromState } from './utils/getDrawerStatusFromState';
|
||||
export { default as useDrawerStatus } from './utils/useDrawerStatus';
|
||||
|
||||
/**
|
||||
* Types
|
||||
@@ -27,6 +31,5 @@ export type {
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationProp,
|
||||
DrawerScreenProps,
|
||||
DrawerContentOptions,
|
||||
DrawerContentComponentProps,
|
||||
} from './types';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DrawerActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import warnOnce from 'warn-once';
|
||||
import DrawerView from '../views/DrawerView';
|
||||
import type {
|
||||
DrawerNavigationOptions,
|
||||
@@ -27,8 +27,46 @@ function DrawerNavigator({
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
// @ts-expect-error: lazy is deprecated
|
||||
lazy,
|
||||
// @ts-expect-error: drawerContentOptions is deprecated
|
||||
drawerContentOptions,
|
||||
...rest
|
||||
}: Props) {
|
||||
let defaultScreenOptions: DrawerNavigationOptions = {};
|
||||
|
||||
if (drawerContentOptions) {
|
||||
Object.assign(defaultScreenOptions, {
|
||||
drawerPosition: drawerContentOptions.drawerPosition,
|
||||
drawerType: drawerContentOptions.drawerType,
|
||||
swipeEdgeWidth: drawerContentOptions.edgeWidth,
|
||||
drawerHideStatusBarOnOpen: drawerContentOptions.hideStatusBar,
|
||||
keyboardDismissMode: drawerContentOptions.keyboardDismissMode,
|
||||
swipeMinDistance: drawerContentOptions.minSwipeDistance,
|
||||
overlayColor: drawerContentOptions.overlayColor,
|
||||
drawerStatusBarAnimation: drawerContentOptions.statusBarAnimation,
|
||||
gestureHandlerProps: drawerContentOptions.gestureHandlerProps,
|
||||
});
|
||||
|
||||
warnOnce(
|
||||
drawerContentOptions,
|
||||
`Drawer Navigator: 'drawerContentOptions' is deprecated. Migrate the options to 'screenOptions' instead.\n\nPlace the following in 'screenOptions' in your code to keep current behavior:\n\n${JSON.stringify(
|
||||
defaultScreenOptions,
|
||||
null,
|
||||
2
|
||||
)}\n\nSee https://reactnavigation.org/docs/6.x/drawer-navigator#options for more details.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof lazy === 'boolean') {
|
||||
defaultScreenOptions.lazy = lazy;
|
||||
|
||||
warnOnce(
|
||||
true,
|
||||
`Drawer Navigator: 'lazy' in props is deprecated. Move it to 'screenOptions' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerRouterOptions,
|
||||
@@ -41,6 +79,7 @@ function DrawerNavigator({
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
defaultScreenOptions,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
||||
import type Animated from 'react-native-reanimated';
|
||||
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
|
||||
import type {
|
||||
PanGestureHandler,
|
||||
PanGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
import type {
|
||||
Route,
|
||||
ParamListBase,
|
||||
@@ -11,6 +13,7 @@ import type {
|
||||
DrawerActionHelpers,
|
||||
RouteProp,
|
||||
} from '@react-navigation/native';
|
||||
import type { HeaderOptions } from '@react-navigation/elements';
|
||||
|
||||
export type Scene = {
|
||||
route: Route<string>;
|
||||
@@ -20,168 +23,52 @@ export type Scene = {
|
||||
|
||||
export type Layout = { width: number; height: number };
|
||||
|
||||
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
/**
|
||||
* Position of the drawer on the screen. Defaults to `left`.
|
||||
*/
|
||||
drawerPosition?: 'left' | 'right';
|
||||
/**
|
||||
* Type of the drawer. It determines how the drawer looks and animates.
|
||||
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
|
||||
* - `back`: The drawer is revealed behind the screen on swipe.
|
||||
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
|
||||
* - `permanent`: A permanent drawer is shown as a sidebar.
|
||||
*/
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
edgeWidth?: number;
|
||||
/**
|
||||
* Whether the statusbar should be hidden when the drawer is pulled or opens,
|
||||
*/
|
||||
hideStatusBar?: boolean;
|
||||
/**
|
||||
* Whether the keyboard should be dismissed when the swipe gesture begins.
|
||||
* Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling.
|
||||
*/
|
||||
keyboardDismissMode?: 'on-drag' | 'none';
|
||||
/**
|
||||
* Minimum swipe distance threshold that should activate opening the drawer.
|
||||
*/
|
||||
minSwipeDistance?: number;
|
||||
/**
|
||||
* Color of the overlay to be displayed on top of the content view when drawer gets open.
|
||||
* The opacity is animated from `0` to `1` when the drawer opens.
|
||||
*/
|
||||
overlayColor?: string;
|
||||
/**
|
||||
* Animation of the statusbar when hiding it. use in combination with `hideStatusBar`.
|
||||
*/
|
||||
statusBarAnimation?: 'slide' | 'none' | 'fade';
|
||||
/**
|
||||
* Props to pass to the underlying pan gesture handler.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureHandlerProps?: PanGestureHandlerProperties;
|
||||
/**
|
||||
* Whether the screens should render the first time they are accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render all screens on initial render.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
export type DrawerNavigationConfig = {
|
||||
/**
|
||||
* Function that returns React element to render as the content of the drawer, for example, navigation items.
|
||||
* Defaults to `DrawerContent`.
|
||||
*/
|
||||
drawerContent?: (props: DrawerContentComponentProps<T>) => React.ReactNode;
|
||||
/**
|
||||
* Options for the content component which will be passed as props.
|
||||
*/
|
||||
drawerContentOptions?: T;
|
||||
/**
|
||||
* Style object for the component wrapping the screen content.
|
||||
*/
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object for the drawer component.
|
||||
* You can pass a custom background color for a drawer or a custom width here.
|
||||
*/
|
||||
drawerStyle?: StyleProp<ViewStyle>;
|
||||
drawerContent?: (props: DrawerContentComponentProps) => React.ReactNode;
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
/**
|
||||
* Whether to use the legacy implementation based on Reanimated 1.
|
||||
* The new implementation based on Reanimated 2 will perform better,
|
||||
* but you need additional configuration and need to use Hermes with Flipper to debug.
|
||||
*
|
||||
* This defaults to `true` in following cases:
|
||||
* - Reanimated 2 is not configured
|
||||
* - App is connected to Chrome debugger (Reanimated 2 cannot be used with Chrome debugger)
|
||||
*
|
||||
* Otherwise, it defaults to `false`
|
||||
*/
|
||||
useLegacyImplementation?: boolean;
|
||||
};
|
||||
|
||||
export type DrawerHeaderOptions = {
|
||||
/**
|
||||
* String or a function that returns a React Element to be used by the header.
|
||||
* Defaults to scene `title`.
|
||||
* It receives `allowFontScaling`, `tintColor`, `style` and `children` in the options object as an argument.
|
||||
* The title string is passed in `children`.
|
||||
*/
|
||||
headerTitle?:
|
||||
| string
|
||||
| ((props: {
|
||||
/**
|
||||
* Whether title font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
allowFontScaling?: boolean;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
tintColor?: string;
|
||||
/**
|
||||
* Content of the title element. Usually the title string.
|
||||
*/
|
||||
children?: string;
|
||||
/**
|
||||
* Style object for the title element.
|
||||
*/
|
||||
style?: StyleProp<TextStyle>;
|
||||
}) => React.ReactNode);
|
||||
/**
|
||||
* How to align the the header title.
|
||||
* Defaults to `center` on iOS and `left` on Android.
|
||||
*/
|
||||
headerTitleAlign?: 'left' | 'center';
|
||||
/**
|
||||
* Style object for the title component.
|
||||
*/
|
||||
headerTitleStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Whether header title font should scale to respect Text Size accessibility settings. Defaults to `false`.
|
||||
*/
|
||||
headerTitleAllowFontScaling?: boolean;
|
||||
/**
|
||||
* Function which returns a React Element to display on the left side of the header.
|
||||
*/
|
||||
headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
|
||||
/**
|
||||
* Accessibility label for the header left button.
|
||||
*/
|
||||
headerLeftAccessibilityLabel?: string;
|
||||
/**
|
||||
* Function which returns a React Element to display on the right side of the header.
|
||||
*/
|
||||
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
|
||||
/**
|
||||
* Color for material ripple (Android >= 5.0 only).
|
||||
*/
|
||||
headerPressColorAndroid?: string;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
headerTintColor?: string;
|
||||
/**
|
||||
* Style object for the header. You can specify a custom background color here, for example.
|
||||
*/
|
||||
headerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Extra padding to add at the top of header to account for translucent status bar.
|
||||
* By default, it uses the top value from the safe area insets of the device.
|
||||
* Pass 0 or a custom value to disable the default behaviour, and customize the height.
|
||||
*/
|
||||
headerStatusBarHeight?: number;
|
||||
};
|
||||
|
||||
export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
export type DrawerNavigationOptions = HeaderOptions & {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Function that given `HeaderProps` returns a React Element to display as a header.
|
||||
* Whether this screens should render the first time it's accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render the screen on initial render.
|
||||
*/
|
||||
lazy?: boolean;
|
||||
|
||||
/**
|
||||
* Function that returns a React Element to display as a header.
|
||||
*/
|
||||
header?: (props: DrawerHeaderProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether to show the header. The header is not shown by default.
|
||||
* Setting this to `true` shows the header.
|
||||
* Whether to show the header. Setting this to `false` hides the header.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
headerShown?: boolean;
|
||||
|
||||
@@ -203,6 +90,87 @@ export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
focused: boolean;
|
||||
}) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the active item in the drawer.
|
||||
*/
|
||||
drawerActiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Background color for the active item in the drawer.
|
||||
*/
|
||||
drawerActiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Color for the icon and label in the inactive items in the drawer.
|
||||
*/
|
||||
drawerInactiveTintColor?: string;
|
||||
|
||||
/**
|
||||
* Background color for the inactive items in the drawer.
|
||||
*/
|
||||
drawerInactiveBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Style object for the single item, which can contain an icon and/or a label.
|
||||
*/
|
||||
drawerItemStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Style object to apply to the `Text` inside content section which renders a label.
|
||||
*/
|
||||
drawerLabelStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the content section.
|
||||
*/
|
||||
drawerContentContainerStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the wrapper view.
|
||||
*/
|
||||
drawerContentStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Style object for the drawer component.
|
||||
* You can pass a custom background color for a drawer or a custom width here.
|
||||
*/
|
||||
drawerStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Position of the drawer on the screen. Defaults to `left`.
|
||||
*/
|
||||
drawerPosition?: 'left' | 'right';
|
||||
|
||||
/**
|
||||
* Type of the drawer. It determines how the drawer looks and animates.
|
||||
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
|
||||
* - `back`: The drawer is revealed behind the screen on swipe.
|
||||
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
|
||||
* - `permanent`: A permanent drawer is shown as a sidebar.
|
||||
*/
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
|
||||
/**
|
||||
* Whether the statusbar should be hidden when the drawer is pulled or opens,
|
||||
*/
|
||||
drawerHideStatusBarOnOpen?: boolean;
|
||||
|
||||
/**
|
||||
* Animation of the statusbar when hiding it. use in combination with `drawerHideStatusBarOnOpen`.
|
||||
*/
|
||||
drawerStatusBarAnimation?: 'slide' | 'none' | 'fade';
|
||||
|
||||
/**
|
||||
* Color of the overlay to be displayed on top of the content view when drawer gets open.
|
||||
* The opacity is animated from `0` to `1` when the drawer opens.
|
||||
*/
|
||||
overlayColor?: string;
|
||||
|
||||
/**
|
||||
* Style object for the component wrapping the screen content.
|
||||
*/
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* Whether you can use gestures to open or close the drawer.
|
||||
* Setting this to `false` disables swipe gestures as well as tap on overlay to close.
|
||||
@@ -212,6 +180,12 @@ export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
*/
|
||||
gestureEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* Props to pass to the underlying pan gesture handler.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureHandlerProps?: PanGestureHandlerProperties;
|
||||
|
||||
/**
|
||||
* Whether you can use swipe gestures to open or close the drawer.
|
||||
* Defaults to `true`.
|
||||
@@ -219,6 +193,23 @@ export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
*/
|
||||
swipeEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
swipeEdgeWidth?: number;
|
||||
|
||||
/**
|
||||
* Minimum swipe distance threshold that should activate opening the drawer.
|
||||
*/
|
||||
swipeMinDistance?: number;
|
||||
|
||||
/**
|
||||
* Whether the keyboard should be dismissed when the swipe gesture begins.
|
||||
* Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling.
|
||||
*/
|
||||
keyboardDismissMode?: 'on-drag' | 'none';
|
||||
|
||||
/**
|
||||
* Whether this screen should be unmounted when navigating away from it.
|
||||
* Defaults to `false`.
|
||||
@@ -226,50 +217,10 @@ export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
unmountOnBlur?: boolean;
|
||||
};
|
||||
|
||||
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
|
||||
export type DrawerContentComponentProps = {
|
||||
state: DrawerNavigationState<ParamListBase>;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
/**
|
||||
* Animated node which represents the current progress of the drawer's open state.
|
||||
* `0` is closed, `1` is open.
|
||||
*/
|
||||
progress: Animated.Node<number>;
|
||||
};
|
||||
|
||||
export type DrawerContentOptions = {
|
||||
/**
|
||||
* Color for the icon and label in the active item in the drawer.
|
||||
*/
|
||||
activeTintColor?: string;
|
||||
/**
|
||||
* Background color for the active item in the drawer.
|
||||
*/
|
||||
activeBackgroundColor?: string;
|
||||
/**
|
||||
* Color for the icon and label in the inactive items in the drawer.
|
||||
*/
|
||||
inactiveTintColor?: string;
|
||||
/**
|
||||
* Background color for the inactive items in the drawer.
|
||||
*/
|
||||
inactiveBackgroundColor?: string;
|
||||
/**
|
||||
* Style object for the single item, which can contain an icon and/or a label.
|
||||
*/
|
||||
itemStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object to apply to the `Text` inside content section which renders a label.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Style object for the content section.
|
||||
*/
|
||||
contentContainerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Style object for the wrapper view.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type DrawerHeaderProps = {
|
||||
@@ -278,24 +229,20 @@ export type DrawerHeaderProps = {
|
||||
*/
|
||||
layout: Layout;
|
||||
/**
|
||||
* Object representing the current scene, such as the route object and descriptor.
|
||||
* Options for the current screen.
|
||||
*/
|
||||
scene: {
|
||||
route: Route<string>;
|
||||
descriptor: DrawerDescriptor;
|
||||
};
|
||||
options: DrawerNavigationOptions;
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
navigation: DrawerNavigationProp<ParamListBase>;
|
||||
};
|
||||
|
||||
export type DrawerNavigationEventMap = {
|
||||
/**
|
||||
* Event which fires when the drawer opens.
|
||||
*/
|
||||
drawerOpen: { data: undefined };
|
||||
/**
|
||||
* Event which fires when the drawer closes.
|
||||
*/
|
||||
drawerClose: { data: undefined };
|
||||
};
|
||||
export type DrawerNavigationEventMap = {};
|
||||
|
||||
export type DrawerNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
@@ -324,12 +271,30 @@ export type DrawerScreenProps<
|
||||
};
|
||||
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerNavigationOptions
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationProp<ParamListBase>,
|
||||
RouteProp<ParamListBase, string>
|
||||
>;
|
||||
|
||||
export type DrawerDescriptorMap = {
|
||||
[key: string]: DrawerDescriptor;
|
||||
export type DrawerDescriptorMap = Record<string, DrawerDescriptor>;
|
||||
|
||||
export type DrawerProps = {
|
||||
dimensions: { width: number; height: number };
|
||||
drawerPosition: 'left' | 'right';
|
||||
drawerStyle?: StyleProp<ViewStyle>;
|
||||
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
||||
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
|
||||
hideStatusBarOnOpen: boolean;
|
||||
keyboardDismissMode: 'none' | 'on-drag';
|
||||
onClose: () => void;
|
||||
onOpen: () => void;
|
||||
open: boolean;
|
||||
overlayStyle?: StyleProp<ViewStyle>;
|
||||
renderDrawerContent: () => React.ReactNode;
|
||||
renderSceneContent: () => React.ReactNode;
|
||||
statusBarAnimation: 'slide' | 'none' | 'fade';
|
||||
swipeDistanceThreshold: number;
|
||||
swipeEdgeWidth: number;
|
||||
swipeEnabled: boolean;
|
||||
swipeVelocityThreshold: number;
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const DrawerOpenContext = React.createContext<boolean | null>(null);
|
||||
|
||||
export default DrawerOpenContext;
|
||||
6
packages/drawer/src/utils/DrawerProgressContext.tsx
Normal file
6
packages/drawer/src/utils/DrawerProgressContext.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type Animated from 'react-native-reanimated';
|
||||
|
||||
export default React.createContext<
|
||||
Readonly<Animated.SharedValue<number>> | Animated.Node<number> | undefined
|
||||
>(undefined);
|
||||
7
packages/drawer/src/utils/DrawerStatusContext.tsx
Normal file
7
packages/drawer/src/utils/DrawerStatusContext.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const DrawerStatusContext = React.createContext<'open' | 'closed' | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export default DrawerStatusContext;
|
||||
@@ -3,14 +3,18 @@ import type {
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export default function getIsDrawerOpenFromState(
|
||||
export default function getDrawerStatusFromState(
|
||||
state: DrawerNavigationState<ParamListBase>
|
||||
): boolean {
|
||||
) {
|
||||
if (state.history == null) {
|
||||
throw new Error(
|
||||
"Couldn't find the drawer status in the state object. Is it a valid state object of drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return state.history.some((it) => it.type === 'drawer');
|
||||
const entry = state.history.find((it) => it.type === 'drawer') as
|
||||
| { type: 'drawer'; status: 'open' }
|
||||
| undefined;
|
||||
|
||||
return entry?.status ?? 'closed';
|
||||
}
|
||||
17
packages/drawer/src/utils/useDrawerProgress.tsx
Normal file
17
packages/drawer/src/utils/useDrawerProgress.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import type Animated from 'react-native-reanimated';
|
||||
import DrawerProgressContext from './DrawerProgressContext';
|
||||
|
||||
export default function useDrawerProgress():
|
||||
| Readonly<Animated.SharedValue<number>>
|
||||
| Animated.Node<number> {
|
||||
const progress = React.useContext(DrawerProgressContext);
|
||||
|
||||
if (progress === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't find a drawer. Is your component inside a drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user