Compare commits

...

24 Commits

Author SHA1 Message Date
Satyajit Sahoo
175c07a28c chore: publish
- @react-navigation/example@5.1.0
 - @react-navigation/bottom-tabs@5.4.7
 - @react-navigation/compat@5.1.23
 - @react-navigation/core@5.8.1
 - @react-navigation/drawer@5.7.7
 - @react-navigation/material-bottom-tabs@5.2.7
 - @react-navigation/material-top-tabs@5.2.7
 - @react-navigation/native@5.4.2
 - @react-navigation/routers@5.4.6
 - @react-navigation/stack@5.3.9
2020-05-20 13:27:29 +02:00
osdnk
2980627cbf chore: publish
- @react-navigation/bottom-tabs@5.4.6
 - @react-navigation/compat@5.1.22
 - @react-navigation/core@5.8.0
 - @react-navigation/drawer@5.7.6
 - @react-navigation/material-bottom-tabs@5.2.6
 - @react-navigation/material-top-tabs@5.2.6
 - @react-navigation/native@5.4.1
 - @react-navigation/routers@5.4.5
 - @react-navigation/stack@5.3.8
2020-05-20 10:29:05 +02:00
Michał Osadnik
d024ec6d74 feat: add getCurrentOptions (#8277) 2020-05-19 20:53:08 +02:00
Satyajit Sahoo
4d1b85c751 chore: limit height of material top tabs example 2020-05-19 19:27:05 +02:00
Satyajit Sahoo
4a818fdfb3 chore: tweak master-detail example 2020-05-19 18:56:51 +02:00
Satyajit Sahoo
0194de1061 chore: upgrade bob 2020-05-19 14:25:20 +02:00
Michał Osadnik
7b25c8eb2e feat: add getCurrentRoute (#8254) 2020-05-18 01:55:09 +02:00
Satyajit Sahoo
9304a8a16c chore: publish
- @react-navigation/bottom-tabs@5.4.5
 - @react-navigation/compat@5.1.21
 - @react-navigation/core@5.7.0
 - @react-navigation/drawer@5.7.5
 - @react-navigation/material-bottom-tabs@5.2.5
 - @react-navigation/material-top-tabs@5.2.5
 - @react-navigation/native@5.4.0
 - @react-navigation/stack@5.3.7
2020-05-17 01:20:24 +02:00
Satyajit Sahoo
51b40879bd fix: center icons in material tab bar. fixes #8248 2020-05-17 01:06:29 +02:00
Satyajit Sahoo
51f4d11fdf fix: don't use Object.fromEntries 2020-05-17 00:56:19 +02:00
Satyajit Sahoo
c2aa4bb2eb test: fix integration tests 2020-05-16 21:18:42 +02:00
Satyajit Sahoo
199a892a6d chore: fix the links in the example 2020-05-16 01:58:05 +02:00
Satyajit Sahoo
60cb3c9ba7 feat: add a PathConfig type 2020-05-15 18:57:28 +02:00
Satyajit Sahoo
6ccceaea8b chore: tweak linking config in the example 2020-05-15 18:48:03 +02:00
Satyajit Sahoo
d14f38b80a fix: fix types for linking options 2020-05-15 18:37:58 +02:00
Satyajit Sahoo
c481748f00 chore: publish
- @react-navigation/stack@5.3.6
2020-05-15 17:39:47 +02:00
Satyajit Sahoo
d45dbe97dc fix: reduce header title margin. fixes #8267 2020-05-15 17:39:33 +02:00
Janic Duplessis
7623945f6e chore: fix repo url in readme (#8235) 2020-05-14 19:10:08 +02:00
Satyajit Sahoo
1dddaff45c chore: publish
- @react-navigation/bottom-tabs@5.4.4
 - @react-navigation/compat@5.1.20
 - @react-navigation/core@5.6.1
 - @react-navigation/drawer@5.7.4
 - @react-navigation/material-bottom-tabs@5.2.4
 - @react-navigation/material-top-tabs@5.2.4
 - @react-navigation/native@5.3.2
 - @react-navigation/stack@5.3.5
2020-05-14 13:22:54 +02:00
Satyajit Sahoo
21b397f0d6 fix: don't use flat since it's not supported in node 2020-05-14 13:22:14 +02:00
Satyajit Sahoo
2ff0531695 chore: publish
- @react-navigation/bottom-tabs@5.4.3
 - @react-navigation/compat@5.1.19
 - @react-navigation/core@5.6.0
 - @react-navigation/drawer@5.7.3
 - @react-navigation/material-bottom-tabs@5.2.3
 - @react-navigation/material-top-tabs@5.2.3
 - @react-navigation/native@5.3.1
 - @react-navigation/stack@5.3.4
2020-05-14 12:45:50 +02:00
Satyajit Sahoo
0149e85a95 fix: ignore state updates when we're not mounted
closes #8226
2020-05-14 12:43:44 +02:00
Satyajit Sahoo
3c47716826 fix: ignore extra slashes in the pattern 2020-05-14 04:40:44 +02:00
Satyajit Sahoo
acc9646426 feat: merge path patterns for nested screens (#8253)
Currently, when we define path patterns in the linking config, they ignore the parent screen's path when parsing, for example, say we have this config:

{
  Home: {
    path: 'home',
    screens: {
      Profile: 'u/:id',
    },
  },
}
If we parse the URL /home, we'll get this state:

{
  routes: [{ name: 'Home' }],
}
If we parse the URL /home/u/cal, we'll get this state:

{
  routes: [
    {
      name: 'Home',
      state: {
        routes: [
          {
            name: 'Home',
            state: {
              routes: [
                {
                  name: 'Profile',
                  params: { id: 'cal' },
                },
              ],
            },
          },
        ],
      },
    },
  ],
}
Note how we have 2 Home screens nested inside each other. This is not something people usually expect and it seems to trip up a lot of people. Something like following will be more intuitive:

{
  routes: [
    {
      name: 'Home',
      state: {
        routes: [
          {
            name: 'Profile',
            params: { id: 'cal' },
          },
        ],
      },
    },
  ],
}
Essentially, we're treating patterns as relative to their parent rather than matching the exact segments. This PR changes the behavior of parsing links to treat configs like so. I hope it'll be easier to understand for people.

There is also a new option, exact, which brings back the olde behavior of matching the exact pattern:

{
  Home: {
    path: 'home',
    screens: {
      Profile: {
        path: 'u/:id',
        exact: true,
      },
    },
  },
}
Which will be useful if home is not in the URL, e.g. /u/cal.

This change only affects situations where both parent and child screen configuration have a pattern defined. If the parent didn't have a pattern defined, nothing changes from previous behavior:

{
  Home: {
    screens: {
      Profile: {
        path: 'u/:id',
      },
    },
  },
}
2020-05-14 00:39:32 +02:00
44 changed files with 1791 additions and 327 deletions

41
example/CHANGELOG.md Normal file
View File

@@ -0,0 +1,41 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.1.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/example@5.0.0-alpha.23...@react-navigation/example@5.1.0) (2020-05-20)
### Bug Fixes
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
* clamp interpolated styles ([67798af](https://github.com/react-navigation/react-navigation/commit/67798af869dcbbf323629fc7e7cc9062d1e12c29))
* disable screens when mode is modal on older expo versions ([94d7b28](https://github.com/react-navigation/react-navigation/commit/94d7b28c0b2ce0d56c99b224610f305be6451626))
* dispatch pop early when screen is closed with gesture ([#336](https://github.com/react-navigation/react-navigation/issues/336)) ([3d937d1](https://github.com/react-navigation/react-navigation/commit/3d937d1e6571cd613e830d64f7b2e7426076d371)), closes [#267](https://github.com/react-navigation/react-navigation/issues/267)
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/react-navigation/issues/238)) ([77b7570](https://github.com/react-navigation/react-navigation/commit/77b757091c0451e20bca01138629669c7da544a8))
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
* rtl in native app example ([50b366e](https://github.com/react-navigation/react-navigation/commit/50b366e7341f201d29a44f20b7771b3a832b0045))
* screens integration on Android ([#294](https://github.com/react-navigation/react-navigation/issues/294)) ([9bfb295](https://github.com/react-navigation/react-navigation/commit/9bfb29562020c61b4d5c9bee278bcb1c7bdb8b67))
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/issues/6785)
* update screens for native stack ([5411816](https://github.com/react-navigation/react-navigation/commit/54118161885738a6d20b062c7e6679f3bace8424))
* wrap navigators in gesture handler root ([41a5e1a](https://github.com/react-navigation/react-navigation/commit/41a5e1a385aa5180abc3992a4c67077c37b998b9))
### Features
* add `animationTypeForReplace` option ([#297](https://github.com/react-navigation/react-navigation/issues/297)) ([6262f72](https://github.com/react-navigation/react-navigation/commit/6262f7298bff843571fb4b1a677d3beabe29833e))
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/react-navigation/issues/308)) ([b931ae6](https://github.com/react-navigation/react-navigation/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/commit/942d2be2c72720469475ce12ec8df23825994dbf))
* add custom theme support ([#211](https://github.com/react-navigation/react-navigation/issues/211)) ([00fc616](https://github.com/react-navigation/react-navigation/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
* add deeplinking to native example ([#309](https://github.com/react-navigation/react-navigation/issues/309)) ([e55e866](https://github.com/react-navigation/react-navigation/commit/e55e866af2f2163ee89bc527997cda13ffeb2abe))
* add headerStatusBarHeight option to stack ([b201fd2](https://github.com/react-navigation/react-navigation/commit/b201fd20716a2f03cb9373c72281f5d396a9356d))
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
* add preventDefault functionality in material bottom tabs ([3dede31](https://github.com/react-navigation/react-navigation/commit/3dede316ccab3b2403a475f60ce20b5c4e4cc068))
* emit appear and dismiss events for native stack ([f1df4a0](https://github.com/react-navigation/react-navigation/commit/f1df4a080877b3642e748a41a5ffc2da8c449a8c))
* initialState should take priority over deep link ([039017b](https://github.com/react-navigation/react-navigation/commit/039017bc2af69120d2d10e8f2c8a62919c37eb65))
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/react-navigation/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))

View File

@@ -6,7 +6,7 @@ beforeEach(async () => {
it('loads the article page', async () => { it('loads the article page', async () => {
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf' '/link-component/article/gandalf'
); );
expect( expect(
((await page.accessibility.snapshot()) as any)?.children?.find( ((await page.accessibility.snapshot()) as any)?.children?.find(
@@ -16,24 +16,24 @@ it('loads the article page', async () => {
}); });
it('goes to the album page and goes back', async () => { it('goes to the album page and goes back', async () => {
await page.click('[href="/link-component/Album"]'); await page.click('[href="/link-component/music"]');
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Album' '/link-component/music'
); );
expect( expect(
((await page.accessibility.snapshot()) as any)?.children?.find( ((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading' (it: any) => it.role === 'heading'
)?.name )?.name
).toBe('Album'); ).toBe('Albums');
await page.click('[aria-label="Article by Gandalf, back"]'); await page.click('[aria-label="Article by Gandalf, back"]');
await page.waitForNavigation(); await page.waitForNavigation();
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf' '/link-component/article/gandalf'
); );
expect( expect(

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/example", "name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation", "description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0", "version": "5.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",

View File

@@ -17,7 +17,7 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -53,24 +53,24 @@ const ArticleScreen = ({
<ScrollView> <ScrollView>
<View style={styles.buttons}> <View style={styles.buttons}>
<Link <Link
to="/link-component/Album" to="/link-component/music"
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Go to /link-component/Album Go to /link-component/music
</Link> </Link>
<Link <Link
to="/link-component/Album" to="/link-component/music"
action={StackActions.replace('Album')} action={StackActions.replace('Albums')}
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Replace with /link-component/Album Replace with /link-component/music
</Link> </Link>
<LinkButton <LinkButton
to="/link-component/Album" to="/link-component/music"
mode="contained" mode="contained"
style={styles.button} style={styles.button}
> >
Go to /link-component/Album Go to /link-component/music
</LinkButton> </LinkButton>
<Button <Button
mode="outlined" mode="outlined"
@@ -97,17 +97,17 @@ const AlbumsScreen = ({
<ScrollView> <ScrollView>
<View style={styles.buttons}> <View style={styles.buttons}>
<Link <Link
to="/link-component/Article?author=Babel" to="/link-component/article/babel"
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Go to /link-component/Article Go to /link-component/article
</Link> </Link>
<LinkButton <LinkButton
to="/link-component/Article?author=Babel" to="/link-component/article/babel"
mode="contained" mode="contained"
style={styles.button} style={styles.button}
> >
Go to /link-component/Article Go to /link-component/article
</LinkButton> </LinkButton>
<Button <Button
mode="outlined" mode="outlined"
@@ -144,9 +144,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</SimpleStack.Navigator> </SimpleStack.Navigator>
); );

View File

@@ -1,12 +1,18 @@
import * as React from 'react'; import * as React from 'react';
import { Dimensions, ScaledSize } from 'react-native'; import { Dimensions, ScaledSize } from 'react-native';
import { Appbar } from 'react-native-paper'; import { Appbar } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/native'; import {
useTheme,
useNavigation,
ParamListBase,
} from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { import {
createDrawerNavigator, createDrawerNavigator,
DrawerNavigationProp, DrawerNavigationProp,
DrawerContent, DrawerContent,
DrawerContentComponentProps,
DrawerContentOptions,
} from '@react-navigation/drawer'; } from '@react-navigation/drawer';
import Article from '../Shared/Article'; import Article from '../Shared/Article';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
@@ -15,7 +21,7 @@ import NewsFeed from '../Shared/NewsFeed';
type DrawerParams = { type DrawerParams = {
Article: undefined; Article: undefined;
NewsFeed: undefined; NewsFeed: undefined;
Album: undefined; Albums: undefined;
}; };
type DrawerNavigation = DrawerNavigationProp<DrawerParams>; type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
@@ -43,10 +49,11 @@ const Header = ({
onGoBack: () => void; onGoBack: () => void;
title: string; title: string;
}) => { }) => {
const { colors } = useTheme();
const isLargeScreen = useIsLargeScreen(); const isLargeScreen = useIsLargeScreen();
return ( return (
<Appbar.Header> <Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />} {isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
<Appbar.Content title={title} /> <Appbar.Content title={title} />
</Appbar.Header> </Appbar.Header>
@@ -80,6 +87,23 @@ const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
); );
}; };
const CustomDrawerContent = (
props: DrawerContentComponentProps<DrawerContentOptions>
) => {
const { colors } = useTheme();
const navigation = useNavigation();
return (
<>
<Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
);
};
const Drawer = createDrawerNavigator<DrawerParams>(); const Drawer = createDrawerNavigator<DrawerParams>();
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & { type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
@@ -100,15 +124,7 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
drawerType={isLargeScreen ? 'permanent' : 'back'} drawerType={isLargeScreen ? 'permanent' : 'back'}
drawerStyle={isLargeScreen ? null : { width: '100%' }} drawerStyle={isLargeScreen ? null : { width: '100%' }}
overlayColor="transparent" overlayColor="transparent"
drawerContent={(props) => ( drawerContent={(props) => <CustomDrawerContent {...props} />}
<>
<Appbar.Header>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
)}
{...rest} {...rest}
> >
<Drawer.Screen name="Article" component={ArticleScreen} /> <Drawer.Screen name="Article" component={ArticleScreen} />
@@ -118,9 +134,9 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }} options={{ title: 'Feed' }}
/> />
<Drawer.Screen <Drawer.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</Drawer.Navigator> </Drawer.Navigator>
); );

View File

@@ -1,4 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { ParamListBase } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts'; import Contacts from '../Shared/Contacts';
@@ -12,7 +14,15 @@ type MaterialTopTabParams = {
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>(); const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
export default function MaterialTopTabsScreen() { type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
export default function MaterialTopTabsScreen({ navigation }: Props) {
navigation.setOptions({
cardStyle: { flex: 1 },
});
return ( return (
<MaterialTopTabs.Navigator> <MaterialTopTabs.Navigator>
<MaterialTopTabs.Screen <MaterialTopTabs.Screen

View File

@@ -12,7 +12,7 @@ import Albums from '../Shared/Albums';
type ModalStackParams = { type ModalStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type ModalStackNavigation = StackNavigationProp<ModalStackParams>; type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
@@ -31,7 +31,7 @@ const ArticleScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('Album')} onPress={() => navigation.push('Albums')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -112,9 +112,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<ModalPresentationStack.Screen <ModalPresentationStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</ModalPresentationStack.Navigator> </ModalPresentationStack.Navigator>
); );

View File

@@ -13,7 +13,7 @@ import NewsFeed from '../Shared/NewsFeed';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
NewsFeed: undefined; NewsFeed: undefined;
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -63,7 +63,7 @@ const NewsFeedScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.navigate('Album')} onPress={() => navigation.navigate('Albums')}
style={styles.button} style={styles.button}
> >
Navigate to album Navigate to album
@@ -136,9 +136,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }} options={{ title: 'Feed' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</SimpleStack.Navigator> </SimpleStack.Navigator>
); );

View File

@@ -15,7 +15,7 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -34,7 +34,7 @@ const ArticleScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('Album')} onPress={() => navigation.push('Albums')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -131,10 +131,10 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ options={{
title: 'Album', title: 'Albums',
headerBackTitle: 'Back', headerBackTitle: 'Back',
headerTransparent: true, headerTransparent: true,
headerBackground: () => ( headerBackground: () => (

View File

@@ -239,6 +239,22 @@ export default function App() {
{ Home: '' } { Home: '' }
), ),
}, },
Article: {
path: 'article/:author?',
parse: {
author: (author) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},
stringify: {
author: (author: string) =>
author.toLowerCase().replace(/\s/g, '-'),
},
},
Albums: 'music',
Chat: 'chat',
Contacts: 'people',
NewsFeed: 'feed',
}, },
}} }}
fallback={<Text>Loading</Text>} fallback={<Text>Loading</Text>}

View File

@@ -13,7 +13,7 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/satya164/react-navigation.git" "url": "git+https://github.com/react-navigation/react-navigation.git"
}, },
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)", "author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
"scripts": { "scripts": {

View File

@@ -3,6 +3,46 @@
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.4.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.6...@react-navigation/bottom-tabs@5.4.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.5...@react-navigation/bottom-tabs@5.4.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.4...@react-navigation/bottom-tabs@5.4.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.3...@react-navigation/bottom-tabs@5.4.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.2...@react-navigation/bottom-tabs@5.4.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10) ## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/bottom-tabs **Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -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.4.2", "version": "5.4.7",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -35,8 +35,8 @@
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",

View File

@@ -3,6 +3,46 @@
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.23](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.22...@react-navigation/compat@5.1.23) (2020-05-20)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.22](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.21...@react-navigation/compat@5.1.22) (2020-05-20)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.21](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.20...@react-navigation/compat@5.1.21) (2020-05-16)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10) ## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
**Note:** Version bump only for package @react-navigation/compat **Note:** Version bump only for package @react-navigation/compat

View File

@@ -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.1.18", "version": "5.1.23",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat", "repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": { "bugs": {
@@ -26,8 +26,8 @@
"clean": "del lib" "clean": "del lib"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"react": "~16.9.0", "react": "~16.9.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"

View File

@@ -3,6 +3,70 @@
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.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.0...@react-navigation/core@5.8.1) (2020-05-20)
**Note:** Version bump only for package @react-navigation/core
# [5.8.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.7.0...@react-navigation/core@5.8.0) (2020-05-20)
### Features
* add getCurrentOptions ([#8277](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8277)) ([d024ec6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d024ec6d74dffe481ce6fde732c729e20c1668f4))
* add getCurrentRoute ([#8254](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8254)) ([7b25c8e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/7b25c8eb2e6f96128fd86b92615346ce55bedeca))
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.1...@react-navigation/core@5.7.0) (2020-05-16)
### Bug Fixes
* don't use Object.fromEntries ([51f4d11](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/51f4d11fdf4bd2bb06f8cd4094f051816590e62c))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.0...@react-navigation/core@5.6.1) (2020-05-14)
### Bug Fixes
* don't use flat since it's not supported in node ([21b397f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/21b397f0d6b96ec4875d3172f47533130bb08009))
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.2...@react-navigation/core@5.6.0) (2020-05-14)
### Bug Fixes
* ignore extra slashes in the pattern ([3c47716](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3c47716826d0dfa69dfa6112141c116723372ea1))
* ignore state updates when we're not mounted ([0149e85](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0149e85a95b90c6a9d487fa753ddbf5d01c03e3d)), closes [#8226](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8226)
### Features
* merge path patterns for nested screens ([#8253](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8253)) ([acc9646](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/acc9646426fee53558d686dfbe5fd0e35361d8c0))
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08) ## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)

View File

@@ -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.5.2", "version": "5.8.1",
"keywords": [ "keywords": [
"react", "react",
"react-native", "react-native",
@@ -30,7 +30,7 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.4.4", "@react-navigation/routers": "^5.4.6",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.5", "nanoid": "^3.1.5",
"query-string": "^6.12.1", "query-string": "^6.12.1",
@@ -38,7 +38,7 @@
"use-subscription": "^1.4.0" "use-subscription": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-is": "^16.7.1", "@types/react-is": "^16.7.1",
"@types/use-subscription": "^1.0.0", "@types/use-subscription": "^1.0.0",

View File

@@ -13,6 +13,7 @@ import { ScheduleUpdateContext } from './useScheduleUpdate';
import useFocusedListeners from './useFocusedListeners'; import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools'; import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters'; import useStateGetters from './useStateGetters';
import useOptionsGetters from './useOptionsGetters';
import useEventEmitter from './useEventEmitter'; import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState'; import useSyncState from './useSyncState';
import isSerializable from './isSerializable'; import isSerializable from './isSerializable';
@@ -39,6 +40,10 @@ export const NavigationStateContext = React.createContext<{
setState: ( setState: (
state: NavigationState | PartialState<NavigationState> | undefined state: NavigationState | PartialState<NavigationState> | undefined
) => void; ) => void;
addOptionsGetter?: (
key: string,
getter: () => object | undefined | null
) => void;
}>({ }>({
isDefault: true, isDefault: true,
@@ -199,8 +204,21 @@ const BaseNavigationContainer = React.forwardRef(
return getStateForRoute('root'); return getStateForRoute('root');
}, [getStateForRoute]); }, [getStateForRoute]);
const getCurrentRoute = React.useCallback(() => {
let state = getRootState();
if (state === undefined) {
return undefined;
}
while (state.routes[state.index].state !== undefined) {
state = state.routes[state.index].state as NavigationState;
}
return state.routes[state.index];
}, [getRootState]);
const emitter = useEventEmitter(); const emitter = useEventEmitter();
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce< ...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any any
@@ -221,6 +239,8 @@ const BaseNavigationContainer = React.forwardRef(
getRootState, getRootState,
dangerouslyGetState: () => state, dangerouslyGetState: () => state,
dangerouslyGetParent: () => undefined, dangerouslyGetParent: () => undefined,
getCurrentRoute,
getCurrentOptions,
})); }));
const builderContext = React.useMemo( const builderContext = React.useMemo(
@@ -244,8 +264,9 @@ const BaseNavigationContainer = React.forwardRef(
setState, setState,
getKey, getKey,
setKey, setKey,
addOptionsGetter,
}), }),
[getKey, getState, setKey, setState, state] [getKey, getState, setKey, setState, state, addOptionsGetter]
); );
React.useEffect(() => { React.useEffect(() => {

View File

@@ -11,6 +11,7 @@ import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer'; import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator'; import EnsureSingleNavigator from './EnsureSingleNavigator';
import { NavigationProp, RouteConfig, EventMapBase } from './types'; import { NavigationProp, RouteConfig, EventMapBase } from './types';
import useOptionsGetters from './useOptionsGetters';
type Props< type Props<
State extends NavigationState, State extends NavigationState,
@@ -24,6 +25,7 @@ type Props<
}; };
getState: () => State; getState: () => State;
setState: (state: State) => void; setState: (state: State) => void;
options: object;
}; };
/** /**
@@ -40,11 +42,24 @@ export default function SceneView<
navigation, navigation,
getState, getState,
setState, setState,
options,
}: Props<State, ScreenOptions, EventMap>) { }: Props<State, ScreenOptions, EventMap>) {
const navigatorKeyRef = React.useRef<string | undefined>(); const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []); const getKey = React.useCallback(() => navigatorKeyRef.current, []);
const optionsRef = React.useRef<object | undefined>(options);
React.useEffect(() => {
optionsRef.current = options;
}, [options]);
const getOptions = React.useCallback(() => optionsRef.current, []);
const { addOptionsGetter } = useOptionsGetters({
key: route.key,
getOptions,
});
const setKey = React.useCallback((key: string) => { const setKey = React.useCallback((key: string) => {
navigatorKeyRef.current = key; navigatorKeyRef.current = key;
}, []); }, []);
@@ -77,8 +92,16 @@ export default function SceneView<
setState: setCurrentState, setState: setCurrentState,
getKey, getKey,
setKey, setKey,
addOptionsGetter,
}), }),
[getCurrentState, getKey, route.state, setCurrentState, setKey] [
getCurrentState,
getKey,
route.state,
setCurrentState,
setKey,
addOptionsGetter,
]
); );
return ( return (

View File

@@ -117,7 +117,8 @@ it("doesn't add query param for empty params", () => {
}); });
it('handles state with config with nested screens', () => { it('handles state with config with nested screens', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true'; const path =
'/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -182,8 +183,77 @@ it('handles state with config with nested screens', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles state with config with nested screens and exact', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
id: (id: number) => `x${id}`,
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles state with config with nested screens and unused configs', () => { it('handles state with config with nested screens and unused configs', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true'; const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -239,6 +309,66 @@ it('handles state with config with nested screens and unused configs', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles state with config with nested screens and unused configs with exact', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
},
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object with stringify in it', () => { it('handles nested object with stringify in it', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true'; const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = { const config = {
@@ -252,7 +382,6 @@ it('handles nested object with stringify in it', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -312,8 +441,82 @@ it('handles nested object with stringify in it', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles nested object with stringify in it with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth', () => { it('handles nested object for second route depth', () => {
const path = '/baz'; const path = '/foo/bar/baz';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -351,7 +554,95 @@ it('handles nested object for second route depth', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles nested object for second route depth and and path and stringify in roots', () => { it('handles nested object for second route depth with exact', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Bar: {
path: 'bar',
screens: {
Baz: {
path: 'baz',
exact: true,
},
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots', () => {
const path = '/foo/dathomir/bar/42/baz';
const config = {
Foo: {
path: 'foo/:planet',
stringify: {
id: (id: number) => `planet=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
params: { planet: 'dathomir' },
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz', params: { id: 42 } }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots with exact', () => {
const path = '/baz'; const path = '/baz';
const config = { const config = {
Foo: { Foo: {
@@ -370,7 +661,10 @@ it('handles nested object for second route depth and and path and stringify in r
id: Number, id: Number,
}, },
screens: { screens: {
Baz: 'baz', Baz: {
path: 'baz',
exact: true,
},
}, },
}, },
}, },
@@ -470,7 +764,7 @@ it('keeps query params if path is empty', () => {
}); });
it('cuts nested configs too', () => { it('cuts nested configs too', () => {
const path = '/baz'; const path = '/foo/baz';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -478,7 +772,48 @@ it('cuts nested configs too', () => {
Bar: '', Bar: '',
}, },
}, },
Baz: { path: 'baz' }, Baz: {
path: 'baz',
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('cuts nested configs too with exact', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Bar: {
path: '',
exact: true,
},
},
},
Baz: {
path: 'baz',
},
}; };
const state = { const state = {
@@ -504,7 +839,7 @@ it('cuts nested configs too', () => {
}); });
it('handles empty path at the end', () => { it('handles empty path at the end', () => {
const path = '/bar'; const path = '/foo/bar';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -641,7 +976,6 @@ it('strips undefined query params', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -681,7 +1015,79 @@ it('strips undefined query params', () => {
params: { params: {
author: 'Jane', author: 'Jane',
count: 10, count: 10,
answer: undefined, valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('strips undefined query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
valid: true, valid: true,
}, },
}, },
@@ -714,7 +1120,6 @@ it('handles stripping all query params', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -753,9 +1158,6 @@ it('handles stripping all query params', () => {
name: 'Bis', name: 'Bis',
params: { params: {
author: 'Jane', author: 'Jane',
count: undefined,
answer: undefined,
valid: undefined,
}, },
}, },
], ],
@@ -773,3 +1175,93 @@ it('handles stripping all query params', () => {
expect(getPathFromState(state, config)).toBe(path); expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles stripping all query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('replaces undefined query params', () => {
const path = '/bar/undefined/apple';
const config = {
Bar: 'bar/:type/:fruit',
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple' },
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});

View File

@@ -147,7 +147,10 @@ it('converts path string to initial state with config with nested screens', () =
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
@@ -213,7 +216,10 @@ it('converts path string to initial state with config with nested screens and un
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Baz: { Baz: {
@@ -268,16 +274,23 @@ it('handles nested object with unused configs and with parse in it', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz', path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -348,11 +361,18 @@ it('handles parse in nested object for second route depth', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
Bar: { Bar: {
path: 'bar', path: 'bar',
exact: true,
screens: { screens: {
Baz: 'baz', Baz: {
path: 'baz',
exact: true,
},
}, },
}, },
}, },
@@ -519,16 +539,23 @@ it('handles two initialRouteNames', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
initialRouteName: 'Bos', initialRouteName: 'Bos',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -601,16 +628,23 @@ it('accepts initialRouteName without config for it', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
initialRouteName: 'Bas', initialRouteName: 'Bas',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -1777,3 +1811,75 @@ it('handle optional params in the beginning v2', () => {
state state
); );
}); });
it('merges parent patterns if needed', () => {
const path = 'foo/42/baz/babel';
const config = {
Foo: {
path: 'foo/:bar',
parse: {
bar: Number,
},
screens: {
Baz: 'baz/:qux',
},
},
};
const state = {
routes: [
{
name: 'Foo',
params: { bar: 42 },
state: {
routes: [
{
name: 'Baz',
params: { qux: 'babel' },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('ignores extra slashes in the pattern', () => {
const path = '/bar/42';
const config = {
Foo: {
screens: {
Bar: {
path: '/bar//:id/',
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
params: { id: '42' },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});

View File

@@ -1490,3 +1490,128 @@ it("doesn't throw if children is null", () => {
expect(() => render(element).update(element)).not.toThrowError(); expect(() => render(element).update(element)).not.toThrowError();
}); });
it('returns currently focused route with getCurrentRoute', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentRoute()).toEqual({
key: 'bar-a',
name: 'bar-a',
});
});
it("returns currently focused route's options with getCurrentOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
});
});
it('does not throw if while getting current options with no options defined', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-b"
component={TestScreen}
options={{ wrongKey: true }}
/>
<Screen name="bar-a" component={TestScreen} />
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({});
});
it('does not throw if while getting current options with empty container', () => {
const navigation = React.createRef<NavigationContainerRef>();
// @ts-ignore
const container = <BaseNavigationContainer ref={navigation} />;
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
});

View File

@@ -4,19 +4,18 @@ import {
PartialState, PartialState,
Route, Route,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { PathConfig } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>; type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
type StringifyConfig = Record<string, (value: any) => string>; type StringifyConfig = Record<string, (value: any) => string>;
type Options = { type OptionsItem = PathConfig[string];
[routeName: string]:
| string type ConfigItem = {
| { pattern?: string;
path?: string; stringify?: StringifyConfig;
stringify?: StringifyConfig; screens?: Record<string, ConfigItem>;
screens?: Options;
};
}; };
/** /**
@@ -48,89 +47,83 @@ type Options = {
*/ */
export default function getPathFromState( export default function getPathFromState(
state?: State, state?: State,
options: Options = {} options: PathConfig = {}
): string { ): string {
if (state === undefined) { if (state === undefined) {
throw Error('NavigationState not passed'); throw Error('NavigationState not passed');
} }
let path = '/';
// Create a normalized configs array which will be easier to use
const configs = createNormalizedConfigs(options);
let path = '/';
let current: State | undefined = state; let current: State | undefined = state;
const allParams: Record<string, any> = {};
while (current) { while (current) {
let index = typeof current.index === 'number' ? current.index : 0; let index = typeof current.index === 'number' ? current.index : 0;
let route = current.routes[index] as Route<string> & { let route = current.routes[index] as Route<string> & {
state?: State; state?: State;
}; };
let currentOptions = options;
let pattern = route.name;
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
let nestedRouteNames = '';
while (route.name in currentOptions) { let pattern: string | undefined;
if (typeof currentOptions[route.name] === 'string') {
pattern = currentOptions[route.name] as string; let currentParams: Record<string, any> = { ...route.params };
break; let currentOptions = configs;
} else if (typeof currentOptions[route.name] === 'object') {
// if there is no `screens` property, we return pattern // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
if ( let nestedRouteNames = [];
!(currentOptions[route.name] as {
screens: Options; let hasNext = true;
}).screens
) { while (route.name in currentOptions && hasNext) {
pattern = (currentOptions[route.name] as { path: string }).path; pattern = currentOptions[route.name].pattern;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break; nestedRouteNames.push(route.name);
if (route.params) {
const stringify = currentOptions[route.name]?.stringify;
currentParams = fromEntries(
Object.entries(route.params).map(([key, value]) => [
key,
stringify?.[key] ? stringify[key](value) : String(value),
])
);
if (pattern) {
Object.assign(allParams, currentParams);
}
}
// If there is no `screens` property or no nested state, we return pattern
if (!currentOptions[route.name].screens || route.state === undefined) {
hasNext = false;
} else {
index =
typeof route.state.index === 'number'
? route.state.index
: route.state.routes.length - 1;
const nextRoute = route.state.routes[index];
const nestedConfig = currentOptions[route.name].screens;
// if there is config for next route name, we go deeper
if (nestedConfig && nextRoute.name in nestedConfig) {
route = nextRoute as Route<string> & { state?: State };
currentOptions = nestedConfig;
} else { } else {
// if it is the end of state, we return pattern // If not, there is no sense in going deeper in config
if (route.state === undefined) { hasNext = false;
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
} else {
index =
typeof route.state.index === 'number' ? route.state.index : 0;
const nextRoute = route.state.routes[index];
const deeperConfig = (currentOptions[route.name] as {
screens: Options;
}).screens;
// if there is config for next route name, we go deeper
if (nextRoute.name in deeperConfig) {
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
route = nextRoute as Route<string> & { state?: State };
currentOptions = deeperConfig;
} else {
// if not, there is no sense in going deeper in config
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
}
}
} }
} }
} }
if (pattern === undefined) { if (pattern === undefined) {
// cut the first `/` pattern = nestedRouteNames.join('/');
pattern = nestedRouteNames.substring(1);
} }
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
if (currentOptions[route.name] !== undefined) { if (currentOptions[route.name] !== undefined) {
path += pattern path += pattern
.split('/') .split('/')
@@ -138,16 +131,23 @@ export default function getPathFromState(
const name = p.replace(/^:/, '').replace(/\?$/, ''); const name = p.replace(/^:/, '').replace(/\?$/, '');
// If the path has a pattern for a param, put the param in the path // If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) { if (p.startsWith(':')) {
const value = params[name]; const value = allParams[name];
// Remove the used value from the params object since we'll use the rest for query string // Remove the used value from the params object since we'll use the rest for query string
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete if (currentParams) {
delete params[name]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete currentParams[name];
}
if (value === undefined && p.endsWith('?')) {
// Optional params without value assigned in route.params should be ignored
return '';
}
return encodeURIComponent(value); return encodeURIComponent(value);
} else if (p.endsWith('?')) {
// optional params without value assigned in route.params should be ignored
return '';
} }
return encodeURIComponent(p); return encodeURIComponent(p);
}) })
.join('/'); .join('/');
@@ -157,14 +157,15 @@ export default function getPathFromState(
if (route.state) { if (route.state) {
path += '/'; path += '/';
} else if (params) { } else if (currentParams) {
for (let param in params) { for (let param in currentParams) {
if (params[param] === 'undefined') { if (currentParams[param] === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[param]; delete currentParams[param];
} }
} }
const query = queryString.stringify(params);
const query = queryString.stringify(currentParams);
if (query) { if (query) {
path += `?${query}`; path += `?${query}`;
@@ -180,3 +181,58 @@ export default function getPathFromState(
return path; return path;
} }
// Object.fromEntries is not available in older iOS versions
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
entries.reduce((acc, [k, v]) => {
acc[k] = v;
return acc;
}, {} as Record<K, V>);
const joinPaths = (...paths: string[]): string =>
([] as string[])
.concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createConfigItem = (
config: OptionsItem | string,
parentPattern?: string
): ConfigItem => {
if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
return { pattern };
}
// If an object is specified as the value (e.g. Foo: { ... }),
// It can have `path` property and `screens` prop which has nested configs
const pattern =
config.exact !== true && parentPattern && config.path
? joinPaths(parentPattern, config.path)
: config.path;
const screens = config.screens
? createNormalizedConfigs(config.screens, pattern)
: undefined;
return {
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
pattern: pattern?.split('/').filter(Boolean).join('/'),
stringify: config.stringify,
screens,
};
};
const createNormalizedConfigs = (
options: PathConfig,
pattern?: string
): Record<string, ConfigItem> =>
fromEntries(
Object.entries(options).map(([name, c]) => {
const result = createConfigItem(c, pattern);
return [name, result];
})
);

View File

@@ -5,26 +5,17 @@ import {
PartialState, PartialState,
InitialState, InitialState,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { PathConfig } from './types';
type ParseConfig = Record<string, (value: string) => any>; type ParseConfig = Record<string, (value: string) => any>;
type Options = {
[routeName: string]:
| string
| {
path?: string;
parse?: ParseConfig;
screens?: Options;
initialRouteName?: string;
};
};
type RouteConfig = { type RouteConfig = {
screen: string; screen: string;
match: RegExp | null; regex?: RegExp;
path: string;
pattern: string; pattern: string;
routeNames: string[]; routeNames: string[];
parse: ParseConfig | undefined; parse?: ParseConfig;
}; };
type InitialRouteConfig = { type InitialRouteConfig = {
@@ -57,22 +48,22 @@ type ResultState = PartialState<NavigationState> & {
*/ */
export default function getStateFromPath( export default function getStateFromPath(
path: string, path: string,
options: Options = {} options: PathConfig = {}
): ResultState | undefined { ): ResultState | undefined {
let initialRoutes: InitialRouteConfig[] = []; let initialRoutes: InitialRouteConfig[] = [];
// Create a normalized configs array which will be easier to use // Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat( const configs = ([] as RouteConfig[])
...Object.keys(options).map((key) => .concat(
createNormalizedConfigs(key, options, [], initialRoutes) ...Object.keys(options).map((key) =>
createNormalizedConfigs(key, options, [], initialRoutes)
)
) )
); .sort(
(a, b) =>
// sort configs so the most exhaustive is always first to be chosen // Sort configs so the most exhaustive is always first to be chosen
configs.sort( b.pattern.split('/').length - a.pattern.split('/').length
(config1, config2) => );
config2.pattern.split('/').length - config1.pattern.split('/').length
);
let remaining = path let remaining = path
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones .replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
@@ -87,18 +78,23 @@ export default function getStateFromPath(
// 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
const match = configs.find( const match = configs.find(
(config) => (config) =>
config.pattern === '' && config.path === '' &&
config.routeNames.every( config.routeNames.every(
// make sure that none of the parent configs have a non-empty path defined // Make sure that none of the parent configs have a non-empty path defined
(name) => !configs.find((c) => c.screen === name)?.pattern (name) => !configs.find((c) => c.screen === name)?.path
) )
); );
if (match) { if (match) {
return createNestedStateObject( return createNestedStateObject(
match.routeNames, match.routeNames.map((name, i, self) => {
initialRoutes, if (i === self.length - 1) {
parseQueryParams(path, match.parse) return { name, params: parseQueryParams(path, match.parse) };
}
return { name };
}),
initialRoutes
); );
} }
@@ -110,15 +106,15 @@ export default function getStateFromPath(
while (remaining) { while (remaining) {
let routeNames: string[] | undefined; let routeNames: string[] | undefined;
let params: Record<string, any> | undefined; let allParams: Record<string, any> | undefined;
// 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
for (const config of configs) { for (const config of configs) {
if (!config.match) { if (!config.regex) {
continue; continue;
} }
const match = remaining.match(config.match); const match = remaining.match(config.regex);
// 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) {
@@ -129,16 +125,10 @@ export default function getStateFromPath(
.filter((p) => p.startsWith(':')); .filter((p) => p.startsWith(':'));
if (paramPatterns.length) { if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => { 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
const value = match[(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
if (value) { acc[p] = value;
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
}
return acc; return acc;
}, {}); }, {});
@@ -159,7 +149,46 @@ export default function getStateFromPath(
remaining = segments.join('/'); remaining = segments.join('/');
} }
const state = createNestedStateObject(routeNames, initialRoutes, params); const state = createNestedStateObject(
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 && config.parse[key]
? config.parse[key](value)
: value;
}
return acc;
}, {});
}
}
}
if (params && Object.keys(params).length) {
return { name, params };
}
return { name };
}),
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
@@ -194,44 +223,65 @@ export default function getStateFromPath(
return result; return result;
} }
function createNormalizedConfigs( const joinPaths = (...paths: string[]): string =>
key: string, ([] as string[])
routeConfig: Options, .concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createNormalizedConfigs = (
screen: string,
routeConfig: PathConfig,
routeNames: string[] = [], routeNames: string[] = [],
initials: InitialRouteConfig[] initials: InitialRouteConfig[],
): RouteConfig[] { parentPattern?: string
): RouteConfig[] => {
const configs: RouteConfig[] = []; const configs: RouteConfig[] = [];
routeNames.push(key); routeNames.push(screen);
const value = routeConfig[key]; const config = routeConfig[screen];
if (typeof value === 'string') { if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern // If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
configs.push(createConfigItem(key, routeNames, value)); const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
} else if (typeof value === 'object') {
configs.push(createConfigItem(screen, routeNames, pattern, config));
} else if (typeof config === 'object') {
let pattern: string | undefined;
// if an object is specified as the value (e.g. Foo: { ... }), // if an object is specified as the value (e.g. Foo: { ... }),
// it can have `path` property and // it can have `path` property and
// it could have `screens` prop which has nested configs // it could have `screens` prop which has nested configs
if (typeof value.path === 'string') { if (typeof config.path === 'string') {
configs.push(createConfigItem(key, routeNames, value.path, value.parse)); pattern =
config.exact !== true && parentPattern
? joinPaths(parentPattern, config.path)
: config.path;
configs.push(
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
);
} }
if (value.screens) { if (config.screens) {
// property `initialRouteName` without `screens` has no purpose // property `initialRouteName` without `screens` has no purpose
if (value.initialRouteName) { if (config.initialRouteName) {
initials.push({ initials.push({
initialRouteName: value.initialRouteName, initialRouteName: config.initialRouteName,
connectedRoutes: Object.keys(value.screens), connectedRoutes: Object.keys(config.screens),
}); });
} }
Object.keys(value.screens).forEach((nestedConfig) => {
Object.keys(config.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs( const result = createNormalizedConfigs(
nestedConfig, nestedConfig,
value.screens as Options, config.screens as PathConfig,
routeNames, routeNames,
initials initials,
pattern
); );
configs.push(...result); configs.push(...result);
}); });
} }
@@ -240,15 +290,19 @@ function createNormalizedConfigs(
routeNames.pop(); routeNames.pop();
return configs; return configs;
} };
function createConfigItem( const createConfigItem = (
screen: string, screen: string,
routeNames: string[], routeNames: string[],
pattern: string, pattern: string,
path: string,
parse?: ParseConfig parse?: ParseConfig
): RouteConfig { ): RouteConfig => {
const match = pattern // Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
pattern = pattern.split('/').filter(Boolean).join('/');
const regex = pattern
? new RegExp( ? new RegExp(
`^(${pattern `^(${pattern
.split('/') .split('/')
@@ -261,35 +315,37 @@ function createConfigItem(
}) })
.join('')})` .join('')})`
) )
: null; : undefined;
return { return {
screen, screen,
match, regex,
pattern, pattern,
path,
// The routeNames array is mutated, so copy it to keep the current state // The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames], routeNames: [...routeNames],
parse, parse,
}; };
} };
function findParseConfigForRoute( const findParseConfigForRoute = (
routeName: string, routeName: string,
flatConfig: RouteConfig[] flatConfig: RouteConfig[]
): ParseConfig | undefined { ): ParseConfig | undefined => {
for (const config of flatConfig) { for (const config of flatConfig) {
if (routeName === config.routeNames[config.routeNames.length - 1]) { if (routeName === config.routeNames[config.routeNames.length - 1]) {
return config.parse; return config.parse;
} }
} }
return undefined;
}
// tries to find an initial route connected with the one passed return undefined;
function findInitialRoute( };
// Try to find an initial route connected with the one passed
const findInitialRoute = (
routeName: string, routeName: string,
initialRoutes: InitialRouteConfig[] initialRoutes: InitialRouteConfig[]
): string | undefined { ): string | undefined => {
for (const config of initialRoutes) { for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) { if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName return config.initialRouteName === routeName
@@ -298,28 +354,25 @@ function findInitialRoute(
} }
} }
return undefined; return undefined;
} };
// returns state object with values depending on whether // returns state object with values depending on whether
// 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
function createStateObject( const createStateObject = (
initialRoute: string | undefined, initialRoute: string | undefined,
routeName: string, routeName: string,
isEmpty: boolean, params: Record<string, any> | undefined,
params?: Record<string, any> | undefined isEmpty: boolean
): InitialState { ): InitialState => {
if (isEmpty) { if (isEmpty) {
if (initialRoute) { if (initialRoute) {
return { return {
index: 1, index: 1,
routes: [ routes: [{ name: initialRoute }, { name: routeName as string, params }],
{ name: initialRoute },
{ name: routeName as string, ...(params && { params }) },
],
}; };
} else { } else {
return { return {
routes: [{ name: routeName as string, ...(params && { params }) }], routes: [{ name: routeName as string, params }],
}; };
} }
} else { } else {
@@ -328,53 +381,59 @@ function createStateObject(
index: 1, index: 1,
routes: [ routes: [
{ name: initialRoute }, { name: initialRoute },
{ name: routeName as string, state: { routes: [] } }, { name: routeName as string, params, state: { routes: [] } },
], ],
}; };
} else { } else {
return { routes: [{ name: routeName as string, state: { routes: [] } }] }; return {
routes: [{ name: routeName as string, params, state: { routes: [] } }],
};
} }
} }
} };
function createNestedStateObject( const createNestedStateObject = (
routeNames: string[], routes: { name: string; params?: object }[],
initialRoutes: InitialRouteConfig[], initialRoutes: InitialRouteConfig[]
params: object | undefined ) => {
) {
let state: InitialState; let state: InitialState;
let routeName = routeNames.shift() as string; let route = routes.shift() as { name: string; params?: object };
let initialRoute = findInitialRoute(routeName, initialRoutes); let initialRoute = findInitialRoute(route.name, initialRoutes);
state = createStateObject( state = createStateObject(
initialRoute, initialRoute,
routeName, route.name,
routeNames.length === 0, route.params,
params routes.length === 0
); );
if (routeNames.length > 0) { if (routes.length > 0) {
let nestedState = state; let nestedState = state;
while ((routeName = routeNames.shift() as string)) { while ((route = routes.shift() as { name: string; params?: object })) {
initialRoute = findInitialRoute(routeName, initialRoutes); initialRoute = findInitialRoute(route.name, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createStateObject(
const nestedStateIndex =
nestedState.index || nestedState.routes.length - 1;
nestedState.routes[nestedStateIndex].state = createStateObject(
initialRoute, initialRoute,
routeName, route.name,
routeNames.length === 0, route.params,
params routes.length === 0
); );
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0] if (routes.length > 0) {
nestedState = nestedState.routes[nestedStateIndex]
.state as InitialState; .state as InitialState;
} }
} }
} }
return state; return state;
} };
function findFocusedRoute(state: InitialState) { const findFocusedRoute = (state: InitialState) => {
let current: InitialState | undefined = state; let current: InitialState | undefined = state;
while (current?.routes[current.index || 0].state) { while (current?.routes[current.index || 0].state) {
@@ -387,12 +446,12 @@ function findFocusedRoute(state: InitialState) {
]; ];
return route; return route;
} };
function parseQueryParams( const parseQueryParams = (
path: string, path: string,
parseConfig?: Record<string, (value: string) => any> parseConfig?: Record<string, (value: string) => any>
) { ) => {
const query = path.split('?')[1]; const query = path.split('?')[1];
const params = queryString.parse(query); const params = queryString.parse(query);
@@ -405,4 +464,4 @@ function parseQueryParams(
} }
return Object.keys(params).length ? params : undefined; return Object.keys(params).length ? params : undefined;
} };

View File

@@ -422,6 +422,14 @@ export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
* Get the rehydrated navigation state of the navigation tree. * Get the rehydrated navigation state of the navigation tree.
*/ */
getRootState(): NavigationState; getRootState(): NavigationState;
/**
* Get the currently focused navigation route.
*/
getCurrentRoute(): Route<string> | undefined;
/**
* Get the currently focused route's options.
*/
getCurrentOptions(): object | undefined;
}; };
export type TypedNavigator< export type TypedNavigator<
@@ -462,3 +470,16 @@ export type TypedNavigator<
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap> _: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
) => null; ) => null;
}; };
export type PathConfig = {
[routeName: string]:
| string
| {
path?: string;
exact?: boolean;
parse?: Record<string, (value: string) => any>;
stringify?: Record<string, (value: any) => string>;
screens?: PathConfig;
initialRouteName?: string;
};
};

View File

@@ -117,6 +117,28 @@ export default function useDescriptors<
const screen = screens[route.name]; const screen = screens[route.name];
const navigation = navigations[route.key]; const navigation = navigations[route.key];
const routeOptions = {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
// @ts-ignore
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
};
acc[route.key] = { acc[route.key] = {
navigation, navigation,
render() { render() {
@@ -128,31 +150,12 @@ export default function useDescriptors<
screen={screen} screen={screen}
getState={getState} getState={getState}
setState={setState} setState={setState}
options={routeOptions}
/> />
</NavigationBuilderContext.Provider> </NavigationBuilderContext.Provider>
); );
}, },
options: { options: routeOptions,
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
// @ts-ignore
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
},
}; };
return acc; return acc;

View File

@@ -0,0 +1,70 @@
import * as React from 'react';
import { NavigationStateContext } from './BaseNavigationContainer';
import { NavigationState } from '@react-navigation/routers';
export default function useOptionsGetters({
key,
getOptions,
getState,
}: {
key?: string;
getOptions?: () => object | undefined;
getState?: () => NavigationState;
}) {
const optionsGettersFromChild = React.useRef<
Record<string, (() => object | undefined | null) | undefined>
>({});
const { addOptionsGetter: parentAddOptionsGetter } = React.useContext(
NavigationStateContext
);
const getOptionsFromListener = React.useCallback(() => {
for (let key in optionsGettersFromChild.current) {
if (optionsGettersFromChild.current.hasOwnProperty(key)) {
const result = optionsGettersFromChild.current[key]?.();
// null means unfocused route
if (result !== null) {
return result;
}
}
}
return null;
}, []);
const getCurrentOptions = React.useCallback(() => {
if (getState) {
const state = getState();
if (state.routes[state.index].key !== key) {
// null means unfocused route
return null;
}
}
const optionsFromListener = getOptionsFromListener();
if (optionsFromListener !== null) {
return optionsFromListener;
}
return getOptions?.() ?? undefined;
}, [getState, getOptionsFromListener, getOptions, key]);
React.useEffect(() => {
return parentAddOptionsGetter?.(key!, getCurrentOptions);
}, [getCurrentOptions, parentAddOptionsGetter, key]);
const addOptionsGetter = React.useCallback(
(key: string, getter: () => object | undefined | null) => {
optionsGettersFromChild.current[key] = getter;
return () => {
optionsGettersFromChild.current[key] = undefined;
};
},
[]
);
return {
addOptionsGetter,
getCurrentOptions,
};
}

View File

@@ -8,6 +8,15 @@ const UNINTIALIZED_STATE = {};
export default function useSyncState<T>(initialState?: (() => T) | T) { export default function useSyncState<T>(initialState?: (() => T) | T) {
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any); const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
const isSchedulingRef = React.useRef(false); const isSchedulingRef = React.useRef(false);
const isMountedRef = React.useRef(true);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
if (stateRef.current === UNINTIALIZED_STATE) { if (stateRef.current === UNINTIALIZED_STATE) {
stateRef.current = stateRef.current =
@@ -20,7 +29,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
const getState = React.useCallback(() => stateRef.current, []); const getState = React.useCallback(() => stateRef.current, []);
const setState = React.useCallback((state: T) => { const setState = React.useCallback((state: T) => {
if (state === stateRef.current) { if (state === stateRef.current || !isMountedRef.current) {
return; return;
} }
@@ -42,6 +51,10 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
}, []); }, []);
const flushUpdates = React.useCallback(() => { const flushUpdates = React.useCallback(() => {
if (!isMountedRef.current) {
return;
}
// Make sure that the tracking state is up-to-date. // Make sure that the tracking state is up-to-date.
// We call it unconditionally, but React should skip the update if state is unchanged. // We call it unconditionally, but React should skip the update if state is unchanged.
setTrackingState(stateRef.current); setTrackingState(stateRef.current);

View File

@@ -3,6 +3,46 @@
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.7.7](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.6...@react-navigation/drawer@5.7.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.5...@react-navigation/drawer@5.7.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.4...@react-navigation/drawer@5.7.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.3...@react-navigation/drawer@5.7.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.2...@react-navigation/drawer@5.7.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10) ## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/drawer **Note:** Version bump only for package @react-navigation/drawer

View File

@@ -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.7.2", "version": "5.7.7",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -40,8 +40,8 @@
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",

View File

@@ -3,6 +3,49 @@
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.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.6...@react-navigation/material-bottom-tabs@5.2.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.5...@react-navigation/material-bottom-tabs@5.2.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.4...@react-navigation/material-bottom-tabs@5.2.5) (2020-05-16)
### Bug Fixes
* center icons in material tab bar. fixes [#8248](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8248) ([51b4087](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/51b40879bdb9cea5462a2291955513a88eb87340))
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.3...@react-navigation/material-bottom-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.2...@react-navigation/material-bottom-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10) ## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs **Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -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.2.2", "version": "5.2.7",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -36,8 +36,8 @@
"clean": "del lib" "clean": "del lib"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",
"@types/react-native-vector-icons": "^6.4.5", "@types/react-native-vector-icons": "^6.4.5",

View File

@@ -69,6 +69,7 @@ function MaterialBottomTabViewInner({
borderless: _1, borderless: _1,
centered: _2, centered: _2,
rippleColor: _3, rippleColor: _3,
style,
...rest ...rest
}) => { }) => {
return ( return (
@@ -86,6 +87,7 @@ function MaterialBottomTabViewInner({
onPress?.(e); onPress?.(e);
} }
}} }}
style={[styles.touchable, style]}
/> />
); );
} }
@@ -153,4 +155,8 @@ const styles = StyleSheet.create({
icon: { icon: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
touchable: {
display: 'flex',
justifyContent: 'center',
},
}); });

View File

@@ -3,6 +3,46 @@
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.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.6...@react-navigation/material-top-tabs@5.2.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.5...@react-navigation/material-top-tabs@5.2.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.4...@react-navigation/material-top-tabs@5.2.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.3...@react-navigation/material-top-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.2...@react-navigation/material-top-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10) ## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-top-tabs **Note:** Version bump only for package @react-navigation/material-top-tabs

View File

@@ -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.2.2", "version": "5.2.7",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -39,8 +39,8 @@
"color": "^3.1.2" "color": "^3.1.2"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",

View File

@@ -3,6 +3,54 @@
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.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.4.1...@react-navigation/native@5.4.2) (2020-05-20)
**Note:** Version bump only for package @react-navigation/native
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.4.0...@react-navigation/native@5.4.1) (2020-05-20)
**Note:** Version bump only for package @react-navigation/native
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.2...@react-navigation/native@5.4.0) (2020-05-16)
### Bug Fixes
* fix types for linking options ([d14f38b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d14f38b80ad569d5828c1919cea426c659173924))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.1...@react-navigation/native@5.3.2) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.0...@react-navigation/native@5.3.1) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10) # [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10)

View File

@@ -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.3.0", "version": "5.4.2",
"keywords": [ "keywords": [
"react-native", "react-native",
"react-navigation", "react-navigation",
@@ -32,10 +32,10 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^5.5.2" "@react-navigation/core": "^5.8.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",

View File

@@ -1,6 +1,7 @@
import { import {
getStateFromPath as getStateFromPathDefault, getStateFromPath as getStateFromPathDefault,
getPathFromState as getPathFromStateDefault, getPathFromState as getPathFromStateDefault,
PathConfig,
} from '@react-navigation/core'; } from '@react-navigation/core';
export type Theme = { export type Theme = {
@@ -39,7 +40,7 @@ export type LinkingOptions = {
* } * }
* ``` * ```
*/ */
config?: Parameters<typeof getStateFromPathDefault>[1]; config?: PathConfig;
/** /**
* Custom function to parse the URL to a valid navigation state (advanced). * Custom function to parse the URL to a valid navigation state (advanced).
* Only applicable on Web. * Only applicable on Web.

View File

@@ -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.4.6](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.5...@react-navigation/routers@5.4.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.4...@react-navigation/routers@5.4.5) (2020-05-20)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.3...@react-navigation/routers@5.4.4) (2020-05-08) ## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.3...@react-navigation/routers@5.4.4) (2020-05-08)

View File

@@ -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.4.4", "version": "5.4.6",
"keywords": [ "keywords": [
"react", "react",
"react-native", "react-native",
@@ -34,7 +34,7 @@
"nanoid": "^3.1.5" "nanoid": "^3.1.5"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.3.9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.8...@react-navigation/stack@5.3.9) (2020-05-20)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.8](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.7...@react-navigation/stack@5.3.8) (2020-05-20)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.7](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.6...@react-navigation/stack@5.3.7) (2020-05-16)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.6](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.5...@react-navigation/stack@5.3.6) (2020-05-15)
### Bug Fixes
* reduce header title margin. fixes [#8267](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8267) ([d45dbe9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/d45dbe97dc6625c6a8e80b5e658ab5a95045e5e8))
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.4...@react-navigation/stack@5.3.5) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.3...@react-navigation/stack@5.3.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11) ## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11)

View File

@@ -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.3.3", "version": "5.3.9",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -39,9 +39,9 @@
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.13.1", "@react-native-community/bob": "^0.14.3",
"@react-native-community/masked-view": "^0.1.10", "@react-native-community/masked-view": "^0.1.10",
"@react-navigation/native": "^5.3.0", "@react-navigation/native": "^5.4.2",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-native": "^0.62.7", "@types/react-native": "^0.62.7",

View File

@@ -355,7 +355,9 @@ export default class HeaderSegment extends React.Component<Props, State> {
: { : {
marginHorizontal: marginHorizontal:
(leftButton ? 32 : 16) + (leftButton ? 32 : 16) +
(leftLabelLayout?.width || 0) + (leftButton && headerBackTitleVisible !== false
? 40
: 0) +
Math.max(insets.left, insets.right), Math.max(insets.left, insets.right),
}, },
titleStyle, titleStyle,

View File

@@ -3516,10 +3516,10 @@
dependencies: dependencies:
"@types/node" ">= 8" "@types/node" ">= 8"
"@react-native-community/bob@^0.13.1": "@react-native-community/bob@^0.14.3":
version "0.13.1" version "0.14.3"
resolved "https://registry.yarnpkg.com/@react-native-community/bob/-/bob-0.13.1.tgz#188fb72a925751fa92bc5ed543c33ca8ceea9375" resolved "https://registry.yarnpkg.com/@react-native-community/bob/-/bob-0.14.3.tgz#0a8d615f936844d67543d0305e5a2f332fdee630"
integrity sha512-MTngPqrasri2B4y9cVvBCrBrwcwt36izS17n4q4Os+eSK10LXT1Rd7imd9r3lRfdVxF90Dgar91W+hbvJRAWLg== integrity sha512-OfE+cpUj558x4NPLCSzfzdwgl+MbG+Tzuc/RXVnPAsO5aYkYQ0slThGPDK1qAIy7LnuSagcOYOQTU640B8WQqA==
dependencies: dependencies:
"@babel/core" "^7.9.6" "@babel/core" "^7.9.6"
"@babel/plugin-proposal-class-properties" "^7.8.3" "@babel/plugin-proposal-class-properties" "^7.8.3"
@@ -3527,6 +3527,7 @@
"@babel/preset-flow" "^7.9.0" "@babel/preset-flow" "^7.9.0"
"@babel/preset-react" "^7.9.4" "@babel/preset-react" "^7.9.4"
"@babel/preset-typescript" "^7.9.0" "@babel/preset-typescript" "^7.9.0"
browserslist "^4.12.0"
chalk "^4.0.0" chalk "^4.0.0"
cosmiconfig "^6.0.0" cosmiconfig "^6.0.0"
cross-spawn "^7.0.2" cross-spawn "^7.0.2"
@@ -5568,7 +5569,7 @@ browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.9.1:
node-releases "^1.1.53" node-releases "^1.1.53"
pkg-up "^2.0.0" pkg-up "^2.0.0"
browserslist@^4.11.1: browserslist@^4.11.1, browserslist@^4.12.0:
version "4.12.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d"
integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==