Compare commits
27 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7754eb450f | ||
|
|
95b2599877 | ||
|
|
efcfa7121f | ||
|
|
a8e27ef448 | ||
|
|
946d2923d7 | ||
|
|
794339eeed | ||
|
|
53141a6436 | ||
|
|
a2337648bf | ||
|
|
8f764d8b08 | ||
|
|
f8e998b10c | ||
|
|
da35085f1e | ||
|
|
1f5fb5481a | ||
|
|
18bbd177d9 | ||
|
|
151055cf5a | ||
|
|
52172453df | ||
|
|
7bc385e4f3 | ||
|
|
6ac4d40140 | ||
|
|
dbe961ba5b | ||
|
|
05d4e4d3be | ||
|
|
48b2e77730 | ||
|
|
e08c91ff0a | ||
|
|
5bd682f0bf | ||
|
|
50a161dc3d | ||
|
|
360b0e9958 | ||
|
|
e50c8aa942 | ||
|
|
8f0efc8db5 | ||
|
|
7de6677e72 |
42
.github/workflows/check-repro.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Check for repro
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited]
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-repro:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v2
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const body = context.payload.comment
|
||||||
|
? context.payload.comment.body
|
||||||
|
: context.payload.issue.body
|
||||||
|
|
||||||
|
if (!/https?:\/\/((github\.com\/[^/]+\/[^/]+\/?[\s\n]+)|(snack\.expo\.io\/.+))/gm.test(body)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.issues.addLabels({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
labels: ['repro provided'],
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await github.issues.removeLabel({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
name: 'needs repro',
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (!/Label does not exist/.test(error.message)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,14 +68,9 @@ module.exports = {
|
|||||||
enhanceMiddleware: (middleware) => {
|
enhanceMiddleware: (middleware) => {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
// When an asset is imported outside the project root, it has wrong path on Android
|
// When an asset is imported outside the project root, it has wrong path on Android
|
||||||
// This happens for the back button in stack, so we fix the path to correct one
|
// So we fix the path to correct one
|
||||||
const assets = '/packages/stack/src/views/assets';
|
if (/\/packages\/.+\.png\?.+$/.test(req.url)) {
|
||||||
|
req.url = `/assets/../${req.url}`;
|
||||||
if (req.url.startsWith(assets)) {
|
|
||||||
req.url = req.url.replace(
|
|
||||||
assets,
|
|
||||||
'/assets/../packages/stack/src/views/assets'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return middleware(req, res, next);
|
return middleware(req, res, next);
|
||||||
|
|||||||
@@ -86,12 +86,18 @@ export default function BottomTabsScreen({
|
|||||||
>
|
>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={SimpleStackScreen}
|
|
||||||
options={{
|
options={{
|
||||||
title: 'Article',
|
title: 'Article',
|
||||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{(props) => (
|
||||||
|
<SimpleStackScreen
|
||||||
|
{...props}
|
||||||
|
screenOptions={{ headerShown: false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</BottomTabs.Screen>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
component={Chat}
|
component={Chat}
|
||||||
|
|||||||
@@ -23,13 +23,19 @@ export default function MaterialBottomTabsScreen() {
|
|||||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||||
<MaterialBottomTabs.Screen
|
<MaterialBottomTabs.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={SimpleStackScreen}
|
|
||||||
options={{
|
options={{
|
||||||
tabBarLabel: 'Article',
|
tabBarLabel: 'Article',
|
||||||
tabBarIcon: 'file-document-box',
|
tabBarIcon: 'file-document-box',
|
||||||
tabBarColor: '#C9E7F8',
|
tabBarColor: '#C9E7F8',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{(props) => (
|
||||||
|
<SimpleStackScreen
|
||||||
|
{...props}
|
||||||
|
screenOptions={{ headerShown: false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MaterialBottomTabs.Screen>
|
||||||
<MaterialBottomTabs.Screen
|
<MaterialBottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
component={Chat}
|
component={Chat}
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
|||||||
cardOverlayEnabled: true,
|
cardOverlayEnabled: true,
|
||||||
gestureEnabled: true,
|
gestureEnabled: true,
|
||||||
headerStatusBarHeight:
|
headerStatusBarHeight:
|
||||||
navigation.dangerouslyGetState().routes.indexOf(route) > 0
|
navigation
|
||||||
|
.dangerouslyGetState()
|
||||||
|
.routes.findIndex((r: any) => r.key === route.key) > 0
|
||||||
? 0
|
? 0
|
||||||
: undefined,
|
: undefined,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -77,18 +77,28 @@ const InputScreen = ({
|
|||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
Alert.alert(
|
if (Platform.OS === 'web') {
|
||||||
'Discard changes?',
|
const discard = confirm(
|
||||||
'You have unsaved changes. Are you sure to discard them and leave the screen?',
|
'You have unsaved changes. Discard them and leave the screen?'
|
||||||
[
|
);
|
||||||
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
|
|
||||||
{
|
if (discard) {
|
||||||
text: 'Discard',
|
navigation.dispatch(action);
|
||||||
style: 'destructive',
|
}
|
||||||
onPress: () => navigation.dispatch(action),
|
} else {
|
||||||
},
|
Alert.alert(
|
||||||
]
|
'Discard changes?',
|
||||||
);
|
'You have unsaved changes. Discard them and leave the screen?',
|
||||||
|
[
|
||||||
|
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
|
||||||
|
{
|
||||||
|
text: 'Discard',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => navigation.dispatch(action),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
[hasUnsavedChanges, navigation]
|
[hasUnsavedChanges, navigation]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from 'react-native-paper';
|
|||||||
import type { ParamListBase } from '@react-navigation/native';
|
import type { ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
|
StackNavigationOptions,
|
||||||
StackScreenProps,
|
StackScreenProps,
|
||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
@@ -105,7 +106,10 @@ const SimpleStack = createStackNavigator<SimpleStackParams>();
|
|||||||
|
|
||||||
export default function SimpleStackScreen({
|
export default function SimpleStackScreen({
|
||||||
navigation,
|
navigation,
|
||||||
}: StackScreenProps<ParamListBase>) {
|
screenOptions,
|
||||||
|
}: StackScreenProps<ParamListBase> & {
|
||||||
|
screenOptions?: StackNavigationOptions;
|
||||||
|
}) {
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
@@ -113,7 +117,7 @@ export default function SimpleStackScreen({
|
|||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleStack.Navigator>
|
<SimpleStack.Navigator screenOptions={screenOptions}>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={ArticleScreen}
|
component={ArticleScreen}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
Provider as PaperProvider,
|
Provider as PaperProvider,
|
||||||
DefaultTheme as PaperLightTheme,
|
DefaultTheme as PaperLightTheme,
|
||||||
DarkTheme as PaperDarkTheme,
|
DarkTheme as PaperDarkTheme,
|
||||||
Appbar,
|
|
||||||
List,
|
List,
|
||||||
Divider,
|
Divider,
|
||||||
Text,
|
Text,
|
||||||
@@ -28,10 +27,7 @@ import {
|
|||||||
PathConfigMap,
|
PathConfigMap,
|
||||||
NavigationContainerRef,
|
NavigationContainerRef,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import {
|
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||||
createDrawerNavigator,
|
|
||||||
DrawerScreenProps,
|
|
||||||
} from '@react-navigation/drawer';
|
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackScreenProps,
|
StackScreenProps,
|
||||||
@@ -65,8 +61,7 @@ if (Platform.OS !== 'web') {
|
|||||||
enableScreens();
|
enableScreens();
|
||||||
|
|
||||||
type RootDrawerParamList = {
|
type RootDrawerParamList = {
|
||||||
Root: undefined;
|
Examples: undefined;
|
||||||
Another: undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCREENS = {
|
const SCREENS = {
|
||||||
@@ -231,50 +226,49 @@ export default function App() {
|
|||||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||||
prefixes: LinkingPrefixes,
|
prefixes: LinkingPrefixes,
|
||||||
config: {
|
config: {
|
||||||
screens: {
|
initialRouteName: 'Home',
|
||||||
Root: {
|
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||||
path: '',
|
(acc, name) => {
|
||||||
initialRouteName: 'Home',
|
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
const path = name
|
||||||
(acc, name) => {
|
.replace(/([A-Z]+)/g, '-$1')
|
||||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
.replace(/^-/, '')
|
||||||
const path = name
|
.toLowerCase();
|
||||||
.replace(/([A-Z]+)/g, '-$1')
|
|
||||||
.replace(/^-/, '')
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
acc[name] = {
|
acc[name] = {
|
||||||
path,
|
path,
|
||||||
screens: {
|
screens: {
|
||||||
Article: {
|
Article: {
|
||||||
path: 'article/:author?',
|
path: 'article/:author?',
|
||||||
parse: {
|
parse: {
|
||||||
author: (author) =>
|
author: (author) =>
|
||||||
author.charAt(0).toUpperCase() +
|
author.charAt(0).toUpperCase() +
|
||||||
author.slice(1).replace(/-/g, ' '),
|
author.slice(1).replace(/-/g, ' '),
|
||||||
},
|
|
||||||
stringify: {
|
|
||||||
author: (author: string) =>
|
|
||||||
author.toLowerCase().replace(/\s/g, '-'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Albums: 'music',
|
|
||||||
Chat: 'chat',
|
|
||||||
Contacts: 'people',
|
|
||||||
NewsFeed: 'feed',
|
|
||||||
Dialog: 'dialog',
|
|
||||||
},
|
},
|
||||||
};
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
return acc;
|
author.toLowerCase().replace(/\s/g, '-'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Albums: 'music',
|
||||||
|
Chat: 'chat',
|
||||||
|
Contacts: 'people',
|
||||||
|
NewsFeed: 'feed',
|
||||||
|
Dialog: 'dialog',
|
||||||
},
|
},
|
||||||
{
|
};
|
||||||
Home: '',
|
|
||||||
NotFound: '*',
|
return acc;
|
||||||
}
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
Home: {
|
||||||
|
screens: {
|
||||||
|
Examples: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NotFound: '*',
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
fallback={<Text>Loading…</Text>}
|
fallback={<Text>Loading…</Text>}
|
||||||
@@ -283,35 +277,29 @@ export default function App() {
|
|||||||
`${options?.title ?? route?.name} - React Navigation Example`,
|
`${options?.title ?? route?.name} - React Navigation Example`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
<Stack.Navigator
|
||||||
<Drawer.Screen
|
screenOptions={{
|
||||||
name="Root"
|
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack.Screen
|
||||||
|
name="Home"
|
||||||
options={{
|
options={{
|
||||||
title: 'Examples',
|
headerShown: false,
|
||||||
drawerIcon: ({ size, color }) => (
|
|
||||||
<MaterialIcons size={size} color={color} name="folder" />
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ navigation }: DrawerScreenProps<RootDrawerParamList>) => (
|
{() => (
|
||||||
<Stack.Navigator
|
<Drawer.Navigator
|
||||||
screenOptions={{
|
drawerType={isLargeScreen ? 'permanent' : undefined}
|
||||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
screenOptions={{ headerShown: true }}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Stack.Screen
|
<Drawer.Screen
|
||||||
name="Home"
|
name="Examples"
|
||||||
options={{
|
options={{
|
||||||
title: 'Examples',
|
title: 'Examples',
|
||||||
headerLeft: isLargeScreen
|
drawerIcon: ({ size, color }) => (
|
||||||
? undefined
|
<MaterialIcons size={size} color={color} name="folder" />
|
||||||
: () => (
|
),
|
||||||
<Appbar.Action
|
|
||||||
color={theme.colors.text}
|
|
||||||
icon="menu"
|
|
||||||
onPress={() => navigation.toggleDrawer()}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
||||||
@@ -352,26 +340,24 @@ export default function App() {
|
|||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
</Stack.Screen>
|
</Drawer.Screen>
|
||||||
<Stack.Screen
|
</Drawer.Navigator>
|
||||||
name="NotFound"
|
|
||||||
component={NotFound}
|
|
||||||
options={{ title: 'Oops!' }}
|
|
||||||
/>
|
|
||||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
|
||||||
(name) => (
|
|
||||||
<Stack.Screen
|
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
getComponent={() => SCREENS[name].component}
|
|
||||||
options={{ title: SCREENS[name].title }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Stack.Navigator>
|
|
||||||
)}
|
)}
|
||||||
</Drawer.Screen>
|
</Stack.Screen>
|
||||||
</Drawer.Navigator>
|
<Stack.Screen
|
||||||
|
name="NotFound"
|
||||||
|
component={NotFound}
|
||||||
|
options={{ title: 'Oops!' }}
|
||||||
|
/>
|
||||||
|
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map((name) => (
|
||||||
|
<Stack.Screen
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
getComponent={() => SCREENS[name].component}
|
||||||
|
options={{ title: SCREENS[name].title }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,42 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.11.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@5.11.2) (2020-11-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.11.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.0...@react-navigation/bottom-tabs@5.11.1) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.7...@react-navigation/bottom-tabs@5.11.0) (2020-11-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add a hook to get bottom tab bar height ([e08c91f](https://github.com/react-navigation/react-navigation/commit/e08c91ff0a3df13dc6e6096a3e95f60722e6946b)), closes [#8037](https://github.com/react-navigation/react-navigation/issues/8037) [#8536](https://github.com/react-navigation/react-navigation/issues/8536)
|
||||||
|
* add a tabBarBadgeStyle option to customize the badge ([6ac4d40](https://github.com/react-navigation/react-navigation/commit/6ac4d40140189a29d857c4d1203bced6929f7baf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.10.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.6...@react-navigation/bottom-tabs@5.10.7) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.10.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.5...@react-navigation/bottom-tabs@5.10.6) (2020-11-04)
|
## [5.10.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.5...@react-navigation/bottom-tabs@5.10.6) (2020-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/bottom-tabs",
|
"name": "@react-navigation/bottom-tabs",
|
||||||
"description": "Bottom tab navigator following iOS design guidelines",
|
"description": "Bottom tab navigator following iOS design guidelines",
|
||||||
"version": "5.10.6",
|
"version": "5.11.2",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@testing-library/react-native": "^7.1.0",
|
"@testing-library/react-native": "^7.1.0",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export { default as createBottomTabNavigator } from './navigators/createBottomTa
|
|||||||
export { default as BottomTabView } from './views/BottomTabView';
|
export { default as BottomTabView } from './views/BottomTabView';
|
||||||
export { default as BottomTabBar } from './views/BottomTabBar';
|
export { default as BottomTabBar } from './views/BottomTabBar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
export { default as BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
|
||||||
|
|
||||||
|
export { default as useBottomTabBarHeight } from './utils/useBottomTabBarHeight';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ export type BottomTabNavigationOptions = {
|
|||||||
*/
|
*/
|
||||||
tabBarBadge?: number | string;
|
tabBarBadge?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom style for the tab bar badge.
|
||||||
|
* You can specify a background color or text color here.
|
||||||
|
*/
|
||||||
|
tabBarBadgeStyle?: StyleProp<TextStyle>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
|
* Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
|
||||||
* It's recommended to set this if you don't have a label for the tab.
|
* It's recommended to set this if you don't have a label for the tab.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default React.createContext<((height: number) => void) | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default React.createContext<number | undefined>(undefined);
|
||||||
14
packages/bottom-tabs/src/utils/useBottomTabBarHeight.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import BottomTabBarHeightContext from './BottomTabBarHeightContext';
|
||||||
|
|
||||||
|
export default function useFloatingBottomTabBarHeight() {
|
||||||
|
const height = React.useContext(BottomTabBarHeightContext);
|
||||||
|
|
||||||
|
if (height === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"Couldn't find the bottom tab bar height. Are you inside a screen in Bottom Tab Navigator?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
@@ -5,20 +5,25 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
Platform,
|
Platform,
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
|
StyleProp,
|
||||||
|
ViewStyle,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import {
|
import {
|
||||||
NavigationContext,
|
NavigationContext,
|
||||||
NavigationRouteContext,
|
NavigationRouteContext,
|
||||||
|
TabNavigationState,
|
||||||
|
ParamListBase,
|
||||||
CommonActions,
|
CommonActions,
|
||||||
useTheme,
|
useTheme,
|
||||||
useLinkBuilder,
|
useLinkBuilder,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { useSafeArea } from 'react-native-safe-area-context';
|
import { useSafeArea, EdgeInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import BottomTabItem from './BottomTabItem';
|
import BottomTabItem from './BottomTabItem';
|
||||||
|
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||||
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
|
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
|
||||||
import type { BottomTabBarProps } from '../types';
|
import type { BottomTabBarProps, LabelPosition } from '../types';
|
||||||
|
|
||||||
type Props = BottomTabBarProps & {
|
type Props = BottomTabBarProps & {
|
||||||
activeTintColor?: string;
|
activeTintColor?: string;
|
||||||
@@ -31,13 +36,93 @@ const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
|||||||
|
|
||||||
const useNativeDriver = Platform.OS !== 'web';
|
const useNativeDriver = Platform.OS !== 'web';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
state: TabNavigationState<ParamListBase>;
|
||||||
|
layout: { height: number; width: number };
|
||||||
|
dimensions: { height: number; width: number };
|
||||||
|
tabStyle: StyleProp<ViewStyle>;
|
||||||
|
labelPosition: LabelPosition | undefined;
|
||||||
|
adaptive: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldUseHorizontalLabels = ({
|
||||||
|
state,
|
||||||
|
layout,
|
||||||
|
dimensions,
|
||||||
|
adaptive = true,
|
||||||
|
labelPosition,
|
||||||
|
tabStyle,
|
||||||
|
}: Options) => {
|
||||||
|
if (labelPosition) {
|
||||||
|
return labelPosition === 'beside-icon';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adaptive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout.width >= 768) {
|
||||||
|
// Screen size matches a tablet
|
||||||
|
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||||
|
|
||||||
|
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
||||||
|
|
||||||
|
if (flattenedStyle) {
|
||||||
|
if (typeof flattenedStyle.width === 'number') {
|
||||||
|
maxTabItemWidth = flattenedStyle.width;
|
||||||
|
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||||
|
maxTabItemWidth = flattenedStyle.maxWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.routes.length * maxTabItemWidth <= layout.width;
|
||||||
|
} else {
|
||||||
|
return dimensions.width > dimensions.height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPaddingBottom = (insets: EdgeInsets) =>
|
||||||
|
Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);
|
||||||
|
|
||||||
|
export const getTabBarHeight = ({
|
||||||
|
dimensions,
|
||||||
|
insets,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: Options & {
|
||||||
|
insets: EdgeInsets;
|
||||||
|
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||||
|
}) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const customHeight = StyleSheet.flatten(style)?.height;
|
||||||
|
|
||||||
|
if (typeof customHeight === 'number') {
|
||||||
|
return customHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLandscape = dimensions.width > dimensions.height;
|
||||||
|
const horizontalLabels = shouldUseHorizontalLabels({ dimensions, ...rest });
|
||||||
|
const paddingBottom = getPaddingBottom(insets);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Platform.OS === 'ios' &&
|
||||||
|
!Platform.isPad &&
|
||||||
|
isLandscape &&
|
||||||
|
horizontalLabels
|
||||||
|
) {
|
||||||
|
return COMPACT_TABBAR_HEIGHT + paddingBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_TABBAR_HEIGHT + paddingBottom;
|
||||||
|
};
|
||||||
|
|
||||||
export default function BottomTabBar({
|
export default function BottomTabBar({
|
||||||
state,
|
state,
|
||||||
navigation,
|
navigation,
|
||||||
descriptors,
|
descriptors,
|
||||||
activeBackgroundColor,
|
activeBackgroundColor,
|
||||||
activeTintColor,
|
activeTintColor,
|
||||||
adaptive = true,
|
adaptive,
|
||||||
allowFontScaling,
|
allowFontScaling,
|
||||||
inactiveBackgroundColor,
|
inactiveBackgroundColor,
|
||||||
inactiveTintColor,
|
inactiveTintColor,
|
||||||
@@ -60,6 +145,8 @@ export default function BottomTabBar({
|
|||||||
const dimensions = useWindowDimensions();
|
const dimensions = useWindowDimensions();
|
||||||
const isKeyboardShown = useIsKeyboardShown();
|
const isKeyboardShown = useIsKeyboardShown();
|
||||||
|
|
||||||
|
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
||||||
|
|
||||||
const shouldShowTabBar =
|
const shouldShowTabBar =
|
||||||
focusedOptions.tabBarVisible !== false &&
|
focusedOptions.tabBarVisible !== false &&
|
||||||
!(keyboardHidesTabBar && isKeyboardShown);
|
!(keyboardHidesTabBar && isKeyboardShown);
|
||||||
@@ -120,11 +207,19 @@ export default function BottomTabBar({
|
|||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLandscape = () => dimensions.width > dimensions.height;
|
|
||||||
|
|
||||||
const handleLayout = (e: LayoutChangeEvent) => {
|
const handleLayout = (e: LayoutChangeEvent) => {
|
||||||
const { height, width } = e.nativeEvent.layout;
|
const { height, width } = e.nativeEvent.layout;
|
||||||
|
|
||||||
|
const topBorderWidth =
|
||||||
|
// @ts-ignore
|
||||||
|
StyleSheet.flatten([styles.tabBar, style])?.borderTopWidth;
|
||||||
|
|
||||||
|
onHeightChange?.(
|
||||||
|
height +
|
||||||
|
paddingBottom +
|
||||||
|
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
|
||||||
|
);
|
||||||
|
|
||||||
setLayout((layout) => {
|
setLayout((layout) => {
|
||||||
if (height === layout.height && width === layout.width) {
|
if (height === layout.height && width === layout.width) {
|
||||||
return layout;
|
return layout;
|
||||||
@@ -138,34 +233,6 @@ export default function BottomTabBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { routes } = state;
|
const { routes } = state;
|
||||||
const shouldUseHorizontalLabels = () => {
|
|
||||||
if (labelPosition) {
|
|
||||||
return labelPosition === 'beside-icon';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adaptive) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.width >= 768) {
|
|
||||||
// Screen size matches a tablet
|
|
||||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
|
||||||
|
|
||||||
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
|
||||||
|
|
||||||
if (flattenedStyle) {
|
|
||||||
if (typeof flattenedStyle.width === 'number') {
|
|
||||||
maxTabItemWidth = flattenedStyle.width;
|
|
||||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
|
||||||
maxTabItemWidth = flattenedStyle.maxWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes.length * maxTabItemWidth <= layout.width;
|
|
||||||
} else {
|
|
||||||
return isLandscape();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultInsets = useSafeArea();
|
const defaultInsets = useSafeArea();
|
||||||
|
|
||||||
@@ -176,22 +243,26 @@ export default function BottomTabBar({
|
|||||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||||
};
|
};
|
||||||
|
|
||||||
const paddingBottom = Math.max(
|
const paddingBottom = getPaddingBottom(insets);
|
||||||
insets.bottom - Platform.select({ ios: 4, default: 0 }),
|
const tabBarHeight = getTabBarHeight({
|
||||||
0
|
state,
|
||||||
);
|
insets,
|
||||||
|
dimensions,
|
||||||
|
layout,
|
||||||
|
adaptive,
|
||||||
|
labelPosition,
|
||||||
|
tabStyle,
|
||||||
|
style,
|
||||||
|
});
|
||||||
|
|
||||||
const getDefaultTabBarHeight = () => {
|
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
||||||
if (
|
state,
|
||||||
Platform.OS === 'ios' &&
|
dimensions,
|
||||||
!Platform.isPad &&
|
layout,
|
||||||
isLandscape() &&
|
adaptive,
|
||||||
shouldUseHorizontalLabels()
|
labelPosition,
|
||||||
) {
|
tabStyle,
|
||||||
return COMPACT_TABBAR_HEIGHT;
|
});
|
||||||
}
|
|
||||||
return DEFAULT_TABBAR_HEIGHT;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
@@ -218,7 +289,7 @@ export default function BottomTabBar({
|
|||||||
position: isTabBarHidden ? 'absolute' : (null as any),
|
position: isTabBarHidden ? 'absolute' : (null as any),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
height: getDefaultTabBarHeight() + paddingBottom,
|
height: tabBarHeight,
|
||||||
paddingBottom,
|
paddingBottom,
|
||||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||||
},
|
},
|
||||||
@@ -276,7 +347,7 @@ export default function BottomTabBar({
|
|||||||
<BottomTabItem
|
<BottomTabItem
|
||||||
route={route}
|
route={route}
|
||||||
focused={focused}
|
focused={focused}
|
||||||
horizontal={shouldUseHorizontalLabels()}
|
horizontal={hasHorizontalLabels}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
@@ -290,6 +361,7 @@ export default function BottomTabBar({
|
|||||||
button={options.tabBarButton}
|
button={options.tabBarButton}
|
||||||
icon={options.tabBarIcon}
|
icon={options.tabBarIcon}
|
||||||
badge={options.tabBarBadge}
|
badge={options.tabBarBadge}
|
||||||
|
badgeStyle={options.tabBarBadgeStyle}
|
||||||
label={label}
|
label={label}
|
||||||
showLabel={showLabel}
|
showLabel={showLabel}
|
||||||
labelStyle={labelStyle}
|
labelStyle={labelStyle}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type Props = {
|
|||||||
* Text to show in a badge on the tab icon.
|
* Text to show in a badge on the tab icon.
|
||||||
*/
|
*/
|
||||||
badge?: number | string;
|
badge?: number | string;
|
||||||
|
/**
|
||||||
|
* Custom style for the badge.
|
||||||
|
*/
|
||||||
|
badgeStyle?: StyleProp<TextStyle>;
|
||||||
/**
|
/**
|
||||||
* URL to use for the link to the tab.
|
* URL to use for the link to the tab.
|
||||||
*/
|
*/
|
||||||
@@ -122,6 +126,7 @@ export default function BottomTabBarItem({
|
|||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
badge,
|
badge,
|
||||||
|
badgeStyle,
|
||||||
to,
|
to,
|
||||||
button = ({
|
button = ({
|
||||||
children,
|
children,
|
||||||
@@ -235,6 +240,7 @@ export default function BottomTabBarItem({
|
|||||||
route={route}
|
route={route}
|
||||||
horizontal={horizontal}
|
horizontal={horizontal}
|
||||||
badge={badge}
|
badge={badge}
|
||||||
|
badgeStyle={badgeStyle}
|
||||||
activeOpacity={activeOpacity}
|
activeOpacity={activeOpacity}
|
||||||
inactiveOpacity={inactiveOpacity}
|
inactiveOpacity={inactiveOpacity}
|
||||||
activeTintColor={activeTintColor}
|
activeTintColor={activeTintColor}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
Dimensions,
|
||||||
|
StyleProp,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
NavigationHelpersContext,
|
||||||
@@ -9,9 +15,13 @@ import {
|
|||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { ScreenContainer } from 'react-native-screens';
|
import { ScreenContainer } from 'react-native-screens';
|
||||||
|
|
||||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
import SafeAreaProviderCompat, {
|
||||||
|
initialSafeAreaInsets,
|
||||||
|
} from './SafeAreaProviderCompat';
|
||||||
import ResourceSavingScene from './ResourceSavingScene';
|
import ResourceSavingScene from './ResourceSavingScene';
|
||||||
import BottomTabBar from './BottomTabBar';
|
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||||
|
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||||
|
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||||
import type {
|
import type {
|
||||||
BottomTabNavigationConfig,
|
BottomTabNavigationConfig,
|
||||||
BottomTabDescriptorMap,
|
BottomTabDescriptorMap,
|
||||||
@@ -27,6 +37,7 @@ type Props = BottomTabNavigationConfig & {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
loaded: string[];
|
loaded: string[];
|
||||||
|
tabBarHeight: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SceneContent({
|
function SceneContent({
|
||||||
@@ -67,9 +78,28 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
state: State = {
|
constructor(props: Props) {
|
||||||
loaded: [this.props.state.routes[this.props.state.index].key],
|
super(props);
|
||||||
};
|
|
||||||
|
const { state, tabBarOptions } = this.props;
|
||||||
|
|
||||||
|
const dimensions = Dimensions.get('window');
|
||||||
|
const tabBarHeight = getTabBarHeight({
|
||||||
|
state,
|
||||||
|
dimensions,
|
||||||
|
layout: { width: dimensions.width, height: 0 },
|
||||||
|
insets: initialSafeAreaInsets,
|
||||||
|
adaptive: tabBarOptions?.adaptive,
|
||||||
|
labelPosition: tabBarOptions?.labelPosition,
|
||||||
|
tabStyle: tabBarOptions?.tabStyle,
|
||||||
|
style: tabBarOptions?.style,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
loaded: [state.routes[state.index].key],
|
||||||
|
tabBarHeight: tabBarHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private renderTabBar = () => {
|
private renderTabBar = () => {
|
||||||
const {
|
const {
|
||||||
@@ -87,6 +117,16 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTabBarHeightChange = (height: number) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
if (state.tabBarHeight !== height) {
|
||||||
|
return { tabBarHeight: height };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@@ -97,7 +137,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
sceneContainerStyle,
|
sceneContainerStyle,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { routes } = state;
|
const { routes } = state;
|
||||||
const { loaded } = this.state;
|
const { loaded, tabBarHeight } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationHelpersContext.Provider value={navigation}>
|
||||||
@@ -133,13 +173,19 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
style={sceneContainerStyle}
|
style={sceneContainerStyle}
|
||||||
>
|
>
|
||||||
{descriptor.render()}
|
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||||
|
{descriptor.render()}
|
||||||
|
</BottomTabBarHeightContext.Provider>
|
||||||
</SceneContent>
|
</SceneContent>
|
||||||
</ResourceSavingScene>
|
</ResourceSavingScene>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ScreenContainer>
|
</ScreenContainer>
|
||||||
{this.renderTabBar()}
|
<BottomTabBarHeightCallbackContext.Provider
|
||||||
|
value={this.handleTabBarHeightChange}
|
||||||
|
>
|
||||||
|
{this.renderTabBar()}
|
||||||
|
</BottomTabBarHeightCallbackContext.Provider>
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaProviderCompat>
|
</SafeAreaProviderCompat>
|
||||||
</NavigationHelpersContext.Provider>
|
</NavigationHelpersContext.Provider>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
// The provider component for safe area initializes asynchornously
|
// The provider component for safe area initializes asynchornously
|
||||||
// Until the insets are available, there'll be blank screen
|
// Until the insets are available, there'll be blank screen
|
||||||
// To avoid the blank screen, we specify some initial values
|
// To avoid the blank screen, we specify some initial values
|
||||||
const initialSafeAreaInsets = {
|
export const initialSafeAreaInsets = {
|
||||||
// Approximate values which are good enough for most cases
|
// Approximate values which are good enough for most cases
|
||||||
top: getStatusBarHeight(true),
|
top: getStatusBarHeight(true),
|
||||||
bottom: getBottomSpace(),
|
bottom: getBottomSpace(),
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
StyleProp,
|
||||||
|
TextStyle,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native';
|
||||||
import type { Route } from '@react-navigation/native';
|
import type { Route } from '@react-navigation/native';
|
||||||
import Badge from './Badge';
|
import Badge from './Badge';
|
||||||
|
|
||||||
@@ -7,6 +13,7 @@ type Props = {
|
|||||||
route: Route<string>;
|
route: Route<string>;
|
||||||
horizontal: boolean;
|
horizontal: boolean;
|
||||||
badge?: string | number;
|
badge?: string | number;
|
||||||
|
badgeStyle?: StyleProp<TextStyle>;
|
||||||
activeOpacity: number;
|
activeOpacity: number;
|
||||||
inactiveOpacity: number;
|
inactiveOpacity: number;
|
||||||
activeTintColor: string;
|
activeTintColor: string;
|
||||||
@@ -22,6 +29,7 @@ type Props = {
|
|||||||
export default function TabBarIcon({
|
export default function TabBarIcon({
|
||||||
horizontal,
|
horizontal,
|
||||||
badge,
|
badge,
|
||||||
|
badgeStyle,
|
||||||
activeOpacity,
|
activeOpacity,
|
||||||
inactiveOpacity,
|
inactiveOpacity,
|
||||||
activeTintColor,
|
activeTintColor,
|
||||||
@@ -56,6 +64,7 @@ export default function TabBarIcon({
|
|||||||
style={[
|
style={[
|
||||||
styles.badge,
|
styles.badge,
|
||||||
horizontal ? styles.badgeHorizontal : styles.badgeVertical,
|
horizontal ? styles.badgeHorizontal : styles.badgeVertical,
|
||||||
|
badgeStyle,
|
||||||
]}
|
]}
|
||||||
size={(size * 3) / 4}
|
size={(size * 3) / 4}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/compat",
|
"name": "@react-navigation/compat",
|
||||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||||
"version": "5.3.6",
|
"version": "5.3.10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"react": "~16.13.1",
|
"react": "~16.13.1",
|
||||||
"typescript": "^4.0.3"
|
"typescript": "^4.0.3"
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ export default function createCompatNavigationProp<
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
// @ts-expect-error: these properties may actually exist
|
|
||||||
key: state.key,
|
key: state.key,
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
routeName: state.name,
|
routeName: state.name,
|
||||||
@@ -202,7 +201,6 @@ export default function createCompatNavigationProp<
|
|||||||
|
|
||||||
const { routes } = navigation.dangerouslyGetState();
|
const { routes } = navigation.dangerouslyGetState();
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
return routes[0].key === state.key;
|
return routes[0].key === state.key;
|
||||||
},
|
},
|
||||||
dangerouslyGetParent() {
|
dangerouslyGetParent() {
|
||||||
|
|||||||
@@ -3,6 +3,50 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.14.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.2...@react-navigation/core@5.14.3) (2020-11-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* improve the error message for incorrect screen configuration ([8f764d8](https://github.com/react-navigation/react-navigation/commit/8f764d8b0809604716d5d92ea33cc1beee02e804))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.14.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.1...@react-navigation/core@5.14.2) (2020-11-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* throw if the same pattern resolves to multiple screens ([48b2e77](https://github.com/react-navigation/react-navigation/commit/48b2e777307908e8b3fcb49d8555b610dc0e38f2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.14.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.0...@react-navigation/core@5.14.1) (2020-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* tweak error message when navigator has non-screen children ([360b0e9](https://github.com/react-navigation/react-navigation/commit/360b0e995835990c55b75898757ebdd120d52446))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.14.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.5...@react-navigation/core@5.14.0) (2020-11-04)
|
# [5.14.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.5...@react-navigation/core@5.14.0) (2020-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/core",
|
"name": "@react-navigation/core",
|
||||||
"description": "Core utilities for building navigators",
|
"description": "Core utilities for building navigators",
|
||||||
"version": "5.14.0",
|
"version": "5.14.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^5.6.0",
|
"@react-navigation/routers": "^5.6.2",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"nanoid": "^3.1.15",
|
"nanoid": "^3.1.15",
|
||||||
"query-string": "^6.13.6",
|
"query-string": "^6.13.6",
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import {
|
|||||||
NavigationAction,
|
NavigationAction,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
|
import UnhandledActionContext from './UnhandledActionContext';
|
||||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||||
import NavigationStateContext from './NavigationStateContext';
|
import NavigationStateContext from './NavigationStateContext';
|
||||||
import UnhandledActionContext from './UnhandledActionContext';
|
import NavigationRouteContext from './NavigationRouteContext';
|
||||||
|
import NavigationContext from './NavigationContext';
|
||||||
import { ScheduleUpdateContext } from './useScheduleUpdate';
|
import { ScheduleUpdateContext } from './useScheduleUpdate';
|
||||||
import useChildListeners from './useChildListeners';
|
import useChildListeners from './useChildListeners';
|
||||||
import useKeyedChildListeners from './useKeyedChildListeners';
|
import useKeyedChildListeners from './useKeyedChildListeners';
|
||||||
@@ -160,9 +162,20 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
|
|
||||||
const resetRoot = React.useCallback(
|
const resetRoot = React.useCallback(
|
||||||
(state?: PartialState<NavigationState> | NavigationState) => {
|
(state?: PartialState<NavigationState> | NavigationState) => {
|
||||||
setState(state);
|
const target = state?.key ?? keyedListeners.getState.root?.().key;
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
throw new Error(NOT_INITIALIZED_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.focus[0]((navigation) =>
|
||||||
|
navigation.dispatch({
|
||||||
|
...CommonActions.reset(state),
|
||||||
|
target,
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[setState]
|
[keyedListeners.getState, listeners.focus]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRootState = React.useCallback(() => {
|
const getRootState = React.useCallback(() => {
|
||||||
@@ -386,7 +399,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
let element = (
|
||||||
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
||||||
<NavigationBuilderContext.Provider value={builderContext}>
|
<NavigationBuilderContext.Provider value={builderContext}>
|
||||||
<NavigationStateContext.Provider value={context}>
|
<NavigationStateContext.Provider value={context}>
|
||||||
@@ -399,6 +412,19 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
</NavigationBuilderContext.Provider>
|
</NavigationBuilderContext.Provider>
|
||||||
</ScheduleUpdateContext.Provider>
|
</ScheduleUpdateContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (independent) {
|
||||||
|
// We need to clear any existing contexts for nested independent container to work correctly
|
||||||
|
element = (
|
||||||
|
<NavigationRouteContext.Provider value={undefined}>
|
||||||
|
<NavigationContext.Provider value={undefined}>
|
||||||
|
{element}
|
||||||
|
</NavigationContext.Provider>
|
||||||
|
</NavigationRouteContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -757,3 +757,67 @@ it('invokes the unhandled action listener with the unhandled action', () => {
|
|||||||
type: 'NAVIGATE',
|
type: 'NAVIGATE',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works with state change events in independent nested container', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ref = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">
|
||||||
|
{() => (
|
||||||
|
<BaseNavigationContainer
|
||||||
|
independent
|
||||||
|
ref={ref}
|
||||||
|
onStateChange={onStateChange}
|
||||||
|
>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="qux">{() => null}</Screen>
|
||||||
|
<Screen name="lex">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="bar">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => ref.current?.navigate('lex'));
|
||||||
|
|
||||||
|
expect(onStateChange).toBeCalledWith({
|
||||||
|
index: 1,
|
||||||
|
key: '15',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'qux', name: 'qux' },
|
||||||
|
{ key: 'lex', name: 'lex' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ref.current?.getRootState()).toEqual({
|
||||||
|
index: 1,
|
||||||
|
key: '15',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'qux', name: 'qux' },
|
||||||
|
{ key: 'lex', name: 'lex' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -2673,6 +2673,47 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws if two screens map to the same pattern', () => {
|
||||||
|
const path = '/bar/42/baz/test';
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
getStateFromPath(path, {
|
||||||
|
screens: {
|
||||||
|
Foo: {
|
||||||
|
screens: {
|
||||||
|
Bar: {
|
||||||
|
path: '/bar/:id/',
|
||||||
|
screens: {
|
||||||
|
Baz: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bax: '/bar/:id/baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toThrow(
|
||||||
|
"Found conflicting screens with the same pattern. The pattern 'bar/:id/baz' resolves to both 'Foo > Bax' and 'Foo > Bar > Baz'. Patterns must be unique and cannot resolve to more than one screen."
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
getStateFromPath(path, {
|
||||||
|
screens: {
|
||||||
|
Foo: {
|
||||||
|
screens: {
|
||||||
|
Bar: {
|
||||||
|
path: '/bar/:id/',
|
||||||
|
screens: {
|
||||||
|
Baz: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('throws if wildcard is specified with legacy config', () => {
|
it('throws if wildcard is specified with legacy config', () => {
|
||||||
const path = '/bar/42/baz/test';
|
const path = '/bar/42/baz/test';
|
||||||
const config = {
|
const config = {
|
||||||
|
|||||||
@@ -1462,6 +1462,51 @@ it('throws when Screen is not the direct children', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws when undefined component is a direct children', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Undefined = undefined;
|
||||||
|
|
||||||
|
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Undefined name="foo" component={jest.fn()} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
spy.mockRestore();
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
"A navigator can only contain 'Screen' components as its direct children (found 'undefined' for the screen 'foo')"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when a tag is a direct children', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<screen name="foo" component={jest.fn()} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
"A navigator can only contain 'Screen' components as its direct children (found 'screen' for the screen 'foo')"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('throws when a React Element is not the direct children', () => {
|
it('throws when a React Element is not the direct children', () => {
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
useNavigationBuilder(MockRouter, props);
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
|||||||
@@ -1178,3 +1178,149 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
|
|||||||
type: 'stack',
|
type: 'stack',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prevents removing a child screen with 'beforeRemove' event with 'resetRoot'", () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(StackRouter, props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{state.routes.map((route) => descriptors[route.key].render())}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBeforeRemove = jest.fn();
|
||||||
|
|
||||||
|
let shouldPrevent = true;
|
||||||
|
let shouldContinue = false;
|
||||||
|
|
||||||
|
const TestScreen = (props: any) => {
|
||||||
|
React.useEffect(
|
||||||
|
() =>
|
||||||
|
props.navigation.addListener('beforeRemove', (e: any) => {
|
||||||
|
onBeforeRemove();
|
||||||
|
|
||||||
|
if (shouldPrevent) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (shouldContinue) {
|
||||||
|
props.navigation.dispatch(e.data.action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[props.navigation]
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
|
const ref = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">{() => null}</Screen>
|
||||||
|
<Screen name="bar">{() => null}</Screen>
|
||||||
|
<Screen name="baz">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="qux" component={TestScreen} />
|
||||||
|
<Screen name="lex">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
act(() => ref.current?.navigate('baz'));
|
||||||
|
|
||||||
|
expect(onStateChange).toBeCalledTimes(1);
|
||||||
|
expect(onStateChange).toBeCalledWith({
|
||||||
|
index: 1,
|
||||||
|
key: 'stack-2',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-3', name: 'foo' },
|
||||||
|
{
|
||||||
|
key: 'baz-4',
|
||||||
|
name: 'baz',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: 'stack-6',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [{ key: 'qux-7', name: 'qux' }],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() =>
|
||||||
|
ref.current?.resetRoot({
|
||||||
|
index: 0,
|
||||||
|
key: 'stack-2',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [{ key: 'foo-3', name: 'foo' }],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onStateChange).toBeCalledTimes(1);
|
||||||
|
expect(onBeforeRemove).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
expect(ref.current?.getRootState()).toEqual({
|
||||||
|
index: 1,
|
||||||
|
key: 'stack-2',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-3', name: 'foo' },
|
||||||
|
{
|
||||||
|
key: 'baz-4',
|
||||||
|
name: 'baz',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: 'stack-6',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [{ key: 'qux-7', name: 'qux' }],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
});
|
||||||
|
|
||||||
|
shouldPrevent = false;
|
||||||
|
|
||||||
|
act(() =>
|
||||||
|
ref.current?.resetRoot({
|
||||||
|
index: 0,
|
||||||
|
key: 'stack-2',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [{ key: 'foo-3', name: 'foo' }],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onStateChange).toBeCalledTimes(2);
|
||||||
|
expect(onStateChange).toBeCalledWith({
|
||||||
|
index: 0,
|
||||||
|
key: 'stack-2',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [{ key: 'foo-3', name: 'foo' }],
|
||||||
|
stale: false,
|
||||||
|
type: 'stack',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -239,6 +239,10 @@ export default function getPathFromState(
|
|||||||
// Object.fromEntries is not available in older iOS versions
|
// Object.fromEntries is not available in older iOS versions
|
||||||
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
||||||
entries.reduce((acc, [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;
|
acc[k] = v;
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<K, V>);
|
}, {} as Record<K, V>);
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ type ResultState = PartialState<NavigationState> & {
|
|||||||
state?: ResultState;
|
state?: ResultState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ParsedRoute = {
|
||||||
|
name: string;
|
||||||
|
params?: Record<string, any> | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to parse a path string to initial state object accepted by the container.
|
* Utility to parse a path string to initial state object accepted by the container.
|
||||||
* This is useful for deep linking when we need to handle the incoming URL.
|
* This is useful for deep linking when we need to handle the incoming URL.
|
||||||
@@ -119,6 +124,12 @@ export default function getStateFromPath(
|
|||||||
// - the most exhaustive ones are always at the beginning
|
// - the most exhaustive ones are always at the beginning
|
||||||
// - patterns with wildcard are always at the end
|
// - patterns with wildcard are always at the end
|
||||||
|
|
||||||
|
// If 2 patterns are same, move the one with less route names up
|
||||||
|
// This is an error state, so it's only useful for consistent error messages
|
||||||
|
if (a.pattern === b.pattern) {
|
||||||
|
return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
|
||||||
|
}
|
||||||
|
|
||||||
// If one of the patterns starts with the other, it's more exhaustive
|
// If one of the patterns starts with the other, it's more exhaustive
|
||||||
// So move it up
|
// So move it up
|
||||||
if (a.pattern.startsWith(b.pattern)) {
|
if (a.pattern.startsWith(b.pattern)) {
|
||||||
@@ -155,6 +166,35 @@ export default function getStateFromPath(
|
|||||||
return bWildcardIndex - aWildcardIndex;
|
return bWildcardIndex - aWildcardIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check for duplicate patterns in the config
|
||||||
|
configs.reduce<Record<string, RouteConfig>>((acc, config) => {
|
||||||
|
if (acc[config.pattern]) {
|
||||||
|
const a = acc[config.pattern].routeNames;
|
||||||
|
const b = config.routeNames;
|
||||||
|
|
||||||
|
// It's not a problem if the path string omitted from a inner most screen
|
||||||
|
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
||||||
|
const intersects =
|
||||||
|
a.length > b.length
|
||||||
|
? b.every((it, i) => a[i] === it)
|
||||||
|
: a.every((it, i) => b[i] === it);
|
||||||
|
|
||||||
|
if (!intersects) {
|
||||||
|
throw new Error(
|
||||||
|
`Found conflicting screens with the same pattern. The pattern '${
|
||||||
|
config.pattern
|
||||||
|
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
||||||
|
' > '
|
||||||
|
)}'. Patterns must be unique and cannot resolve to more than one screen.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(acc, {
|
||||||
|
[config.pattern]: config,
|
||||||
|
});
|
||||||
|
}, {});
|
||||||
|
|
||||||
if (remaining === '/') {
|
if (remaining === '/') {
|
||||||
// We need to add special handling of empty path so navigation to empty path also works
|
// We need to add special handling of empty path so navigation to empty path also works
|
||||||
// When handling empty path, we should only look at the root level config
|
// When handling empty path, we should only look at the root level config
|
||||||
@@ -189,7 +229,7 @@ export default function getStateFromPath(
|
|||||||
if (legacy === false) {
|
if (legacy === false) {
|
||||||
// If we're not in legacy mode,, we match the whole path against the regex instead of segments
|
// 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
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
||||||
const { routeNames, allParams, remainingPath } = matchAgainstConfigs(
|
const { routes, remainingPath } = matchAgainstConfigs(
|
||||||
remaining,
|
remaining,
|
||||||
configs.map((c) => ({
|
configs.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
@@ -198,39 +238,30 @@ export default function getStateFromPath(
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (routeNames !== undefined) {
|
if (routes !== undefined) {
|
||||||
// This will always be empty if full path matched
|
// This will always be empty if full path matched
|
||||||
|
current = createNestedStateObject(routes, initialRoutes);
|
||||||
remaining = remainingPath;
|
remaining = remainingPath;
|
||||||
current = createNestedStateObject(
|
|
||||||
createRouteObjects(configs, routeNames, allParams),
|
|
||||||
initialRoutes
|
|
||||||
);
|
|
||||||
result = current;
|
result = current;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In legacy mode, we divide the path into segments and match piece by piece
|
// 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
|
// This preserves the legacy behaviour, but we should remove it in next major
|
||||||
while (remaining) {
|
while (remaining) {
|
||||||
let { routeNames, allParams, remainingPath } = matchAgainstConfigs(
|
let { routes, remainingPath } = matchAgainstConfigs(remaining, configs);
|
||||||
remaining,
|
|
||||||
configs
|
|
||||||
);
|
|
||||||
|
|
||||||
remaining = remainingPath;
|
remaining = remainingPath;
|
||||||
|
|
||||||
// If we hadn't matched any segments earlier, use the path as route name
|
// If we hadn't matched any segments earlier, use the path as route name
|
||||||
if (routeNames === undefined) {
|
if (routes === undefined) {
|
||||||
const segments = remaining.split('/');
|
const segments = remaining.split('/');
|
||||||
|
|
||||||
routeNames = [decodeURIComponent(segments[0])];
|
routes = [{ name: decodeURIComponent(segments[0]) }];
|
||||||
segments.shift();
|
segments.shift();
|
||||||
remaining = segments.join('/');
|
remaining = segments.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = createNestedStateObject(
|
const state = createNestedStateObject(routes, initialRoutes);
|
||||||
createRouteObjects(configs, routeNames, allParams),
|
|
||||||
initialRoutes
|
|
||||||
);
|
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
// The state should be nested inside the deepest route we parsed before
|
// The state should be nested inside the deepest route we parsed before
|
||||||
@@ -274,8 +305,7 @@ const joinPaths = (...paths: string[]): string =>
|
|||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
||||||
let routeNames: string[] | undefined;
|
let routes: ParsedRoute[] | undefined;
|
||||||
let allParams: Record<string, any> | undefined;
|
|
||||||
let remainingPath = remaining;
|
let remainingPath = remaining;
|
||||||
|
|
||||||
// Go through all configs, and see if the next path segment matches our regex
|
// Go through all configs, and see if the next path segment matches our regex
|
||||||
@@ -288,21 +318,40 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
|||||||
|
|
||||||
// If our regex matches, we need to extract params from the path
|
// If our regex matches, we need to extract params from the path
|
||||||
if (match) {
|
if (match) {
|
||||||
routeNames = [...config.routeNames];
|
const matchedParams = config.pattern
|
||||||
|
?.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'))
|
||||||
|
.reduce<Record<string, any>>(
|
||||||
|
(acc, p, i) =>
|
||||||
|
Object.assign(acc, {
|
||||||
|
// The param segments appear every second item starting from 2 in the regex match result
|
||||||
|
[p]: match![(i + 1) * 2].replace(/\//, ''),
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
const paramPatterns = config.pattern
|
routes = config.routeNames.map((name) => {
|
||||||
.split('/')
|
const config = configs.find((c) => c.screen === name);
|
||||||
.filter((p) => p.startsWith(':'));
|
const params = config?.path
|
||||||
|
?.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'))
|
||||||
|
.reduce<Record<string, any>>((acc, p) => {
|
||||||
|
const value = matchedParams[p];
|
||||||
|
|
||||||
if (paramPatterns.length) {
|
if (value) {
|
||||||
allParams = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
const key = p.replace(/^:/, '').replace(/\?$/, '');
|
||||||
const value = match![(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
|
acc[key] = config.parse?.[key] ? config.parse[key](value) : value;
|
||||||
|
}
|
||||||
|
|
||||||
acc[p] = value;
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
return acc;
|
if (params && Object.keys(params).length) {
|
||||||
}, {});
|
return { name, params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { name };
|
||||||
|
});
|
||||||
|
|
||||||
remainingPath = remainingPath.replace(match[1], '');
|
remainingPath = remainingPath.replace(match[1], '');
|
||||||
|
|
||||||
@@ -310,7 +359,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { routeNames, allParams, remainingPath };
|
return { routes, remainingPath };
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNormalizedConfigs = (
|
const createNormalizedConfigs = (
|
||||||
@@ -473,57 +522,48 @@ const findInitialRoute = (
|
|||||||
// it is the end of state and if there is initialRoute for this level
|
// it is the end of state and if there is initialRoute for this level
|
||||||
const createStateObject = (
|
const createStateObject = (
|
||||||
initialRoute: string | undefined,
|
initialRoute: string | undefined,
|
||||||
routeName: string,
|
route: ParsedRoute,
|
||||||
params: Record<string, any> | undefined,
|
|
||||||
isEmpty: boolean
|
isEmpty: boolean
|
||||||
): InitialState => {
|
): InitialState => {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
if (initialRoute) {
|
if (initialRoute) {
|
||||||
return {
|
return {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [{ name: initialRoute }, { name: routeName as string, params }],
|
routes: [{ name: initialRoute }, route],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
routes: [{ name: routeName as string, params }],
|
routes: [route],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (initialRoute) {
|
if (initialRoute) {
|
||||||
return {
|
return {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [{ name: initialRoute }, { ...route, state: { routes: [] } }],
|
||||||
{ name: initialRoute },
|
|
||||||
{ name: routeName as string, params, state: { routes: [] } },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
routes: [{ name: routeName as string, params, state: { routes: [] } }],
|
routes: [{ ...route, state: { routes: [] } }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNestedStateObject = (
|
const createNestedStateObject = (
|
||||||
routes: { name: string; params?: object }[],
|
routes: ParsedRoute[],
|
||||||
initialRoutes: InitialRouteConfig[]
|
initialRoutes: InitialRouteConfig[]
|
||||||
) => {
|
) => {
|
||||||
let state: InitialState;
|
let state: InitialState;
|
||||||
let route = routes.shift() as { name: string; params?: object };
|
let route = routes.shift() as ParsedRoute;
|
||||||
let initialRoute = findInitialRoute(route.name, initialRoutes);
|
let initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
|
|
||||||
state = createStateObject(
|
state = createStateObject(initialRoute, route, routes.length === 0);
|
||||||
initialRoute,
|
|
||||||
route.name,
|
|
||||||
route.params,
|
|
||||||
routes.length === 0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (routes.length > 0) {
|
if (routes.length > 0) {
|
||||||
let nestedState = state;
|
let nestedState = state;
|
||||||
|
|
||||||
while ((route = routes.shift() as { name: string; params?: object })) {
|
while ((route = routes.shift() as ParsedRoute)) {
|
||||||
initialRoute = findInitialRoute(route.name, initialRoutes);
|
initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
|
|
||||||
const nestedStateIndex =
|
const nestedStateIndex =
|
||||||
@@ -531,8 +571,7 @@ const createNestedStateObject = (
|
|||||||
|
|
||||||
nestedState.routes[nestedStateIndex].state = createStateObject(
|
nestedState.routes[nestedStateIndex].state = createStateObject(
|
||||||
initialRoute,
|
initialRoute,
|
||||||
route.name,
|
route,
|
||||||
route.params,
|
|
||||||
routes.length === 0
|
routes.length === 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -546,46 +585,6 @@ const createNestedStateObject = (
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRouteObjects = (
|
|
||||||
configs: RouteConfig[],
|
|
||||||
routeNames: string[],
|
|
||||||
allParams?: Record<string, any>
|
|
||||||
) =>
|
|
||||||
routeNames.map((name) => {
|
|
||||||
const config = configs.find((c) => c.screen === name);
|
|
||||||
|
|
||||||
let params: object | undefined;
|
|
||||||
|
|
||||||
if (allParams && config?.path) {
|
|
||||||
const pattern = config.path;
|
|
||||||
|
|
||||||
if (pattern) {
|
|
||||||
const paramPatterns = pattern
|
|
||||||
.split('/')
|
|
||||||
.filter((p) => p.startsWith(':'));
|
|
||||||
|
|
||||||
if (paramPatterns.length) {
|
|
||||||
params = paramPatterns.reduce<Record<string, any>>((acc, p) => {
|
|
||||||
const key = p.replace(/^:/, '').replace(/\?$/, '');
|
|
||||||
const value = allParams![p];
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
acc[key] = config.parse?.[key] ? config.parse[key](value) : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params && Object.keys(params).length) {
|
|
||||||
return { name, params };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name };
|
|
||||||
});
|
|
||||||
|
|
||||||
const findFocusedRoute = (state: InitialState) => {
|
const findFocusedRoute = (state: InitialState) => {
|
||||||
let current: InitialState | undefined = state;
|
let current: InitialState | undefined = state;
|
||||||
|
|
||||||
|
|||||||
@@ -90,10 +90,17 @@ const getRouteConfigsFromChildren = <
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`A navigator can only contain 'Screen' components as its direct children (found '${
|
`A navigator can only contain 'Screen' components as its direct children (found ${
|
||||||
// @ts-expect-error: child can be any type and we're accessing it safely, but TS doesn't understand it
|
React.isValidElement(child)
|
||||||
child.type?.name ? child.type.name : String(child)
|
? `'${
|
||||||
}')`
|
typeof child.type === 'string' ? child.type : child.type?.name
|
||||||
|
}'${
|
||||||
|
child.props?.name ? ` for the screen '${child.props.name}'` : ''
|
||||||
|
}`
|
||||||
|
: typeof child === 'object'
|
||||||
|
? JSON.stringify(child)
|
||||||
|
: `'${String(child)}'`
|
||||||
|
}). To render this component in the navigator, pass it in the 'component' prop to 'Screen'.`
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -90,18 +90,11 @@ export default function useOnAction({
|
|||||||
onDispatchAction(action, state === result);
|
onDispatchAction(action, state === result);
|
||||||
|
|
||||||
if (state !== result) {
|
if (state !== result) {
|
||||||
const nextRouteKeys = (result.routes as any[]).map(
|
|
||||||
(route: { key?: string }) => route.key
|
|
||||||
);
|
|
||||||
|
|
||||||
const removedRoutes = state.routes.filter(
|
|
||||||
(route) => !nextRouteKeys.includes(route.key)
|
|
||||||
);
|
|
||||||
|
|
||||||
const isPrevented = shouldPreventRemove(
|
const isPrevented = shouldPreventRemove(
|
||||||
emitter,
|
emitter,
|
||||||
beforeRemoveListeners,
|
beforeRemoveListeners,
|
||||||
removedRoutes,
|
state.routes,
|
||||||
|
result.routes,
|
||||||
action
|
action
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type {
|
import type {
|
||||||
NavigationState,
|
NavigationState,
|
||||||
Route,
|
|
||||||
NavigationAction,
|
NavigationAction,
|
||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import NavigationBuilderContext, {
|
import NavigationBuilderContext, {
|
||||||
@@ -22,11 +21,16 @@ const VISITED_ROUTE_KEYS = Symbol('VISITED_ROUTE_KEYS');
|
|||||||
export const shouldPreventRemove = (
|
export const shouldPreventRemove = (
|
||||||
emitter: NavigationEventEmitter<EventMapCore<any>>,
|
emitter: NavigationEventEmitter<EventMapCore<any>>,
|
||||||
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>,
|
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>,
|
||||||
routes: Route<string>[],
|
currentRoutes: { key: string }[],
|
||||||
|
nextRoutes: { key?: string | undefined }[],
|
||||||
action: NavigationAction
|
action: NavigationAction
|
||||||
) => {
|
) => {
|
||||||
|
const nextRouteKeys = nextRoutes.map((route) => route.key);
|
||||||
|
|
||||||
// Call these in reverse order so last screens handle the event first
|
// Call these in reverse order so last screens handle the event first
|
||||||
const reversedRoutes = [...routes].reverse();
|
const removedRoutes = currentRoutes
|
||||||
|
.filter((route) => !nextRouteKeys.includes(route.key))
|
||||||
|
.reverse();
|
||||||
|
|
||||||
const visitedRouteKeys: Set<string> =
|
const visitedRouteKeys: Set<string> =
|
||||||
// @ts-expect-error: add this property to mark that we've already emitted this action
|
// @ts-expect-error: add this property to mark that we've already emitted this action
|
||||||
@@ -37,7 +41,7 @@ export const shouldPreventRemove = (
|
|||||||
[VISITED_ROUTE_KEYS]: visitedRouteKeys,
|
[VISITED_ROUTE_KEYS]: visitedRouteKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const route of reversedRoutes) {
|
for (const route of removedRoutes) {
|
||||||
if (visitedRouteKeys.has(route.key)) {
|
if (visitedRouteKeys.has(route.key)) {
|
||||||
// Skip if we've already emitted this action for this screen
|
// Skip if we've already emitted this action for this screen
|
||||||
continue;
|
continue;
|
||||||
@@ -85,6 +89,7 @@ export default function useOnPreventRemove({
|
|||||||
emitter,
|
emitter,
|
||||||
beforeRemoveListeners,
|
beforeRemoveListeners,
|
||||||
state.routes,
|
state.routes,
|
||||||
|
[],
|
||||||
action
|
action
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.17](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.16...@react-navigation/devtools@5.1.17) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.16](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.15...@react-navigation/devtools@5.1.16) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.15](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.14...@react-navigation/devtools@5.1.15) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.14](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.13...@react-navigation/devtools@5.1.14) (2020-11-04)
|
## [5.1.14](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.13...@react-navigation/devtools@5.1.14) (2020-11-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/devtools
|
**Note:** Version bump only for package @react-navigation/devtools
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/devtools",
|
"name": "@react-navigation/devtools",
|
||||||
"description": "Developer tools for React Navigation",
|
"description": "Developer tools for React Navigation",
|
||||||
"version": "5.1.14",
|
"version": "5.1.18",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^5.14.0",
|
"@react-navigation/core": "^5.14.4",
|
||||||
"deep-equal": "^2.0.4"
|
"deep-equal": "^2.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,69 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* hide drawer's header by default ([794339e](https://github.com/react-navigation/react-navigation/commit/794339eeed7c0d3b0e8b1752e494fbb4608ddfad))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.11.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.1...@react-navigation/drawer@5.11.2) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.11.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.11.0...@react-navigation/drawer@5.11.1) (2020-11-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* provide correct context to drawe header ([18bbd17](https://github.com/react-navigation/react-navigation/commit/18bbd177d91ccc4308516208a8b9f1a34ca5cc41))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.7...@react-navigation/drawer@5.11.0) (2020-11-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* try fixing drawer blink on Android ([5217245](https://github.com/react-navigation/react-navigation/commit/52172453dfb71822c2fb0f5947d00bac4a840d07))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add a getIsDrawerOpenFromState utility to drawer ([5bd682f](https://github.com/react-navigation/react-navigation/commit/5bd682f0bf6b28a95fb3e7fc9e1974057a877cb0))
|
||||||
|
* add option to show a header in drawer navigator screens ([dbe961b](https://github.com/react-navigation/react-navigation/commit/dbe961ba5bb243e8da4d889c3c7dd6ed1de287c4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.10.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.6...@react-navigation/drawer@5.10.7) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.10.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.5...@react-navigation/drawer@5.10.6) (2020-11-04)
|
## [5.10.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.5...@react-navigation/drawer@5.10.6) (2020-11-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/drawer
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/drawer",
|
"name": "@react-navigation/drawer",
|
||||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||||
"version": "5.10.6",
|
"version": "5.11.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@testing-library/react-native": "^7.1.0",
|
"@testing-library/react-native": "^7.1.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "^0.63.30",
|
"@types/react-native": "^0.63.30",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export { default as DrawerContentScrollView } from './views/DrawerContentScrollV
|
|||||||
*/
|
*/
|
||||||
export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
|
export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
|
||||||
|
|
||||||
|
export { default as getIsDrawerOpenFromState } from './utils/getIsDrawerOpenFromState';
|
||||||
export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export type Scene = {
|
|||||||
color?: string;
|
color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Layout = { width: number; height: number };
|
||||||
|
|
||||||
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||||
/**
|
/**
|
||||||
* Position of the drawer on the screen. Defaults to `left`.
|
* Position of the drawer on the screen. Defaults to `left`.
|
||||||
@@ -94,12 +96,95 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
|||||||
detachInactiveScreens?: boolean;
|
detachInactiveScreens?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DrawerNavigationOptions = {
|
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 & {
|
||||||
/**
|
/**
|
||||||
* Title text for the screen.
|
* Title text for the screen.
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that given `HeaderProps` 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.
|
||||||
|
*/
|
||||||
|
headerShown?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Title string of a screen displayed in the drawer
|
* Title string of a screen displayed in the drawer
|
||||||
* or a function that given { focused: boolean, color: string } returns a React.Node
|
* or a function that given { focused: boolean, color: string } returns a React.Node
|
||||||
@@ -187,6 +272,20 @@ export type DrawerContentOptions = {
|
|||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DrawerHeaderProps = {
|
||||||
|
/**
|
||||||
|
* Layout of the screen.
|
||||||
|
*/
|
||||||
|
layout: Layout;
|
||||||
|
/**
|
||||||
|
* Object representing the current scene, such as the route object and descriptor.
|
||||||
|
*/
|
||||||
|
scene: {
|
||||||
|
route: Route<string>;
|
||||||
|
descriptor: DrawerDescriptor;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type DrawerNavigationEventMap = {
|
export type DrawerNavigationEventMap = {
|
||||||
/**
|
/**
|
||||||
* Event which fires when the drawer opens.
|
* Event which fires when the drawer opens.
|
||||||
|
|||||||
16
packages/drawer/src/utils/getIsDrawerOpenFromState.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type {
|
||||||
|
DrawerNavigationState,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/native';
|
||||||
|
|
||||||
|
export default function getIsDrawerOpenFromState(
|
||||||
|
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');
|
||||||
|
}
|
||||||
@@ -57,9 +57,10 @@ const DIRECTION_LEFT = 1;
|
|||||||
const DIRECTION_RIGHT = -1;
|
const DIRECTION_RIGHT = -1;
|
||||||
|
|
||||||
const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
|
const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
|
||||||
|
|
||||||
const SWIPE_DISTANCE_MINIMUM = 5;
|
const SWIPE_DISTANCE_MINIMUM = 5;
|
||||||
|
|
||||||
|
const DEFAULT_DRAWER_WIDTH = '80%';
|
||||||
|
|
||||||
const SPRING_CONFIG = {
|
const SPRING_CONFIG = {
|
||||||
stiffness: 1000,
|
stiffness: 1000,
|
||||||
damping: 500,
|
damping: 500,
|
||||||
@@ -202,7 +203,8 @@ export default class DrawerView extends React.Component<Props> {
|
|||||||
|
|
||||||
private getDrawerWidth = (): number => {
|
private getDrawerWidth = (): number => {
|
||||||
const { drawerStyle, dimensions } = this.props;
|
const { drawerStyle, dimensions } = this.props;
|
||||||
const { width } = StyleSheet.flatten(drawerStyle);
|
const { width = DEFAULT_DRAWER_WIDTH } =
|
||||||
|
StyleSheet.flatten(drawerStyle) || {};
|
||||||
|
|
||||||
if (typeof width === 'string' && width.endsWith('%')) {
|
if (typeof width === 'string' && width.endsWith('%')) {
|
||||||
// Try to calculate width if a percentage is given
|
// Try to calculate width if a percentage is given
|
||||||
@@ -246,7 +248,7 @@ export default class DrawerView extends React.Component<Props> {
|
|||||||
private containerWidth = new Value<number>(this.props.dimensions.width);
|
private containerWidth = new Value<number>(this.props.dimensions.width);
|
||||||
private drawerWidth = new Value<number>(this.initialDrawerWidth);
|
private drawerWidth = new Value<number>(this.initialDrawerWidth);
|
||||||
private drawerOpacity = new Value<number>(
|
private drawerOpacity = new Value<number>(
|
||||||
this.initialDrawerWidth || this.props.drawerType === 'permanent' ? 1 : 0
|
this.props.drawerType === 'permanent' ? 1 : 0
|
||||||
);
|
);
|
||||||
private drawerPosition = new Value<number>(
|
private drawerPosition = new Value<number>(
|
||||||
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
|
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
|
||||||
@@ -730,7 +732,7 @@ const styles = StyleSheet.create({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: '80%',
|
width: DEFAULT_DRAWER_WIDTH,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
import { ScreenContainer } from 'react-native-screens';
|
import { ScreenContainer } from 'react-native-screens';
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
NavigationHelpersContext,
|
||||||
|
NavigationContext,
|
||||||
|
NavigationRouteContext,
|
||||||
DrawerNavigationState,
|
DrawerNavigationState,
|
||||||
DrawerActions,
|
DrawerActions,
|
||||||
useTheme,
|
useTheme,
|
||||||
@@ -19,16 +21,19 @@ import {
|
|||||||
import { GestureHandlerRootView } from './GestureHandler';
|
import { GestureHandlerRootView } from './GestureHandler';
|
||||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
||||||
import ResourceSavingScene from './ResourceSavingScene';
|
import ResourceSavingScene from './ResourceSavingScene';
|
||||||
|
import Header from './Header';
|
||||||
import DrawerContent from './DrawerContent';
|
import DrawerContent from './DrawerContent';
|
||||||
import Drawer from './Drawer';
|
import Drawer from './Drawer';
|
||||||
import DrawerOpenContext from '../utils/DrawerOpenContext';
|
import DrawerOpenContext from '../utils/DrawerOpenContext';
|
||||||
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||||
|
import getIsDrawerOpenFromState from '../utils/getIsDrawerOpenFromState';
|
||||||
import type {
|
import type {
|
||||||
DrawerDescriptorMap,
|
DrawerDescriptorMap,
|
||||||
DrawerNavigationConfig,
|
DrawerNavigationConfig,
|
||||||
DrawerNavigationHelpers,
|
DrawerNavigationHelpers,
|
||||||
DrawerContentComponentProps,
|
DrawerContentComponentProps,
|
||||||
|
DrawerHeaderProps,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
type Props = DrawerNavigationConfig & {
|
type Props = DrawerNavigationConfig & {
|
||||||
@@ -90,7 +95,7 @@ export default function DrawerView({
|
|||||||
|
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
const isDrawerOpen = getIsDrawerOpenFromState(state);
|
||||||
|
|
||||||
const handleDrawerOpen = React.useCallback(() => {
|
const handleDrawerOpen = React.useCallback(() => {
|
||||||
navigation.dispatch({
|
navigation.dispatch({
|
||||||
@@ -169,6 +174,11 @@ export default function DrawerView({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
header = (props: DrawerHeaderProps) => <Header {...props} />,
|
||||||
|
headerShown = false,
|
||||||
|
} = descriptor.options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourceSavingScene
|
<ResourceSavingScene
|
||||||
key={route.key}
|
key={route.key}
|
||||||
@@ -176,6 +186,16 @@ export default function DrawerView({
|
|||||||
isVisible={isFocused}
|
isVisible={isFocused}
|
||||||
enabled={detachInactiveScreens}
|
enabled={detachInactiveScreens}
|
||||||
>
|
>
|
||||||
|
{headerShown ? (
|
||||||
|
<NavigationContext.Provider value={descriptor.navigation}>
|
||||||
|
<NavigationRouteContext.Provider value={route}>
|
||||||
|
{header({
|
||||||
|
layout: dimensions,
|
||||||
|
scene: { route, descriptor },
|
||||||
|
})}
|
||||||
|
</NavigationRouteContext.Provider>
|
||||||
|
</NavigationContext.Provider>
|
||||||
|
) : null}
|
||||||
{descriptor.render()}
|
{descriptor.render()}
|
||||||
</ResourceSavingScene>
|
</ResourceSavingScene>
|
||||||
);
|
);
|
||||||
|
|||||||
240
packages/drawer/src/views/Header.tsx
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Text, View, Image, StyleSheet, Platform } from 'react-native';
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
import { DrawerActions, useTheme } from '@react-navigation/native';
|
||||||
|
import TouchableItem from './TouchableItem';
|
||||||
|
import type { Layout, DrawerHeaderProps } from '../types';
|
||||||
|
|
||||||
|
export const getDefaultHeaderHeight = (
|
||||||
|
layout: Layout,
|
||||||
|
statusBarHeight: number
|
||||||
|
): number => {
|
||||||
|
const isLandscape = layout.width > layout.height;
|
||||||
|
|
||||||
|
let headerHeight;
|
||||||
|
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
if (isLandscape && !Platform.isPad) {
|
||||||
|
headerHeight = 32;
|
||||||
|
} else {
|
||||||
|
headerHeight = 44;
|
||||||
|
}
|
||||||
|
} else if (Platform.OS === 'android') {
|
||||||
|
headerHeight = 56;
|
||||||
|
} else {
|
||||||
|
headerHeight = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headerHeight + statusBarHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function HeaderSegment({ scene, layout }: DrawerHeaderProps) {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
headerTitle,
|
||||||
|
headerTitleAlign = Platform.select({
|
||||||
|
ios: 'center',
|
||||||
|
default: 'left',
|
||||||
|
}),
|
||||||
|
headerLeft,
|
||||||
|
headerLeftAccessibilityLabel,
|
||||||
|
headerRight,
|
||||||
|
headerTitleAllowFontScaling,
|
||||||
|
headerTitleStyle,
|
||||||
|
headerTintColor,
|
||||||
|
headerPressColorAndroid,
|
||||||
|
headerStyle,
|
||||||
|
headerStatusBarHeight = insets.top,
|
||||||
|
} = scene.descriptor.options;
|
||||||
|
|
||||||
|
const currentTitle =
|
||||||
|
typeof headerTitle !== 'function' && headerTitle !== undefined
|
||||||
|
? headerTitle
|
||||||
|
: title !== undefined
|
||||||
|
? title
|
||||||
|
: scene.route.name;
|
||||||
|
|
||||||
|
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||||
|
|
||||||
|
const leftButton = headerLeft ? (
|
||||||
|
headerLeft({ tintColor: headerTintColor })
|
||||||
|
) : (
|
||||||
|
<TouchableItem
|
||||||
|
accessible
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityComponentType="button"
|
||||||
|
accessibilityLabel={headerLeftAccessibilityLabel}
|
||||||
|
accessibilityTraits="button"
|
||||||
|
delayPressIn={0}
|
||||||
|
onPress={() =>
|
||||||
|
scene.descriptor.navigation.dispatch(DrawerActions.toggleDrawer())
|
||||||
|
}
|
||||||
|
style={styles.touchable}
|
||||||
|
pressColor={headerPressColorAndroid}
|
||||||
|
hitSlop={Platform.select({
|
||||||
|
ios: undefined,
|
||||||
|
default: { top: 16, right: 16, bottom: 16, left: 16 },
|
||||||
|
})}
|
||||||
|
borderless
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
style={[
|
||||||
|
styles.icon,
|
||||||
|
headerTintColor ? { tintColor: headerTintColor } : null,
|
||||||
|
]}
|
||||||
|
source={require('./assets/toggle-drawer-icon.png')}
|
||||||
|
fadeDuration={0}
|
||||||
|
/>
|
||||||
|
</TouchableItem>
|
||||||
|
);
|
||||||
|
const rightButton = headerRight
|
||||||
|
? headerRight({ tintColor: headerTintColor })
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
pointerEvents="box-none"
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
height: defaultHeight,
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
shadowColor: colors.border,
|
||||||
|
},
|
||||||
|
styles.container,
|
||||||
|
headerStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
|
||||||
|
<View pointerEvents="box-none" style={styles.content}>
|
||||||
|
{leftButton ? (
|
||||||
|
<View
|
||||||
|
pointerEvents="box-none"
|
||||||
|
style={[styles.left, { left: insets.left }]}
|
||||||
|
>
|
||||||
|
{leftButton}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
<View
|
||||||
|
pointerEvents="box-none"
|
||||||
|
style={[
|
||||||
|
headerTitleAlign === 'left'
|
||||||
|
? {
|
||||||
|
position: 'absolute',
|
||||||
|
left: (leftButton ? 72 : 16) + insets.left,
|
||||||
|
right: (rightButton ? 72 : 16) + insets.right,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
marginHorizontal:
|
||||||
|
(leftButton ? 32 : 16) +
|
||||||
|
Math.max(insets.left, insets.right),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{typeof headerTitle === 'function' ? (
|
||||||
|
headerTitle({
|
||||||
|
children: currentTitle,
|
||||||
|
allowFontScaling: headerTitleAllowFontScaling,
|
||||||
|
tintColor: headerTintColor,
|
||||||
|
style: headerTitleStyle,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
accessibilityRole="header"
|
||||||
|
aria-level="1"
|
||||||
|
numberOfLines={1}
|
||||||
|
allowFontScaling={headerTitleAllowFontScaling}
|
||||||
|
style={[
|
||||||
|
styles.title,
|
||||||
|
{ color: headerTintColor ?? colors.text },
|
||||||
|
styles.title,
|
||||||
|
headerTitleStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{currentTitle}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
{rightButton ? (
|
||||||
|
<View
|
||||||
|
pointerEvents="box-none"
|
||||||
|
style={[styles.right, { right: insets.right }]}
|
||||||
|
>
|
||||||
|
{rightButton}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Platform.select({
|
||||||
|
android: {
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
ios: {
|
||||||
|
shadowOpacity: 0.85,
|
||||||
|
shadowRadius: 0,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
title: Platform.select({
|
||||||
|
ios: {
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontFamily: 'sans-serif-medium',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
icon: {
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
margin: 3,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
},
|
||||||
|
touchable: {
|
||||||
|
marginHorizontal: 11,
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
});
|
||||||
BIN
packages/drawer/src/views/assets/toggle-drawer-icon.png
Normal file
|
After Width: | Height: | Size: 116 B |
|
After Width: | Height: | Size: 106 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@1.5x.ios.png
Normal file
|
After Width: | Height: | Size: 159 B |
|
After Width: | Height: | Size: 84 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@1x.ios.png
Normal file
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 100 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@2x.ios.png
Normal file
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 126 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@3x.ios.png
Normal file
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 116 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@4x.ios.png
Normal file
|
After Width: | Height: | Size: 219 B |
@@ -3,6 +3,38 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.3.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.9...@react-navigation/material-bottom-tabs@5.3.10) (2020-11-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.8...@react-navigation/material-bottom-tabs@5.3.9) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.7...@react-navigation/material-bottom-tabs@5.3.8) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.6...@react-navigation/material-bottom-tabs@5.3.7) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.5...@react-navigation/material-bottom-tabs@5.3.6) (2020-11-04)
|
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.5...@react-navigation/material-bottom-tabs@5.3.6) (2020-11-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-bottom-tabs",
|
"name": "@react-navigation/material-bottom-tabs",
|
||||||
"description": "Integration for bottom navigation component from react-native-paper",
|
"description": "Integration for bottom navigation component from react-native-paper",
|
||||||
"version": "5.3.6",
|
"version": "5.3.10",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@testing-library/react-native": "^7.1.0",
|
"@testing-library/react-native": "^7.1.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "^0.63.30",
|
"@types/react-native": "^0.63.30",
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.3.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.9...@react-navigation/material-top-tabs@5.3.10) (2020-11-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.8...@react-navigation/material-top-tabs@5.3.9) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.7...@react-navigation/material-top-tabs@5.3.8) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.6...@react-navigation/material-top-tabs@5.3.7) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.5...@react-navigation/material-top-tabs@5.3.6) (2020-11-04)
|
## [5.3.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.5...@react-navigation/material-top-tabs@5.3.6) (2020-11-04)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-top-tabs",
|
"name": "@react-navigation/material-top-tabs",
|
||||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||||
"version": "5.3.6",
|
"version": "5.3.10",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@testing-library/react-native": "^7.1.0",
|
"@testing-library/react-native": "^7.1.0",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-native": "^0.63.30",
|
"@types/react-native": "^0.63.30",
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.8.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.9...@react-navigation/native@5.8.10) (2020-11-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.8.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.8...@react-navigation/native@5.8.9) (2020-11-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.8.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.7...@react-navigation/native@5.8.8) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.8.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.6...@react-navigation/native@5.8.7) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.8.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.5...@react-navigation/native@5.8.6) (2020-11-04)
|
## [5.8.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.5...@react-navigation/native@5.8.6) (2020-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/native",
|
"name": "@react-navigation/native",
|
||||||
"description": "React Native integration for React Navigation",
|
"description": "React Native integration for React Navigation",
|
||||||
"version": "5.8.6",
|
"version": "5.8.10",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation",
|
"react-navigation",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^5.14.0",
|
"@react-navigation/core": "^5.14.4",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"nanoid": "^3.1.15"
|
"nanoid": "^3.1.15"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.6.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.6.1...@react-navigation/routers@5.6.2) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/routers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.6.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.6.0...@react-navigation/routers@5.6.1) (2020-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/routers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.6.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.5.1...@react-navigation/routers@5.6.0) (2020-11-04)
|
# [5.6.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.5.1...@react-navigation/routers@5.6.0) (2020-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/routers",
|
"name": "@react-navigation/routers",
|
||||||
"description": "Routers to help build custom navigators",
|
"description": "Routers to help build custom navigators",
|
||||||
"version": "5.6.0",
|
"version": "5.6.2",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type Action =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'RESET';
|
type: 'RESET';
|
||||||
payload: ResetState;
|
payload: ResetState | undefined;
|
||||||
source?: string;
|
source?: string;
|
||||||
target?: string;
|
target?: string;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export function navigate(...args: any): Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reset(state: ResetState): Action {
|
export function reset(state: ResetState | undefined): Action {
|
||||||
return { type: 'RESET', payload: state };
|
return { type: 'RESET', payload: state };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const isDrawerOpen = (
|
|||||||
state:
|
state:
|
||||||
| DrawerNavigationState<ParamListBase>
|
| DrawerNavigationState<ParamListBase>
|
||||||
| PartialState<DrawerNavigationState<ParamListBase>>
|
| PartialState<DrawerNavigationState<ParamListBase>>
|
||||||
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
|
) => Boolean(state.history?.some((it) => it.type === 'drawer'));
|
||||||
|
|
||||||
const openDrawer = (
|
const openDrawer = (
|
||||||
state: DrawerNavigationState<ParamListBase>
|
state: DrawerNavigationState<ParamListBase>
|
||||||
|
|||||||
@@ -56,12 +56,11 @@ export type PartialRoute<R extends Route<string>> = Omit<R, 'key'> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PartialState<State extends NavigationState> = Partial<
|
export type PartialState<State extends NavigationState> = Partial<
|
||||||
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
|
Omit<State, 'stale' | 'routes'>
|
||||||
> &
|
> &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
stale?: true;
|
stale?: true;
|
||||||
type?: string;
|
routes: PartialRoute<Route<State['routeNames'][number]>>[];
|
||||||
routes: PartialRoute<Route<string>>[];
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type Route<
|
export type Route<
|
||||||
|
|||||||
@@ -3,6 +3,44 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.12.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.6...@react-navigation/stack@5.12.7) (2020-11-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.12.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.5...@react-navigation/stack@5.12.6) (2020-11-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* make sure inactive screen don't increase scroll area on web ([da35085](https://github.com/react-navigation/react-navigation/commit/da35085f1e3440f26eea800c892c88aec64d072f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.12.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.4...@react-navigation/stack@5.12.5) (2020-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.12.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.3...@react-navigation/stack@5.12.4) (2020-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't hide child header automatically in stack ([8f0efc8](https://github.com/react-navigation/react-navigation/commit/8f0efc8db534297a95ea8a2bcb6d2e387c1fea53))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.12.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.2...@react-navigation/stack@5.12.3) (2020-11-04)
|
## [5.12.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.2...@react-navigation/stack@5.12.3) (2020-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/stack",
|
"name": "@react-navigation/stack",
|
||||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||||
"version": "5.12.3",
|
"version": "5.12.7",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.16.2",
|
"@react-native-community/bob": "^0.16.2",
|
||||||
"@react-native-community/masked-view": "^0.1.10",
|
"@react-native-community/masked-view": "^0.1.10",
|
||||||
"@react-navigation/native": "^5.8.6",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@testing-library/react-native": "^7.1.0",
|
"@testing-library/react-native": "^7.1.0",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
forNoAnimation,
|
forNoAnimation,
|
||||||
forSlideRight,
|
forSlideRight,
|
||||||
} from '../../TransitionConfigs/HeaderStyleInterpolators';
|
} from '../../TransitionConfigs/HeaderStyleInterpolators';
|
||||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
|
||||||
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
||||||
import type {
|
import type {
|
||||||
Layout,
|
Layout,
|
||||||
@@ -56,7 +55,6 @@ export default function HeaderContainer({
|
|||||||
style,
|
style,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const focusedRoute = getFocusedRoute();
|
const focusedRoute = getFocusedRoute();
|
||||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
|
||||||
const parentPreviousScene = React.useContext(PreviousSceneContext);
|
const parentPreviousScene = React.useContext(PreviousSceneContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -66,11 +64,8 @@ export default function HeaderContainer({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { header, headerShown = true, headerTransparent } =
|
||||||
header,
|
scene.descriptor.options || {};
|
||||||
headerShown = isParentHeaderShown === false,
|
|
||||||
headerTransparent,
|
|
||||||
} = scene.descriptor.options || {};
|
|
||||||
|
|
||||||
if (!headerShown) {
|
if (!headerShown) {
|
||||||
return null;
|
return null;
|
||||||
@@ -85,11 +80,10 @@ export default function HeaderContainer({
|
|||||||
const previousScene = self[i - 1];
|
const previousScene = self[i - 1];
|
||||||
const nextScene = self[i + 1];
|
const nextScene = self[i + 1];
|
||||||
|
|
||||||
const {
|
const { headerShown: previousHeaderShown = true } =
|
||||||
headerShown: previousHeaderShown = isParentHeaderShown === false,
|
previousScene?.descriptor.options || {};
|
||||||
} = previousScene?.descriptor.options || {};
|
|
||||||
|
|
||||||
const { headerShown: nextHeaderShown = isParentHeaderShown === false } =
|
const { headerShown: nextHeaderShown = true } =
|
||||||
nextScene?.descriptor.options || {};
|
nextScene?.descriptor.options || {};
|
||||||
|
|
||||||
const isHeaderStatic =
|
const isHeaderStatic =
|
||||||
|
|||||||
@@ -216,7 +216,14 @@ function CardContainer({
|
|||||||
pageOverflowEnabled={headerMode === 'screen' && mode === 'card'}
|
pageOverflowEnabled={headerMode === 'screen' && mode === 'card'}
|
||||||
containerStyle={hasAbsoluteHeader ? { marginTop: headerHeight } : null}
|
containerStyle={hasAbsoluteHeader ? { marginTop: headerHeight } : null}
|
||||||
contentStyle={[{ backgroundColor: colors.background }, cardStyle]}
|
contentStyle={[{ backgroundColor: colors.background }, cardStyle]}
|
||||||
style={StyleSheet.absoluteFill}
|
style={[
|
||||||
|
{
|
||||||
|
// This is necessary to avoid unfocused larger pages increasing scroll area
|
||||||
|
// The issue can be seen on the web when a smaller screen is pushed over a larger one
|
||||||
|
overflow: active ? undefined : 'hidden',
|
||||||
|
},
|
||||||
|
StyleSheet.absoluteFill,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.scene}>
|
<View style={styles.scene}>
|
||||||
|
|||||||
@@ -447,10 +447,7 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
? this.state.scenes.slice(-2).some((scene) => {
|
? this.state.scenes.slice(-2).some((scene) => {
|
||||||
const { descriptor } = scene;
|
const { descriptor } = scene;
|
||||||
const options = descriptor ? descriptor.options : {};
|
const options = descriptor ? descriptor.options : {};
|
||||||
const {
|
const { headerTransparent, headerShown = true } = options;
|
||||||
headerTransparent,
|
|
||||||
headerShown = isParentHeaderShown === false,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
if (headerTransparent || headerShown === false) {
|
if (headerTransparent || headerShown === false) {
|
||||||
return true;
|
return true;
|
||||||
@@ -542,7 +539,7 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
safeAreaInsets,
|
safeAreaInsets,
|
||||||
headerShown = isParentHeaderShown === false,
|
headerShown = true,
|
||||||
headerTransparent,
|
headerTransparent,
|
||||||
cardShadowEnabled,
|
cardShadowEnabled,
|
||||||
cardOverlayEnabled,
|
cardOverlayEnabled,
|
||||||
|
|||||||