Compare commits

..

42 Commits

Author SHA1 Message Date
Satyajit Sahoo
c514542305 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.32
 - @react-navigation/compat@5.0.0-alpha.21
 - @react-navigation/drawer@5.0.0-alpha.34
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.29
 - @react-navigation/material-top-tabs@5.0.0-alpha.27
 - @react-navigation/native-stack@5.0.0-alpha.22
 - @react-navigation/routers@5.0.0-alpha.20
 - @react-navigation/stack@5.0.0-alpha.51
2020-01-05 01:40:51 +01:00
Satyajit Sahoo
dcc5f99ecd feat: add support for pager component 2020-01-05 01:20:53 +01:00
Satyajit Sahoo
adbeb292f5 fix: preserve focused route in tab on changing screens list 2020-01-05 01:14:25 +01:00
Satyajit Sahoo
543679f185 chore: fix webpack config for example 2020-01-04 01:48:34 +01:00
Satyajit Sahoo
cbe240eae6 chore: publish
- @react-navigation/stack@5.0.0-alpha.50
2020-01-03 21:42:13 +01:00
Satyajit Sahoo
7f963a74bb fix: keep screens for replace when animation is enabled 2020-01-03 21:39:03 +01:00
Satyajit Sahoo
572beae41b fix: use gesture direction when using next screen's animation 2020-01-03 21:27:48 +01:00
Satyajit Sahoo
15fe3ebb51 refactor: remove PointerEventsView 2020-01-03 21:22:44 +01:00
Satyajit Sahoo
31565d5576 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.31
 - @react-navigation/drawer@5.0.0-alpha.33
 - @react-navigation/native-stack@5.0.0-alpha.21
 - @react-navigation/stack@5.0.0-alpha.49
2020-01-03 18:12:02 +01:00
Satyajit Sahoo
2c31d1705c fix: dismiss keyboard on page change 2020-01-03 18:11:44 +01:00
Janic Duplessis
8f9a250958 Use space instead of empty string to hide back button on iOS (#234) 2020-01-03 17:16:48 +01:00
Satyajit Sahoo
87d28ca430 refactor: minor tweaks 2020-01-03 17:07:42 +01:00
Michał Osadnik
fa4411a14d feat: add interaction handle to drawer (#239) 2020-01-03 17:05:37 +01:00
Satyajit Sahoo
77b757091c fix: provide initial values for safe area to prevent blank screen (#238)
https://github.com/react-navigation/stack/issues/328
2020-01-03 16:25:59 +01:00
Michał Osadnik
6b9b999c5b fix: interaction manager in stack (#237) 2020-01-03 15:48:06 +01:00
imgbot[bot]
8c5f84094f chore: optimize images (#236)
*Total -- 272.34kb -> 260.85kb (4.22%)

/packages/stack/src/views/assets/back-icon-mask.png -- 1.58kb -> 0.89kb (43.61%)
/example/assets/avatar-1.png -- 3.33kb -> 2.40kb (27.96%)
/example/assets/avatar-2.png -- 2.29kb -> 1.67kb (26.87%)
/example/assets/icon.png -- 1.07kb -> 0.88kb (17.51%)
/example/assets/album-art-4.jpg -- 34.70kb -> 33.05kb (4.75%)
/example/assets/album-art-1.jpg -- 28.85kb -> 27.58kb (4.38%)
/example/assets/album-art-3.jpg -- 28.53kb -> 27.37kb (4.05%)
/example/assets/book.jpg -- 97.60kb -> 94.13kb (3.56%)
/example/assets/album-art-2.jpg -- 20.94kb -> 20.33kb (2.94%)
/example/assets/album-art-6.jpg -- 22.07kb -> 21.57kb (2.26%)
/example/assets/album-art-8.jpg -- 21.28kb -> 20.92kb (1.69%)
/example/assets/album-art-7.jpg -- 10.10kb -> 10.05kb (0.5%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: Imgbot <help@imgbot.net>
2020-01-03 11:37:33 +01:00
Satyajit Sahoo
21709f7674 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.30
 - @react-navigation/drawer@5.0.0-alpha.32
2020-01-03 11:11:09 +01:00
Satyajit Sahoo
22742e4a7f chore: bump peer dep version for safe-area-context 2020-01-03 11:08:22 +01:00
Satyajit Sahoo
3cd1aedcf4 fix: pass backBehavior to the router in drawer. fixes #230 2020-01-03 11:00:35 +01:00
Satyajit Sahoo
797d3a798e chore: publish
- @react-navigation/stack@5.0.0-alpha.48
2020-01-01 16:28:28 +01:00
Satyajit Sahoo
59803f54d6 fix: improve gesture performance 2020-01-01 16:26:51 +01:00
Satyajit Sahoo
935659899f fix: use native driver for gestures 2020-01-01 15:48:26 +01:00
Satyajit Sahoo
ef0f5d6567 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.29
 - @react-navigation/compat@5.0.0-alpha.20
 - @react-navigation/core@5.0.0-alpha.30
 - @react-navigation/drawer@5.0.0-alpha.31
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.28
 - @react-navigation/material-top-tabs@5.0.0-alpha.26
 - @react-navigation/native-stack@5.0.0-alpha.20
 - @react-navigation/native@5.0.0-alpha.22
 - @react-navigation/routers@5.0.0-alpha.19
 - @react-navigation/stack@5.0.0-alpha.47
2020-01-01 13:33:50 +01:00
Satyajit Sahoo
499f66dba4 chore: fix type error 2020-01-01 13:32:50 +01:00
Satyajit Sahoo
2ef2f1a86f refactor: navigate instead of reset when we can when handling links 2020-01-01 13:28:31 +01:00
Satyajit Sahoo
0252bdc222 fix: show error if an action was not handled 2020-01-01 13:12:41 +01:00
Satyajit Sahoo
282f62c258 refactor: use animated instead of reanimated 2020-01-01 12:53:01 +01:00
Satyajit Sahoo
f462d67270 fix: cleanup transaction even if action wasn't handled 2019-12-25 01:41:14 +01:00
Satyajit Sahoo
873afec9fe chore: tweak route names in example 2019-12-19 23:25:58 +01:00
Satyajit Sahoo
878297e52f chore: update eslint config 2019-12-19 23:13:23 +01:00
Satyajit Sahoo
1ea9b4524d chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.28
 - @react-navigation/compat@5.0.0-alpha.19
 - @react-navigation/core@5.0.0-alpha.29
 - @react-navigation/drawer@5.0.0-alpha.30
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.27
 - @react-navigation/material-top-tabs@5.0.0-alpha.25
 - @react-navigation/native-stack@5.0.0-alpha.19
 - @react-navigation/native@5.0.0-alpha.21
 - @react-navigation/routers@5.0.0-alpha.18
 - @react-navigation/stack@5.0.0-alpha.46
2019-12-19 14:45:13 +01:00
Satyajit Sahoo
c52a8c46a8 fix: fix typescript issues 2019-12-19 14:39:35 +01:00
Satyajit Sahoo
0635365483 fix: set screen background in drawer from theme 2019-12-17 00:01:47 +01:00
Satyajit Sahoo
9843b92e05 chore: upgrade deps 2019-12-16 23:42:19 +01:00
Satyajit Sahoo
ebd145a09d fix: fix backgroundColor in sceneContainerStyle overriden by theme
Closes #215
2019-12-16 23:00:12 +01:00
Satyajit Sahoo
9fc1af02c2 refactor: remove extra prop 2019-12-16 15:43:38 +01:00
Satyajit Sahoo
68a334cc93 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.27
 - @react-navigation/compat@5.0.0-alpha.18
 - @react-navigation/core@5.0.0-alpha.28
 - @react-navigation/drawer@5.0.0-alpha.29
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.26
 - @react-navigation/material-top-tabs@5.0.0-alpha.24
 - @react-navigation/native-stack@5.0.0-alpha.18
 - @react-navigation/native@5.0.0-alpha.20
 - @react-navigation/routers@5.0.0-alpha.17
 - @react-navigation/stack@5.0.0-alpha.45
2019-12-16 15:27:11 +01:00
Satyajit Sahoo
c110570d4c fix: disable style interpolation for card when animation is disabled 2019-12-16 15:25:19 +01:00
Satyajit Sahoo
d57226fd8b refactor: remove cardTransparent in favor of cardStyle 2019-12-16 15:09:10 +01:00
Oliver Winter
c3d3748143 fix: use Partial type for initialParam (#206) 2019-12-16 13:58:37 +01:00
Wojciech Lewicki
8002d51795 feat: add nested config in deep linking (#210)
Fixes #154
2019-12-16 13:30:28 +01:00
Erivelton Elias
31cf19912b chore: update README expo and native-stack (#212) 2019-12-16 12:29:31 +01:00
109 changed files with 2404 additions and 1957 deletions

View File

@@ -13,5 +13,8 @@ module.exports = {
'@babel/preset-react', '@babel/preset-react',
'@babel/preset-typescript', '@babel/preset-typescript',
], ],
plugins: ['@babel/plugin-proposal-class-properties'], plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
],
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 900 B

View File

@@ -28,24 +28,25 @@
"react-dom": "~16.9.0", "react-dom": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "~1.5.0", "react-native-gesture-handler": "~1.5.0",
"react-native-paper": "^3.2.1", "react-native-iphone-x-helper": "^1.2.1",
"react-native-paper": "^3.3.0",
"react-native-reanimated": "^1.4.0", "react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0", "react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.12", "react-native-screens": "^2.0.0-alpha.19",
"react-native-tab-view": "2.11.0", "react-native-tab-view": "2.11.0",
"react-native-unimodules": "^0.6.0", "react-native-unimodules": "^0.7.0",
"react-native-web": "^0.11.7", "react-native-web": "^0.11.7",
"scheduler": "^0.18.0", "scheduler": "^0.18.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"use-subscription": "^1.3.0" "use-subscription": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.2", "@babel/core": "^7.7.5",
"@expo/webpack-config": "^0.10.1", "@expo/webpack-config": "^0.10.6",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"babel-preset-expo": "^7.1.0", "babel-preset-expo": "^8.0.0",
"expo-cli": "^3.8.0", "expo-cli": "^3.11.1",
"typescript": "^3.7.2" "typescript": "^3.7.3"
} }
} }

View File

@@ -16,10 +16,10 @@ const getTabBarIcon = (name: string) => ({
}) => <MaterialCommunityIcons name={name} color={color} size={size} />; }) => <MaterialCommunityIcons name={name} color={color} size={size} />;
type BottomTabParams = { type BottomTabParams = {
article: undefined; Article: undefined;
albums: undefined; Albums: undefined;
contacts: undefined; Contacts: undefined;
chat: undefined; Chat: undefined;
}; };
const BottomTabs = createBottomTabNavigator<BottomTabParams>(); const BottomTabs = createBottomTabNavigator<BottomTabParams>();
@@ -32,7 +32,7 @@ export default function BottomTabsScreen() {
}} }}
> >
<BottomTabs.Screen <BottomTabs.Screen
name="article" name="Article"
options={{ options={{
title: 'Article', title: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'), tabBarIcon: getTabBarIcon('file-document-box'),
@@ -41,7 +41,7 @@ export default function BottomTabsScreen() {
{props => <SimpleStackScreen {...props} headerMode="none" />} {props => <SimpleStackScreen {...props} headerMode="none" />}
</BottomTabs.Screen> </BottomTabs.Screen>
<BottomTabs.Screen <BottomTabs.Screen
name="chat" name="Chat"
component={Chat} component={Chat}
options={{ options={{
tabBarLabel: 'Chat', tabBarLabel: 'Chat',
@@ -49,7 +49,7 @@ export default function BottomTabsScreen() {
}} }}
/> />
<BottomTabs.Screen <BottomTabs.Screen
name="contacts" name="Contacts"
component={Contacts} component={Contacts}
options={{ options={{
title: 'Contacts', title: 'Contacts',
@@ -57,7 +57,7 @@ export default function BottomTabsScreen() {
}} }}
/> />
<BottomTabs.Screen <BottomTabs.Screen
name="albums" name="Albums"
component={Albums} component={Albums}
options={{ options={{
title: 'Albums', title: 'Albums',

View File

@@ -7,10 +7,10 @@ import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack'; import SimpleStackScreen from './SimpleStack';
type MaterialBottomTabParams = { type MaterialBottomTabParams = {
article: undefined; Article: undefined;
albums: undefined; Albums: undefined;
contacts: undefined; Contacts: undefined;
chat: undefined; Chat: undefined;
}; };
const MaterialBottomTabs = createMaterialBottomTabNavigator< const MaterialBottomTabs = createMaterialBottomTabNavigator<
@@ -21,7 +21,7 @@ export default function MaterialBottomTabsScreen() {
return ( return (
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}> <MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="article" name="Article"
options={{ options={{
tabBarLabel: 'Article', tabBarLabel: 'Article',
tabBarIcon: 'file-document-box', tabBarIcon: 'file-document-box',
@@ -31,7 +31,7 @@ export default function MaterialBottomTabsScreen() {
{props => <SimpleStackScreen {...props} headerMode="none" />} {props => <SimpleStackScreen {...props} headerMode="none" />}
</MaterialBottomTabs.Screen> </MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="chat" name="Chat"
component={Chat} component={Chat}
options={{ options={{
tabBarLabel: 'Chat', tabBarLabel: 'Chat',
@@ -41,7 +41,7 @@ export default function MaterialBottomTabsScreen() {
}} }}
/> />
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="contacts" name="Contacts"
component={Contacts} component={Contacts}
options={{ options={{
tabBarLabel: 'Contacts', tabBarLabel: 'Contacts',
@@ -50,7 +50,7 @@ export default function MaterialBottomTabsScreen() {
}} }}
/> />
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="albums" name="Albums"
component={Albums} component={Albums}
options={{ options={{
tabBarLabel: 'Albums', tabBarLabel: 'Albums',

View File

@@ -5,9 +5,9 @@ import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat'; import Chat from '../Shared/Chat';
type MaterialTopTabParams = { type MaterialTopTabParams = {
albums: undefined; Albums: undefined;
contacts: undefined; Contacts: undefined;
chat: undefined; Chat: undefined;
}; };
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>(); const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
@@ -16,17 +16,17 @@ export default function MaterialTopTabsScreen() {
return ( return (
<MaterialTopTabs.Navigator> <MaterialTopTabs.Navigator>
<MaterialTopTabs.Screen <MaterialTopTabs.Screen
name="chat" name="Chat"
component={Chat} component={Chat}
options={{ title: 'Chat' }} options={{ title: 'Chat' }}
/> />
<MaterialTopTabs.Screen <MaterialTopTabs.Screen
name="contacts" name="Contacts"
component={Contacts} component={Contacts}
options={{ title: 'Contacts' }} options={{ title: 'Contacts' }}
/> />
<MaterialTopTabs.Screen <MaterialTopTabs.Screen
name="albums" name="Albums"
component={Albums} component={Albums}
options={{ title: 'Albums' }} options={{ title: 'Albums' }}
/> />

View File

@@ -11,19 +11,19 @@ import {
import Article from '../Shared/Article'; import Article from '../Shared/Article';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
type SimpleStackParams = { type ModalStackParams = {
article: { author: string }; Article: { author: string };
album: undefined; Album: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
const ArticleScreen = ({ const ArticleScreen = ({
navigation, navigation,
route, route,
}: { }: {
navigation: SimpleStackNavigation; navigation: ModalStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>; route: RouteProp<ModalStackParams, 'Article'>;
}) => { }) => {
const insets = useSafeArea(); const insets = useSafeArea();
@@ -32,7 +32,7 @@ const ArticleScreen = ({
<View style={[styles.buttons, { marginTop: insets.top }]}> <View style={[styles.buttons, { marginTop: insets.top }]}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('album')} onPress={() => navigation.push('Album')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -50,11 +50,7 @@ const ArticleScreen = ({
); );
}; };
const AlbumsScreen = ({ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const insets = useSafeArea(); const insets = useSafeArea();
return ( return (
@@ -62,7 +58,7 @@ const AlbumsScreen = ({
<View style={[styles.buttons, { marginTop: insets.top }]}> <View style={[styles.buttons, { marginTop: insets.top }]}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })} onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button} style={styles.button}
> >
Push article Push article
@@ -80,7 +76,7 @@ const AlbumsScreen = ({
); );
}; };
const ModalPresentationStack = createStackNavigator<SimpleStackParams>(); const ModalPresentationStack = createStackNavigator<ModalStackParams>();
type Props = { type Props = {
options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>; options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>;
@@ -104,7 +100,7 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
{...options} {...options}
> >
<ModalPresentationStack.Screen <ModalPresentationStack.Screen
name="article" name="Article"
component={ArticleScreen} component={ArticleScreen}
options={({ route }) => ({ options={({ route }) => ({
title: `Article by ${route.params.author}`, title: `Article by ${route.params.author}`,
@@ -112,7 +108,7 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<ModalPresentationStack.Screen <ModalPresentationStack.Screen
name="album" name="Album"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Album' }}
/> />

View File

@@ -18,8 +18,8 @@ import {
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
type NativeStackParams = { type NativeStackParams = {
article: { author: string }; Article: { author: string };
album: undefined; Album: undefined;
}; };
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>; type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
@@ -42,7 +42,7 @@ const ArticleScreen = ({
navigation, navigation,
}: { }: {
navigation: NativeStackNavigation; navigation: NativeStackNavigation;
route: RouteProp<NativeStackParams, 'article'>; route: RouteProp<NativeStackParams, 'Article'>;
}) => { }) => {
const { colors } = useTheme(); const { colors } = useTheme();
@@ -54,7 +54,7 @@ const ArticleScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('album')} onPress={() => navigation.push('Album')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -141,7 +141,7 @@ const AlbumsScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })} onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button} style={styles.button}
> >
Push article Push article
@@ -188,7 +188,7 @@ export default function NativeStackScreen({ navigation }: Props) {
return ( return (
<NativeStack.Navigator> <NativeStack.Navigator>
<NativeStack.Screen <NativeStack.Screen
name="article" name="Article"
component={ArticleScreen} component={ArticleScreen}
options={{ options={{
title: 'Lorem Ipsum', title: 'Lorem Ipsum',
@@ -197,7 +197,7 @@ export default function NativeStackScreen({ navigation }: Props) {
}} }}
/> />
<NativeStack.Screen <NativeStack.Screen
name="album" name="Album"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Album' }}
/> />

View File

@@ -10,8 +10,8 @@ import Article from '../Shared/Article';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
type SimpleStackParams = { type SimpleStackParams = {
article: { author: string }; Article: { author: string };
album: undefined; Album: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -21,14 +21,14 @@ const ArticleScreen = ({
route, route,
}: { }: {
navigation: SimpleStackNavigation; navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>; route: RouteProp<SimpleStackParams, 'Article'>;
}) => { }) => {
return ( return (
<React.Fragment> <React.Fragment>
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('album')} onPress={() => navigation.push('Album')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -56,7 +56,7 @@ const AlbumsScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })} onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button} style={styles.button}
> >
Push article Push article
@@ -88,7 +88,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
return ( return (
<SimpleStack.Navigator {...rest}> <SimpleStack.Navigator {...rest}>
<SimpleStack.Screen <SimpleStack.Screen
name="article" name="Article"
component={ArticleScreen} component={ArticleScreen}
options={({ route }) => ({ options={({ route }) => ({
title: `Article by ${route.params.author}`, title: `Article by ${route.params.author}`,
@@ -96,7 +96,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="album" name="Album"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Album' }}
/> />

View File

@@ -1,3 +1,5 @@
/* eslint-disable import/no-commonjs */
import * as React from 'react'; import * as React from 'react';
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native'; import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native'; import { useScrollToTop } from '@react-navigation/native';

View File

@@ -21,7 +21,6 @@ import {
import { Asset } from 'expo-asset'; import { Asset } from 'expo-asset';
import { import {
InitialState, InitialState,
getStateFromPath,
useLinking, useLinking,
NavigationContainerRef, NavigationContainerRef,
NavigationNativeContainer, NavigationNativeContainer,
@@ -104,20 +103,19 @@ export default function App() {
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`) // The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, { const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes, prefixes: LinkingPrefixes,
getStateFromPath: path => { config: {
const state = getStateFromPath(path); Root: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
return { return acc;
routes: [ },
{ {}
name: 'root', ),
state: {
...state,
routes: [{ name: 'home' }, ...(state ? state.routes : [])],
},
},
],
};
}, },
}); });
@@ -252,19 +250,19 @@ export default function App() {
/> />
</View> </View>
<Divider /> <Divider />
{(Object.keys(SCREENS) as Array< {(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
keyof typeof SCREENS name => (
>).map(name => ( <List.Item
<List.Item key={name}
key={name} title={SCREENS[name].title}
title={SCREENS[name].title} onPress={() => navigation.push(name)}
onPress={() => navigation.push(name)} />
/> )
))} )}
</ScrollView> </ScrollView>
)} )}
</Stack.Screen> </Stack.Screen>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map( {(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => ( name => (
<Stack.Screen <Stack.Screen
key={name} key={name}

View File

@@ -4,6 +4,9 @@ const createExpoWebpackConfigAsync = require('@expo/webpack-config');
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const node_modules = path.resolve(__dirname, 'node_modules');
const packages = path.resolve(__dirname, '..', 'packages');
module.exports = async function(env, argv) { module.exports = async function(env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv); const config = await createExpoWebpackConfigAsync(env, argv);
@@ -19,28 +22,15 @@ module.exports = async function(env, argv) {
); );
Object.assign(config.resolve.alias, { Object.assign(config.resolve.alias, {
react: path.resolve(__dirname, 'node_modules', 'react'), react: path.resolve(node_modules, 'react'),
'react-native': path.resolve(__dirname, 'node_modules', 'react-native-web'), 'react-native': path.resolve(node_modules, 'react-native-web'),
'react-native-web': path.resolve( 'react-native-web': path.resolve(node_modules, 'react-native-web'),
__dirname, '@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
'node_modules',
'react-native-web'
),
'react-native-reanimated': path.resolve(
__dirname,
'node_modules',
'react-native-reanimated-web'
),
'@expo/vector-icons/MaterialCommunityIcons': require.resolve(
'@expo/vector-icons/MaterialCommunityIcons'
),
}); });
fs.readdirSync(path.join(__dirname, '..')).forEach(name => { fs.readdirSync(packages).forEach(name => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve( config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
__dirname, packages,
'..',
'packages',
name, name,
'src' 'src'
); );

View File

@@ -2,7 +2,7 @@ const error = console.error;
console.error = (...args) => console.error = (...args) =>
// Supress error messages regarding error boundary in tests // Supress error messages regarding error boundary in tests
/Consider adding an error boundary to your tree to customize error handling behavior/m.test( /(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
args[0] args[0]
) )
? void 0 ? void 0

View File

@@ -24,22 +24,23 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.7.0", "@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/preset-env": "^7.7.1", "@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.0", "@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.2", "@babel/preset-typescript": "^7.7.2",
"@babel/runtime": "^7.7.2", "@babel/runtime": "^7.7.6",
"@commitlint/config-conventional": "^8.2.0", "@commitlint/config-conventional": "^8.2.0",
"@types/jest": "^24.0.23", "@types/jest": "^24.0.23",
"codecov": "^3.6.1", "codecov": "^3.6.1",
"commitlint": "^8.2.0", "commitlint": "^8.2.0",
"core-js": "^3.4.1", "core-js": "^3.5.0",
"eslint": "^6.6.0", "eslint": "^6.7.2",
"eslint-config-satya164": "^3.1.2", "eslint-config-satya164": "^3.1.5",
"husky": "^3.0.9", "husky": "^3.0.9",
"jest": "^24.8.0", "jest": "^24.8.0",
"lerna": "^3.18.4", "lerna": "^3.18.4",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"resolutions": { "resolutions": {
"react": "~16.9.0", "react": "~16.9.0",

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.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.31...@react-navigation/bottom-tabs@5.0.0-alpha.32) (2020-01-05)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.30...@react-navigation/bottom-tabs@5.0.0-alpha.31) (2020-01-03)
### Bug Fixes
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.29...@react-navigation/bottom-tabs@5.0.0-alpha.30) (2020-01-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.28...@react-navigation/bottom-tabs@5.0.0-alpha.29) (2020-01-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.27...@react-navigation/bottom-tabs@5.0.0-alpha.28) (2019-12-19)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.26...@react-navigation/bottom-tabs@5.0.0-alpha.27) (2019-12-16)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.25...@react-navigation/bottom-tabs@5.0.0-alpha.26) (2019-12-14) # [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.25...@react-navigation/bottom-tabs@5.0.0-alpha.26) (2019-12-14)

View File

@@ -10,7 +10,7 @@
"android", "android",
"tab" "tab"
], ],
"version": "5.0.0-alpha.26", "version": "5.0.0-alpha.32",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,25 +33,26 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16", "@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2" "color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/color": "^3.0.0", "@types/color": "^3.0.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-safe-area-context": "^0.6.0", "react-native-safe-area-context": "^0.6.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",
"react": "*", "react": "*",
"react-native": "*", "react-native": "*",
"react-native-safe-area-context": "^0.3.6" "react-native-safe-area-context": "^0.6.0"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {
"source": "src", "source": "src",

View File

@@ -14,7 +14,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
if (screensEnabled && screensEnabled()) { if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore // @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />; return <Screen active={isVisible ? 1 : 0} {...rest} />;

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider, SafeAreaProvider,
SafeAreaConsumer, SafeAreaConsumer,
} from 'react-native-safe-area-context'; } from 'react-native-safe-area-context';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
const initialSafeAreaInsets = {
top: getStatusBarHeight(true),
bottom: getBottomSpace(),
right: 0,
left: 0,
};
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children; return children;
} }
return <SafeAreaProvider>{children}</SafeAreaProvider>; return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}} }}
</SafeAreaConsumer> </SafeAreaConsumer>
); );

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.17...@react-navigation/compat@5.0.0-alpha.18) (2019-12-16)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11) # [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11)
**Note:** Version bump only for package @react-navigation/compat **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.0.0-alpha.17", "version": "5.0.0-alpha.21",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,12 +24,12 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16" "@react-navigation/routers": "^5.0.0-alpha.20"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"react": "~16.9.0", "react": "~16.9.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",

View File

@@ -25,7 +25,7 @@ export function navigate({
} }
export function back(options?: { key?: null | string }) { export function back(options?: { key?: null | string }) {
return options && options.key != null return options?.key != null
? (state: NavigationState) => ({ ? (state: NavigationState) => ({
...CommonActions.goBack(), ...CommonActions.goBack(),
source: options.key, source: options.key,

View File

@@ -109,17 +109,17 @@ export default function createCompatNavigationProp<
break; break;
case 'didFocus': { case 'didFocus': {
const unsubscribe = focusSubscriptions.get(callback); const unsubscribe = focusSubscriptions.get(callback);
unsubscribe && unsubscribe(); unsubscribe?.();
break; break;
} }
case 'didBlur': { case 'didBlur': {
const unsubscribe = blurSubscriptions.get(callback); const unsubscribe = blurSubscriptions.get(callback);
unsubscribe && unsubscribe(); unsubscribe?.();
break; break;
} }
case 'refocus': { case 'refocus': {
const unsubscribe = refocusSubscriptions.get(callback); const unsubscribe = refocusSubscriptions.get(callback);
unsubscribe && unsubscribe(); unsubscribe?.();
break; break;
} }
default: default:

View File

@@ -47,7 +47,7 @@ export default function createCompatNavigatorFactory<
>( >(
routeConfig: CompatRouteConfig<NavigationPropType>, routeConfig: CompatRouteConfig<NavigationPropType>,
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & { navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
order?: Array<Extract<keyof ParamList, string>>; order?: Extract<keyof ParamList, string>[];
defaultNavigationOptions?: ScreenOptions; defaultNavigationOptions?: ScreenOptions;
navigationOptions?: Record<string, any>; navigationOptions?: Record<string, any>;
} = {} } = {}

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.29...@react-navigation/core@5.0.0-alpha.30) (2020-01-01)
### Bug Fixes
* cleanup transaction even if action wasn't handled ([f462d67](https://github.com/react-navigation/navigation-ex/commit/f462d672708cabfb0477c3a48505bd194ea626fd))
* show error if an action was not handled ([0252bdc](https://github.com/react-navigation/navigation-ex/commit/0252bdc2222ebe7410a0ed593bf03b2bdf5dc7ca))
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.28...@react-navigation/core@5.0.0-alpha.29) (2019-12-19)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.27...@react-navigation/core@5.0.0-alpha.28) (2019-12-16)
### Bug Fixes
* use Partial type for initialParam ([#206](https://github.com/react-navigation/navigation-ex/issues/206)) ([c3d3748](https://github.com/react-navigation/navigation-ex/commit/c3d374814308b0bd6d259099444f0f24593f4d7e))
### Features
* add nested config in deep linking ([#210](https://github.com/react-navigation/navigation-ex/issues/210)) ([8002d51](https://github.com/react-navigation/navigation-ex/commit/8002d5179524a7211c37760a4ed45e8c12af4358)), closes [#154](https://github.com/react-navigation/navigation-ex/issues/154)
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.26...@react-navigation/core@5.0.0-alpha.27) (2019-12-10) # [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.26...@react-navigation/core@5.0.0-alpha.27) (2019-12-10)
**Note:** Version bump only for package @react-navigation/core **Note:** Version bump only for package @react-navigation/core

View File

@@ -6,7 +6,7 @@
"react-native", "react-native",
"react-navigation" "react-navigation"
], ],
"version": "5.0.0-alpha.27", "version": "5.0.0-alpha.30",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -35,15 +35,15 @@
"use-subscription": "^1.3.0" "use-subscription": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.2", "@babel/core": "^7.7.5",
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native-testing-library": "^1.9.1", "react-native-testing-library": "^1.12.0",
"react-test-renderer": "~16.9.0", "react-test-renderer": "~16.9.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "~16.9.0" "react": "~16.9.0"

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-cycle
import { NavigationState, PartialState } from './types'; import { NavigationState, PartialState } from './types';
export type Action = export type Action =

View File

@@ -123,9 +123,11 @@ const Container = React.forwardRef(function NavigationContainer(
isTransactionActiveRef.current = true; isTransactionActiveRef.current = true;
transactionStateRef.current = navigationState; transactionStateRef.current = navigationState;
callback(); try {
callback();
isTransactionActiveRef.current = false; } finally {
isTransactionActiveRef.current = false;
}
return transactionStateRef.current; return transactionStateRef.current;
}); });
@@ -202,7 +204,7 @@ const Container = React.forwardRef(function NavigationContainer(
}, [getStateForRoute]); }, [getStateForRoute]);
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce< ...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any any
>((acc, name) => { >((acc, name) => {
acc[name] = (...args: any[]) => acc[name] = (...args: any[]) =>

View File

@@ -0,0 +1,75 @@
import getActionFromState from '../getActionFromState';
it('gets navigate action from state', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
params: { answer: 42 },
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: {
params: {
answer: 42,
params: {
author: 'jane',
},
screen: 'qux',
},
screen: 'bar',
},
},
type: 'NAVIGATE',
});
});
it('gets reset action from state', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: state,
type: 'RESET_ROOT',
});
});

View File

@@ -127,3 +127,238 @@ it('handles route without param', () => {
it('returns undefined for invalid path', () => { it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined); expect(getStateFromPath('//')).toBe(undefined);
}); });
it('converts path string to initial state with config with nested screens', () => {
expect(
getStateFromPath(
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: 'few',
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
}
)
).toEqual({
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,
},
},
],
},
},
],
},
},
],
},
},
],
});
});
it('converts path string to initial state with config with nested screens and unused configs', () => {
expect(
getStateFromPath('/few/baz/jane?count=10&answer=42&valid=true', {
Foo: {
Foe: 'few',
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
})
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
});
});
it('handles parse in nested object with parse in it', () => {
expect(
getStateFromPath(
'/bar/sweet/apple/few/bis/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: 'few',
},
Bar: 'bar/:type/:fruit',
Baz: {
Bis: {
path: 'bis/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
}
)
).toEqual({
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
},
},
],
});
});
it('handles parse in nested object for second route depth', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo',
Foe: 'foe',
Bar: {
Baz: 'baz',
},
},
})
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
});
});
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo/:id',
parse: {
id: Number,
},
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
Baz: 'baz',
},
},
})
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
});
});

View File

@@ -357,6 +357,8 @@ it("doesn't update state if action wasn't handled", () => {
const onStateChange = jest.fn(); const onStateChange = jest.fn();
const spy = jest.spyOn(console, 'error').mockImplementation();
render( render(
<NavigationContainer onStateChange={onStateChange}> <NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo"> <TestNavigator initialRouteName="foo">
@@ -367,6 +369,12 @@ it("doesn't update state if action wasn't handled", () => {
); );
expect(onStateChange).toBeCalledTimes(0); expect(onStateChange).toBeCalledTimes(0);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
);
spy.mockRestore();
}); });
it('cleans up state when the navigator unmounts', () => { it('cleans up state when the navigator unmounts', () => {

View File

@@ -305,8 +305,7 @@ it("action doesn't bubble if target is specified", () => {
expect(onStateChange).not.toBeCalled(); expect(onStateChange).not.toBeCalled();
}); });
// eslint-disable-next-line jest/expect-expect it('logs error if no navigator handled the action', () => {
it("doesn't crash if no navigator handled the action", () => {
const TestRouter = MockRouter; const TestRouter = MockRouter;
const TestNavigator = (props: any) => { const TestNavigator = (props: any) => {
@@ -366,5 +365,13 @@ it("doesn't crash if no navigator handled the action", () => {
</NavigationContainer> </NavigationContainer>
); );
const spy = jest.spyOn(console, 'error').mockImplementation();
render(element).update(element); render(element).update(element);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
);
spy.mockRestore();
}); });

View File

@@ -19,7 +19,7 @@ it('gets route prop from context', () => {
const Test = () => { const Test = () => {
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>(); const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1); expect(route?.params?.x).toEqual(1);
return null; return null;
}; };

View File

@@ -0,0 +1,67 @@
import { PartialState, NavigationState } from './types';
type NavigateParams = {
screen?: string;
params?: NavigateParams;
};
type Action =
| {
type: 'NAVIGATE';
payload: { name: string; params: NavigateParams };
}
| {
type: 'RESET_ROOT';
payload: PartialState<NavigationState>;
};
export default function getActionFromState(
state: PartialState<NavigationState>
): Action {
let payload: { name: string; params: NavigateParams } | undefined;
if (state.routes.length === 1) {
// Try to construct payload for a `NAVIGATE` action from the state
// This lets us preserve the navigation state and not lose it
let route = state.routes[0];
payload = {
name: route.name,
params: { ...route.params },
};
let current = state.routes[0].state;
let params = payload.params;
while (current) {
if (current.routes.length === 1) {
route = current.routes[0];
params.screen = route.name;
if (route.state) {
params.params = { ...route.params };
params = params.params;
} else {
params.params = route.params;
}
current = route.state;
} else {
payload = undefined;
break;
}
}
}
if (payload) {
return {
type: 'NAVIGATE',
payload,
};
}
return {
type: 'RESET_ROOT',
payload: state,
};
}

View File

@@ -60,7 +60,7 @@ export default function getPathFromState(
Object.entries(route.params).reduce<{ Object.entries(route.params).reduce<{
[key: string]: string; [key: string]: string;
}>((acc, [key, value]) => { }>((acc, [key, value]) => {
acc[key] = config && config[key] ? config[key](value) : String(value); acc[key] = config?.[key] ? config[key](value) : String(value);
return acc; return acc;
}, {}) }, {})
: undefined; : undefined;
@@ -80,6 +80,7 @@ export default function getPathFromState(
if (params && name in params && p.startsWith(':')) { if (params && name in params && p.startsWith(':')) {
const value = params[name]; const value = params[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
delete params[name]; delete params[name];
return encodeURIComponent(value); return encodeURIComponent(value);
} }

View File

@@ -1,11 +1,22 @@
import escape from 'escape-string-regexp'; import escape from 'escape-string-regexp';
import queryString from 'query-string'; import queryString from 'query-string';
import { NavigationState, PartialState } from './types'; import { NavigationState, PartialState, InitialState } from './types';
type ParseConfig = Record<string, (value: string) => any>; type ParseConfig = Record<string, (value: string) => any>;
type Options = { type Options = {
[routeName: string]: string | { path: string; parse?: ParseConfig }; [routeName: string]: string | { path: string; parse?: ParseConfig } | Options;
};
type RouteConfig = {
match: RegExp;
pattern: string;
routeNames: string[];
parse: Record<string, (value: string) => any> | undefined;
};
type ResultState = PartialState<NavigationState> & {
state?: ResultState;
}; };
/** /**
@@ -30,28 +41,11 @@ type Options = {
export default function getStateFromPath( export default function getStateFromPath(
path: string, path: string,
options: Options = {} options: Options = {}
): PartialState<NavigationState> | undefined { ): ResultState | undefined {
// Create a normalized config array which will be easier to use // Create a normalized configs array which will be easier to use
const routeConfig = Object.keys(options).map(key => { const configs = ([] as RouteConfig[]).concat(
const pattern = ...Object.keys(options).map(key => createNormalizedConfigs(key, options))
typeof options[key] === 'string' );
? (options[key] as string)
: (options[key] as { path: string }).path;
// Create a regex from the provided path pattern
// With the pattern, we can match segements containing params and extract them
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
return {
match,
pattern,
routeName: key,
// @ts-ignore
parse: options[key].parse,
};
});
let result: PartialState<NavigationState> | undefined; let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined; let current: PartialState<NavigationState> | undefined;
@@ -62,16 +56,16 @@ export default function getStateFromPath(
.replace(/\?.*/, ''); // Remove query params which we will handle later .replace(/\?.*/, ''); // Remove query params which we will handle later
while (remaining) { while (remaining) {
let routeName; let routeNames;
let params; let params;
// 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 routeConfig) { for (const config of configs) {
const match = remaining.match(config.match); const match = remaining.match(config.match);
// 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) {
routeName = config.routeName; routeNames = config.routeNames;
const paramPatterns = config.pattern const paramPatterns = config.pattern
.split('/') .split('/')
@@ -99,20 +93,54 @@ export default function getStateFromPath(
} }
// If we hadn't matched any segments earlier, use the path as route name // If we hadn't matched any segments earlier, use the path as route name
if (routeName === undefined) { if (routeNames === undefined) {
const segments = remaining.split('/'); const segments = remaining.split('/');
routeName = decodeURIComponent(segments[0]); routeNames = [decodeURIComponent(segments[0])];
segments.shift(); segments.shift();
remaining = segments.join('/'); remaining = segments.join('/');
} }
const state = { let state: InitialState;
routes: [{ name: routeName, params }],
}; if (routeNames.length === 1) {
state = {
routes: [
{ name: routeNames.shift() as string, ...(params && { params }) },
],
};
} else {
state = {
routes: [{ name: routeNames.shift() as string, state: { routes: [] } }],
};
let helper = state.routes[0].state as InitialState;
let routeName;
while ((routeName = routeNames.shift())) {
if (routeNames.length === 0) {
helper.routes.push({
name: routeName,
...(params && { params }),
});
} else {
helper.routes[0] = {
name: routeName,
state: {
routes: [],
},
};
helper = helper.routes[0].state as InitialState;
}
}
}
if (current) { if (current) {
// The state should be nested inside the route we parsed before // The state should be nested inside the deepest route we parsed before
while (current.routes[0].state) {
current = current.routes[0].state;
}
current.routes[0].state = state; current.routes[0].state = state;
} else { } else {
result = state; result = state;
@@ -128,17 +156,20 @@ export default function getStateFromPath(
const query = path.split('?')[1]; const query = path.split('?')[1];
if (query) { if (query) {
while (current.routes[0].state) {
// The query params apply to the deepest route
current = current.routes[0].state;
}
const route = current.routes[0]; const route = current.routes[0];
const params = queryString.parse(query); const params = queryString.parse(query);
const config = options[route.name] const parseFunction = findParseConfigForRoute(route.name, options);
? (options[route.name] as { parse?: ParseConfig }).parse
: undefined;
if (config) { if (parseFunction) {
Object.keys(params).forEach(name => { Object.keys(params).forEach(name => {
if (config[name] && typeof params[name] === 'string') { if (parseFunction[name] && typeof params[name] === 'string') {
params[name] = config[name](params[name] as string); params[name] = parseFunction[name](params[name] as string);
} }
}); });
} }
@@ -148,3 +179,89 @@ export default function getStateFromPath(
return result; return result;
} }
function createNormalizedConfigs(
key: string,
routeConfig: Options,
routeNames: string[] = []
): RouteConfig[] {
const configs = [];
routeNames.push(key);
const value = routeConfig[key];
if (typeof value === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
configs.push(createConfigItem(routeNames, value));
} else if (typeof value === 'object') {
// if an object is specified as the value (e.g. Foo: { ... }),
// it could have config object and optionally nested config
Object.keys(value).forEach(nestedKey => {
if (nestedKey === 'path') {
configs.push(
createConfigItem(
routeNames,
value[nestedKey] as string,
value.parse ? (value.parse as ParseConfig) : undefined
)
);
} else if (nestedKey === 'parse') {
// We handle custom parse function when a `path` is specified (in nestedKey === path)
} else {
// If the name of the key is not `path` or `parse`, it's a nested config for route
// So we need to traverse into it and collect the configs
const result = createNormalizedConfigs(
nestedKey,
routeConfig[key] as Options,
routeNames
);
configs.push(...result);
}
});
}
routeNames.pop();
return configs;
}
function createConfigItem(
routeNames: string[],
pattern: string,
parse?: ParseConfig
): RouteConfig {
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
return {
match,
pattern,
// The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames],
parse,
};
}
function findParseConfigForRoute(
routeName: string,
config: Options
): ParseConfig | undefined {
if (config[routeName]) {
return (config[routeName] as { parse?: ParseConfig }).parse;
}
for (const name in config) {
if (typeof config[name] === 'object') {
const parse = findParseConfigForRoute(routeName, config[name] as Options);
if (parse) {
return parse;
}
}
}
return undefined;
}

View File

@@ -17,5 +17,6 @@ export { default as useIsFocused } from './useIsFocused';
export { default as getStateFromPath } from './getStateFromPath'; export { default as getStateFromPath } from './getStateFromPath';
export { default as getPathFromState } from './getPathFromState'; export { default as getPathFromState } from './getPathFromState';
export { default as getActionFromState } from './getActionFromState';
export * from './types'; export * from './types';

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions'; import * as CommonActions from './CommonActions';
import * as React from 'react'; import * as React from 'react';
@@ -20,9 +19,9 @@ export type NavigationState = {
/** /**
* List of rendered routes. * List of rendered routes.
*/ */
routes: Array< routes: (Route<string> & {
Route<string> & { state?: NavigationState | PartialState<NavigationState> } state?: NavigationState | PartialState<NavigationState>;
>; })[];
/** /**
* Custom type for the state, whether it's for tab, stack, drawer etc. * Custom type for the state, whether it's for tab, stack, drawer etc.
* During rehydration, the state will be discarded if type doesn't match with router type. * During rehydration, the state will be discarded if type doesn't match with router type.
@@ -38,7 +37,7 @@ export type NavigationState = {
export type InitialState = Partial< export type InitialState = Partial<
Omit<NavigationState, 'stale' | 'routes'> Omit<NavigationState, 'stale' | 'routes'>
> & { > & {
routes: Array<Omit<Route<string>, 'key'> & { state?: InitialState }>; routes: (Omit<Route<string>, 'key'> & { state?: InitialState })[];
}; };
export type PartialState<State extends NavigationState> = Partial< export type PartialState<State extends NavigationState> = Partial<
@@ -46,9 +45,10 @@ export type PartialState<State extends NavigationState> = Partial<
> & { > & {
stale?: true; stale?: true;
type?: string; type?: string;
routes: Array< routes: (Omit<Route<string>, 'key'> & {
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState } key?: string;
>; state?: InitialState;
})[];
}; };
export type Route<RouteName extends string> = { export type Route<RouteName extends string> = {
@@ -71,6 +71,10 @@ export type NavigationAction = {
* Type of the action (e.g. `NAVIGATE`) * Type of the action (e.g. `NAVIGATE`)
*/ */
type: string; type: string;
/**
* Additional data for the action
*/
payload?: object;
/** /**
* Key of the route which dispatched this action. * Key of the route which dispatched this action.
*/ */
@@ -533,7 +537,7 @@ export type RouteConfig<
/** /**
* Initial params object for the route. * Initial params object for the route.
*/ */
initialParams?: ParamList[RouteName]; initialParams?: Partial<ParamList[RouteName]>;
} & ( } & (
| { | {
/** /**

View File

@@ -44,16 +44,15 @@ export default function useDevTools({ name, reset, state }: Options) {
const devTools = devToolsRef.current; const devTools = devToolsRef.current;
const lastStateRef = React.useRef<State>(state); const lastStateRef = React.useRef<State>(state);
const actions = React.useRef<Array<NavigationAction | string>>([]); const actions = React.useRef<(NavigationAction | string)[]>([]);
React.useEffect(() => { React.useEffect(() => {
devTools && devTools.init(lastStateRef.current); devTools?.init(lastStateRef.current);
}, [devTools]); }, [devTools]);
React.useEffect( React.useEffect(
() => () =>
devTools && devTools?.subscribe(message => {
devTools.subscribe(message => {
if (message.type === 'DISPATCH' && message.state) { if (message.type === 'DISPATCH' && message.state) {
reset(JSON.parse(message.state)); reset(JSON.parse(message.state));
} }

View File

@@ -5,7 +5,7 @@ export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
create: (target: string) => EventConsumer<Record<string, any>>; create: (target: string) => EventConsumer<Record<string, any>>;
}; };
type Listeners = Array<(data: any) => void>; type Listeners = ((data: any) => void)[];
/** /**
* Hook to manage the event system used by the navigator to notify screens of various events. * Hook to manage the event system used by the navigator to notify screens of various events.
@@ -69,7 +69,7 @@ export default function useEventEmitter(): NavigationEventEmitter {
}, },
}; };
callbacks && callbacks.forEach(cb => cb(event)); callbacks?.forEach(cb => cb(event));
return event; return event;
}, },

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import useNavigation from './useNavigation'; import useNavigation from './useNavigation';
type EffectCallback = (() => void) | (() => () => void); type EffectCallback = (() => undefined) | (() => () => void);
/** /**
* Hook to run an effect in a focused screen, similar to `React.useEffect`. * Hook to run an effect in a focused screen, similar to `React.useEffect`.
@@ -15,7 +15,7 @@ export default function useFocusEffect(callback: EffectCallback) {
React.useEffect(() => { React.useEffect(() => {
let isFocused = false; let isFocused = false;
let cleanup: (() => void) | void; let cleanup: (() => void) | undefined;
// We need to run the effect on intial render/dep changes if the screen is focused // We need to run the effect on intial render/dep changes if the screen is focused
if (navigation.isFocused()) { if (navigation.isFocused()) {
@@ -30,19 +30,19 @@ export default function useFocusEffect(callback: EffectCallback) {
return; return;
} }
cleanup && cleanup(); cleanup?.();
cleanup = callback(); cleanup = callback();
isFocused = true; isFocused = true;
}); });
const unsubscribeBlur = navigation.addListener('blur', () => { const unsubscribeBlur = navigation.addListener('blur', () => {
cleanup && cleanup(); cleanup?.();
cleanup = undefined; cleanup = undefined;
isFocused = false; isFocused = false;
}); });
return () => { return () => {
cleanup && cleanup(); cleanup?.();
unsubscribeFocus(); unsubscribeFocus();
unsubscribeBlur(); unsubscribeBlur();
}; };

View File

@@ -21,8 +21,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
// Coz the child screen can't be focused if the parent screen is out of fcous // Coz the child screen can't be focused if the parent screen is out of fcous
React.useEffect( React.useEffect(
() => () =>
navigation && navigation?.addListener('focus', () =>
navigation.addListener('focus', () =>
emitter.emit({ type: 'focus', target: currentFocusedKey }) emitter.emit({ type: 'focus', target: currentFocusedKey })
), ),
[currentFocusedKey, emitter, navigation] [currentFocusedKey, emitter, navigation]
@@ -30,8 +29,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
React.useEffect( React.useEffect(
() => () =>
navigation && navigation?.addListener('blur', () =>
navigation.addListener('blur', () =>
emitter.emit({ type: 'blur', target: currentFocusedKey }) emitter.emit({ type: 'blur', target: currentFocusedKey })
), ),
[currentFocusedKey, emitter, navigation] [currentFocusedKey, emitter, navigation]

View File

@@ -38,7 +38,7 @@ export default function useFocusedListenersChildrenAdapter({
[focusedListeners, navigation] [focusedListeners, navigation]
); );
React.useEffect(() => addFocusedListener && addFocusedListener(listener), [ React.useEffect(() => addFocusedListener?.(listener), [
addFocusedListener, addFocusedListener,
listener, listener,
]); ]);

View File

@@ -117,7 +117,7 @@ export default function useNavigationBuilder<
const { current: router } = React.useRef<Router<State, any>>( const { current: router } = React.useRef<Router<State, any>>(
createRouter({ createRouter({
...((rest as unknown) as RouterOptions), ...((rest as unknown) as RouterOptions),
...(route && route.params && typeof route.params.screen === 'string' ...(route?.params && typeof route.params.screen === 'string'
? { initialRouteName: route.params.screen } ? { initialRouteName: route.params.screen }
: null), : null),
}) })
@@ -141,7 +141,7 @@ export default function useNavigationBuilder<
(acc, curr) => { (acc, curr) => {
const { initialParams } = screens[curr]; const { initialParams } = screens[curr];
const initialParamsFromParams = const initialParamsFromParams =
route && route.params && route.params.screen === curr route?.params && route.params.screen === curr
? route.params.params ? route.params.params
: undefined; : undefined;

View File

@@ -42,13 +42,22 @@ export default function useNavigationHelpers<
const { performTransaction } = React.useContext(NavigationStateContext); const { performTransaction } = React.useContext(NavigationStateContext);
return React.useMemo(() => { return React.useMemo(() => {
const dispatch = (action: Action | ((state: State) => Action)) => const dispatch = (action: Action | ((state: State) => Action)) => {
performTransaction(() => { performTransaction(() => {
const payload = const payload =
typeof action === 'function' ? action(getState()) : action; typeof action === 'function' ? action(getState()) : action;
onAction(payload); const handled = onAction(payload);
if (!handled && process.env.NODE_ENV !== 'production') {
console.error(
`The action '${payload.type}' with payload '${JSON.stringify(
payload.payload
)}' was not handled by any navigator.`
);
}
}); });
};
const actions = { const actions = {
...router.actionCreators, ...router.actionCreators,
@@ -81,7 +90,7 @@ export default function useNavigationHelpers<
routeNames: state.routeNames, routeNames: state.routeNames,
routeParamList: {}, routeParamList: {},
}) !== null || }) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) || parentNavigationHelpers?.canGoBack() ||
false false
); );
}, },

View File

@@ -130,10 +130,10 @@ export default function useOnAction({
] ]
); );
React.useEffect( React.useEffect(() => addActionListenerParent?.(onAction), [
() => addActionListenerParent && addActionListenerParent(onAction), addActionListenerParent,
[addActionListenerParent, onAction] onAction,
); ]);
return onAction; return onAction;
} }

View File

@@ -26,6 +26,6 @@ export default function useOnGetState({
}, [getState, getStateForRoute]); }, [getState, getStateForRoute]);
React.useEffect(() => { React.useEffect(() => {
return addStateGetter && addStateGetter(key, getRehydratedState); return addStateGetter?.(key, getRehydratedState);
}, [addStateGetter, getRehydratedState, key]); }, [addStateGetter, getRehydratedState, key]);
} }

View File

@@ -3,6 +3,68 @@
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.0.0-alpha.34](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.33...@react-navigation/drawer@5.0.0-alpha.34) (2020-01-05)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.32...@react-navigation/drawer@5.0.0-alpha.33) (2020-01-03)
### Bug Fixes
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
### Features
* add interaction handle to drawer ([#239](https://github.com/react-navigation/navigation-ex/issues/239)) ([fa4411a](https://github.com/react-navigation/navigation-ex/commit/fa4411a14dc4aae568794e4b884088e3276a2876))
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.31...@react-navigation/drawer@5.0.0-alpha.32) (2020-01-03)
### Bug Fixes
* pass backBehavior to the router in drawer. fixes [#230](https://github.com/react-navigation/navigation-ex/issues/230) ([3cd1aed](https://github.com/react-navigation/navigation-ex/commit/3cd1aedcf490a4c7962b2d36873d714637f3b9b0))
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.30...@react-navigation/drawer@5.0.0-alpha.31) (2020-01-01)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.29...@react-navigation/drawer@5.0.0-alpha.30) (2019-12-19)
### Bug Fixes
* set screen background in drawer from theme ([0635365](https://github.com/react-navigation/navigation-ex/commit/0635365483bf5ac38e75191b4ba8f52cf6d73896))
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.28...@react-navigation/drawer@5.0.0-alpha.29) (2019-12-16)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.27...@react-navigation/drawer@5.0.0-alpha.28) (2019-12-14) # [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.27...@react-navigation/drawer@5.0.0-alpha.28) (2019-12-14)

View File

@@ -11,7 +11,7 @@
"material", "material",
"drawer" "drawer"
], ],
"version": "5.0.0-alpha.28", "version": "5.0.0-alpha.34",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,21 +34,22 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16", "@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2" "color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.0", "react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0", "react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0", "react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.11", "react-native-screens": "^2.0.0-alpha.19",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",
@@ -56,7 +57,7 @@
"react-native": "*", "react-native": "*",
"react-native-gesture-handler": "^1.0.0", "react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0", "react-native-reanimated": "^1.0.0",
"react-native-safe-area-context": "^0.3.6", "react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0" "react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {

View File

@@ -19,10 +19,11 @@ import {
type Props = DefaultNavigatorOptions<DrawerNavigationOptions> & type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
DrawerRouterOptions & DrawerRouterOptions &
Partial<DrawerNavigationConfig>; DrawerNavigationConfig;
function DrawerNavigator({ function DrawerNavigator({
initialRouteName, initialRouteName,
backBehavior,
children, children,
screenOptions, screenOptions,
...rest ...rest
@@ -34,6 +35,7 @@ function DrawerNavigator({
DrawerNavigationEventMap DrawerNavigationEventMap
>(DrawerRouter, { >(DrawerRouter, {
initialRouteName, initialRouteName,
backBehavior,
children, children,
screenOptions, screenOptions,
}); });

View File

@@ -20,14 +20,14 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/** /**
* Position of the drawer on the screen. Defaults to `left`. * Position of the drawer on the screen. Defaults to `left`.
*/ */
drawerPosition: 'left' | 'right'; drawerPosition?: 'left' | 'right';
/** /**
* Type of the drawer. It determines how the drawer looks and animates. * Type of the drawer. It determines how the drawer looks and animates.
* - `front`: Traditional drawer which covers the screen with a overlay behind it. * - `front`: Traditional drawer which covers the screen with a overlay behind it.
* - `back`: The drawer is revealed behind the screen on swipe. * - `back`: The drawer is revealed behind the screen on swipe.
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer. * - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
*/ */
drawerType: 'front' | 'back' | 'slide'; drawerType?: 'front' | 'back' | 'slide';
/** /**
* How far from the edge of the screen the swipe gesture should activate. * How far from the edge of the screen the swipe gesture should activate.
*/ */
@@ -35,12 +35,12 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/** /**
* Whether the statusbar should be hidden when the drawer is pulled or opens, * Whether the statusbar should be hidden when the drawer is pulled or opens,
*/ */
hideStatusBar: boolean; hideStatusBar?: boolean;
/** /**
* Whether the keyboard should be dismissed when the swipe gesture begins. * Whether the keyboard should be dismissed when the swipe gesture begins.
* Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling. * Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling.
*/ */
keyboardDismissMode: 'on-drag' | 'none'; keyboardDismissMode?: 'on-drag' | 'none';
/** /**
* Minimum swipe distance threshold that should activate opening the drawer. * Minimum swipe distance threshold that should activate opening the drawer.
*/ */
@@ -53,7 +53,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/** /**
* Animation of the statusbar when hiding it. use in combination with `hideStatusBar`. * Animation of the statusbar when hiding it. use in combination with `hideStatusBar`.
*/ */
statusBarAnimation: 'slide' | 'none' | 'fade'; statusBarAnimation?: 'slide' | 'none' | 'fade';
/** /**
* Props to pass to the underlying pan gesture handler. * Props to pass to the underlying pan gesture handler.
*/ */
@@ -62,7 +62,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
* Whether the screens should render the first time they are accessed. Defaults to `true`. * Whether the screens should render the first time they are accessed. Defaults to `true`.
* Set it to `false` if you want to render all screens on initial render. * Set it to `false` if you want to render all screens on initial render.
*/ */
lazy: boolean; lazy?: boolean;
/** /**
* Whether a screen should be unmounted when navigating away from it. * Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`. * Defaults to `false`.
@@ -72,7 +72,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
* Function that returns React element to render as the content of the drawer, for example, navigation items. * Function that returns React element to render as the content of the drawer, for example, navigation items.
* Defaults to `DrawerContent`. * Defaults to `DrawerContent`.
*/ */
drawerContent: (props: DrawerContentComponentProps<T>) => React.ReactNode; drawerContent?: (props: DrawerContentComponentProps<T>) => React.ReactNode;
/** /**
* Options for the content component which will be passed as props. * Options for the content component which will be passed as props.
*/ */

View File

@@ -9,6 +9,7 @@ import {
StatusBar, StatusBar,
StyleProp, StyleProp,
View, View,
InteractionManager,
} from 'react-native'; } from 'react-native';
import { import {
PanGestureHandler, PanGestureHandler,
@@ -161,9 +162,24 @@ export default class DrawerView extends React.PureComponent<Props> {
componentWillUnmount() { componentWillUnmount() {
this.toggleStatusBar(false); this.toggleStatusBar(false);
this.handleEndInteraction();
} }
private handleEndInteraction = () => {
if (this.interactionHandle !== undefined) {
InteractionManager.clearInteractionHandle(this.interactionHandle);
this.interactionHandle = undefined;
}
};
private handleStartInteraction = () => {
if (this.interactionHandle === undefined) {
this.interactionHandle = InteractionManager.createInteractionHandle();
}
};
private clock = new Clock(); private clock = new Clock();
private interactionHandle: number | undefined;
private isDrawerTypeFront = new Value<Binary>( private isDrawerTypeFront = new Value<Binary>(
this.props.drawerType === 'front' ? TRUE : FALSE this.props.drawerType === 'front' ? TRUE : FALSE
@@ -277,6 +293,7 @@ export default class DrawerView extends React.PureComponent<Props> {
set(state.velocity, this.velocityX), set(state.velocity, this.velocityX),
set(this.isOpen, isOpen), set(this.isOpen, isOpen),
startClock(this.clock), startClock(this.clock),
call([], this.handleStartInteraction),
set(this.manuallyTriggerSpring, FALSE), set(this.manuallyTriggerSpring, FALSE),
]), ]),
spring(this.clock, state, { ...SPRING_CONFIG, toValue }), spring(this.clock, state, { ...SPRING_CONFIG, toValue }),
@@ -288,8 +305,9 @@ export default class DrawerView extends React.PureComponent<Props> {
set(this.offsetX, 0), set(this.offsetX, 0),
// When the animation finishes, stop the clock // When the animation finishes, stop the clock
stopClock(this.clock), stopClock(this.clock),
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => { call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value); const open = Boolean(value);
this.handleEndInteraction();
if (open !== this.props.open) { if (open !== this.props.open) {
// Sync drawer's state after animation finished // Sync drawer's state after animation finished
@@ -304,7 +322,7 @@ export default class DrawerView extends React.PureComponent<Props> {
private dragX = block([ private dragX = block([
onChange( onChange(
this.isOpen, this.isOpen,
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => { call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value); const open = Boolean(value);
this.currentOpenValue = open; this.currentOpenValue = open;
@@ -344,7 +362,7 @@ export default class DrawerView extends React.PureComponent<Props> {
// Listen to updates for this value only when it changes // Listen to updates for this value only when it changes
// Without `onChange`, this will fire even if the value didn't change // Without `onChange`, this will fire even if the value didn't change
// We don't want to call the listeners if the value didn't change // We don't want to call the listeners if the value didn't change
call([this.isSwiping], ([value]: ReadonlyArray<Binary>) => { call([this.isSwiping], ([value]: readonly Binary[]) => {
const { keyboardDismissMode } = this.props; const { keyboardDismissMode } = this.props;
if (value === TRUE) { if (value === TRUE) {
@@ -358,6 +376,13 @@ export default class DrawerView extends React.PureComponent<Props> {
} }
}) })
), ),
onChange(
this.gestureState,
cond(
eq(this.gestureState, State.ACTIVE),
call([], this.handleStartInteraction)
)
),
cond( cond(
eq(this.gestureState, State.ACTIVE), eq(this.gestureState, State.ACTIVE),
[ [

View File

@@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { ScrollView, StyleSheet, ScrollViewProps } from 'react-native'; import { ScrollView, StyleSheet, ScrollViewProps } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context'; import { useSafeArea } from 'react-native-safe-area-context';
import { useTheme } from '@react-navigation/native';
type Props = ScrollViewProps & { type Props = ScrollViewProps & {
drawerPosition: 'left' | 'right'; drawerPosition: 'left' | 'right';
@@ -16,7 +15,6 @@ export default function DrawerContentScrollView({
...rest ...rest
}: Props) { }: Props) {
const insets = useSafeArea(); const insets = useSafeArea();
const { colors } = useTheme();
return ( return (
<ScrollView <ScrollView
@@ -29,7 +27,7 @@ export default function DrawerContentScrollView({
}, },
contentContainerStyle, contentContainerStyle,
]} ]}
style={[styles.container, { backgroundColor: colors.card }, style]} style={[styles.container, style]}
> >
{children} {children}
</ScrollView> </ScrollView>

View File

@@ -13,6 +13,7 @@ import {
DrawerNavigationState, DrawerNavigationState,
DrawerActions, DrawerActions,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { useTheme } from '@react-navigation/native';
import DrawerGestureContext from '../utils/DrawerGestureContext'; import DrawerGestureContext from '../utils/DrawerGestureContext';
import SafeAreaProviderCompat from './SafeAreaProviderCompat'; import SafeAreaProviderCompat from './SafeAreaProviderCompat';
@@ -26,16 +27,10 @@ import {
DrawerContentComponentProps, DrawerContentComponentProps,
} from '../types'; } from '../types';
type Props = Omit<DrawerNavigationConfig, 'overlayColor'> & { type Props = DrawerNavigationConfig & {
state: DrawerNavigationState; state: DrawerNavigationState;
navigation: DrawerNavigationHelpers; navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap; descriptors: DrawerDescriptorMap;
overlayColor: string;
};
type State = {
loaded: number[];
drawerWidth: number;
}; };
const getDefaultDrawerWidth = ({ const getDefaultDrawerWidth = ({
@@ -62,49 +57,52 @@ const getDefaultDrawerWidth = ({
/** /**
* Component that renders the drawer. * Component that renders the drawer.
*/ */
export default class DrawerView extends React.PureComponent<Props, State> { export default function DrawerView({
static defaultProps = { state,
lazy: true, navigation,
drawerContent: (props: DrawerContentComponentProps) => ( descriptors,
<DrawerContent {...props} /> lazy = true,
), drawerContent = (props: DrawerContentComponentProps) => (
drawerPosition: I18nManager.isRTL ? 'right' : 'left', <DrawerContent {...props} />
keyboardDismissMode: 'on-drag', ),
overlayColor: 'rgba(0, 0, 0, 0.5)', drawerPosition = I18nManager.isRTL ? 'right' : 'left',
drawerType: 'front', keyboardDismissMode = 'on-drag',
hideStatusBar: false, overlayColor = 'rgba(0, 0, 0, 0.5)',
statusBarAnimation: 'slide', drawerType = 'front',
}; hideStatusBar = false,
statusBarAnimation = 'slide',
drawerContentOptions,
drawerStyle,
edgeWidth,
gestureHandlerProps,
minSwipeDistance,
sceneContainerStyle,
unmountInactiveScreens,
}: Props) {
const [loaded, setLoaded] = React.useState([state.index]);
const [drawerWidth, setDrawerWidth] = React.useState(() =>
getDefaultDrawerWidth(Dimensions.get('window'))
);
static getDerivedStateFromProps(nextProps: Props, prevState: State) { const drawerGestureRef = React.useRef<PanGestureHandler>(null);
const { index } = nextProps.state;
return { const { colors } = useTheme();
// Set the current tab to be loaded if it was not loaded before
loaded: prevState.loaded.includes(index) React.useEffect(() => {
? prevState.loaded const updateWidth = ({ window }: { window: ScaledSize }) => {
: [...prevState.loaded, index], setDrawerWidth(getDefaultDrawerWidth(window));
}; };
Dimensions.addEventListener('change', updateWidth);
return () => Dimensions.removeEventListener('change', updateWidth);
}, []);
if (!loaded.includes(state.index)) {
setLoaded([...loaded, state.index]);
} }
state: State = { const handleDrawerOpen = () => {
loaded: [this.props.state.index],
drawerWidth: getDefaultDrawerWidth(Dimensions.get('window')),
};
componentDidMount() {
Dimensions.addEventListener('change', this.updateWidth);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.updateWidth);
}
private drawerGestureRef = React.createRef<PanGestureHandler>();
private handleDrawerOpen = () => {
const { state, navigation } = this.props;
navigation.dispatch({ navigation.dispatch({
...DrawerActions.openDrawer(), ...DrawerActions.openDrawer(),
target: state.key, target: state.key,
@@ -113,9 +111,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerOpen' }); navigation.emit({ type: 'drawerOpen' });
}; };
private handleDrawerClose = () => { const handleDrawerClose = () => {
const { state, navigation } = this.props;
navigation.dispatch({ navigation.dispatch({
...DrawerActions.closeDrawer(), ...DrawerActions.closeDrawer(),
target: state.key, target: state.key,
@@ -124,24 +120,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerClose' }); navigation.emit({ type: 'drawerClose' });
}; };
private updateWidth = ({ window }: { window: ScaledSize }) => { const renderNavigationView = ({ progress }: any) => {
const drawerWidth = getDefaultDrawerWidth(window);
if (this.state.drawerWidth !== drawerWidth) {
this.setState({ drawerWidth });
}
};
private renderNavigationView = ({ progress }: any) => {
const {
state,
navigation,
descriptors,
drawerPosition,
drawerContent,
drawerContentOptions,
} = this.props;
return drawerContent({ return drawerContent({
...drawerContentOptions, ...drawerContentOptions,
progress: progress, progress: progress,
@@ -152,11 +131,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
}); });
}; };
private renderContent = () => { const renderContent = () => {
let { lazy, state, descriptors, unmountInactiveScreens } = this.props;
const { loaded } = this.state;
return ( return (
<ScreenContainer style={styles.content}> <ScreenContainer style={styles.content}>
{state.routes.map((route, index) => { {state.routes.map((route, index) => {
@@ -164,7 +139,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
return null; return null;
} }
if (lazy && !loaded.includes(index)) { if (lazy && !loaded.includes(index) && index !== state.index) {
// Don't render a screen if we've never navigated to it // Don't render a screen if we've never navigated to it
return null; return null;
} }
@@ -186,58 +161,45 @@ export default class DrawerView extends React.PureComponent<Props, State> {
); );
}; };
private setDrawerGestureRef = (ref: PanGestureHandler | null) => { const activeKey = state.routes[state.index].key;
// @ts-ignore const { gestureEnabled } = descriptors[activeKey].options;
this.drawerGestureRef.current = ref;
};
render() { return (
const { <SafeAreaProviderCompat>
state, <DrawerGestureContext.Provider value={drawerGestureRef}>
descriptors, <Drawer
drawerType, open={state.isDrawerOpen}
drawerPosition, gestureEnabled={gestureEnabled !== false}
overlayColor, onOpen={handleDrawerOpen}
sceneContainerStyle, onClose={handleDrawerClose}
drawerStyle, onGestureRef={ref => {
edgeWidth, // @ts-ignore
minSwipeDistance, drawerGestureRef.current = ref;
hideStatusBar, }}
statusBarAnimation, gestureHandlerProps={gestureHandlerProps}
gestureHandlerProps, drawerType={drawerType}
} = this.props; drawerPosition={drawerPosition}
sceneContainerStyle={[
const { drawerWidth } = this.state; { backgroundColor: colors.background },
sceneContainerStyle,
const activeKey = state.routes[state.index].key; ]}
const { gestureEnabled } = descriptors[activeKey].options; drawerStyle={[
{ width: drawerWidth, backgroundColor: colors.card },
return ( drawerStyle,
<SafeAreaProviderCompat> ]}
<DrawerGestureContext.Provider value={this.drawerGestureRef}> overlayStyle={{ backgroundColor: overlayColor }}
<Drawer swipeEdgeWidth={edgeWidth}
open={state.isDrawerOpen} swipeDistanceThreshold={minSwipeDistance}
gestureEnabled={gestureEnabled !== false} hideStatusBar={hideStatusBar}
onOpen={this.handleDrawerOpen} statusBarAnimation={statusBarAnimation}
onClose={this.handleDrawerClose} renderDrawerContent={renderNavigationView}
onGestureRef={this.setDrawerGestureRef} renderSceneContent={renderContent}
gestureHandlerProps={gestureHandlerProps} keyboardDismissMode={keyboardDismissMode}
drawerType={drawerType} drawerPostion={drawerPosition}
drawerPosition={drawerPosition} />
sceneContainerStyle={sceneContainerStyle} </DrawerGestureContext.Provider>
drawerStyle={[{ width: drawerWidth }, drawerStyle]} </SafeAreaProviderCompat>
overlayStyle={{ backgroundColor: overlayColor }} );
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@@ -13,7 +13,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
if (screensEnabled && screensEnabled()) { if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore // @ts-ignore

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider, SafeAreaProvider,
SafeAreaConsumer, SafeAreaConsumer,
} from 'react-native-safe-area-context'; } from 'react-native-safe-area-context';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
const initialSafeAreaInsets = {
top: getStatusBarHeight(true),
bottom: getBottomSpace(),
right: 0,
left: 0,
};
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children; return children;
} }
return <SafeAreaProvider>{children}</SafeAreaProvider>; return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}} }}
</SafeAreaConsumer> </SafeAreaConsumer>
); );

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.28...@react-navigation/material-bottom-tabs@5.0.0-alpha.29) (2020-01-05)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.27...@react-navigation/material-bottom-tabs@5.0.0-alpha.28) (2020-01-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.26...@react-navigation/material-bottom-tabs@5.0.0-alpha.27) (2019-12-19)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.25...@react-navigation/material-bottom-tabs@5.0.0-alpha.26) (2019-12-16)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.24...@react-navigation/material-bottom-tabs@5.0.0-alpha.25) (2019-12-14) # [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.24...@react-navigation/material-bottom-tabs@5.0.0-alpha.25) (2019-12-14)

View File

@@ -11,7 +11,7 @@
"material", "material",
"tab" "tab"
], ],
"version": "5.0.0-alpha.25", "version": "5.0.0-alpha.29",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,19 +34,19 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16" "@react-navigation/routers": "^5.0.0-alpha.20"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"@types/react-native-vector-icons": "^6.4.4", "@types/react-native-vector-icons": "^6.4.4",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-paper": "^3.2.1", "react-native-paper": "^3.3.0",
"react-native-vector-icons": "^6.6.0", "react-native-vector-icons": "^6.6.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.26...@react-navigation/material-top-tabs@5.0.0-alpha.27) (2020-01-05)
### Features
* add support for pager component ([dcc5f99](https://github.com/react-navigation/navigation-ex/commit/dcc5f99ecd495ad1903c9e99884e0d4e9b3994f1))
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.25...@react-navigation/material-top-tabs@5.0.0-alpha.26) (2020-01-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.24...@react-navigation/material-top-tabs@5.0.0-alpha.25) (2019-12-19)
### Bug Fixes
* fix backgroundColor in sceneContainerStyle overriden by theme ([ebd145a](https://github.com/react-navigation/navigation-ex/commit/ebd145a09d80f119070a14a8d4940b5757b5e7fb)), closes [#215](https://github.com/react-navigation/navigation-ex/issues/215)
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.23...@react-navigation/material-top-tabs@5.0.0-alpha.24) (2019-12-16)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.22...@react-navigation/material-top-tabs@5.0.0-alpha.23) (2019-12-14) # [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.22...@react-navigation/material-top-tabs@5.0.0-alpha.23) (2019-12-14)

View File

@@ -11,7 +11,7 @@
"material", "material",
"tab" "tab"
], ],
"version": "5.0.0-alpha.23", "version": "5.0.0-alpha.27",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,20 +34,20 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16", "@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2" "color": "^3.1.2"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.0", "react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0", "react-native-reanimated": "^1.4.0",
"react-native-tab-view": "^2.11.0", "react-native-tab-view": "^2.11.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",

View File

@@ -113,9 +113,15 @@ export type MaterialTopTabNavigationConfig = Partial<
| 'onSwipeEnd' | 'onSwipeEnd'
| 'renderScene' | 'renderScene'
| 'renderTabBar' | 'renderTabBar'
| 'renderPager'
| 'renderLazyPlaceholder' | 'renderLazyPlaceholder'
> >
> & { > & {
/**
* Function that returns a React element to use as the pager.
* The pager handles swipe gestures and page switching.
*/
pager?: React.ComponentProps<typeof TabView>['renderPager'];
/** /**
* Function that returns a React element to render for routes that haven't been rendered yet. * Function that returns a React element to render for routes that haven't been rendered yet.
* Receives an object containing the route as the prop. * Receives an object containing the route as the prop.

View File

@@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View } from 'react-native';
import { TabView, SceneRendererProps } from 'react-native-tab-view'; import { TabView, SceneRendererProps } from 'react-native-tab-view';
import { Route, useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
import { TabNavigationState, TabActions } from '@react-navigation/routers'; import { TabNavigationState, TabActions } from '@react-navigation/routers';
import MaterialTopTabBar from './MaterialTopTabBar'; import MaterialTopTabBar from './MaterialTopTabBar';
@@ -16,49 +15,28 @@ type Props = MaterialTopTabNavigationConfig & {
state: TabNavigationState; state: TabNavigationState;
navigation: MaterialTopTabNavigationHelpers; navigation: MaterialTopTabNavigationHelpers;
descriptors: MaterialTopTabDescriptorMap; descriptors: MaterialTopTabDescriptorMap;
tabBarPosition: 'top' | 'bottom'; tabBarPosition?: 'top' | 'bottom';
}; };
function SceneContent({ children }: { children: React.ReactNode }) { export default function MaterialTopTabView({
pager,
lazyPlaceholder,
tabBar = (props: MaterialTopTabBarProps) => <MaterialTopTabBar {...props} />,
tabBarOptions,
state,
navigation,
descriptors,
sceneContainerStyle,
...rest
}: Props) {
const { colors } = useTheme(); const { colors } = useTheme();
return ( const renderTabBar = (props: SceneRendererProps) => {
<View style={{ flex: 1, backgroundColor: colors.background }}>
{children}
</View>
);
}
export default class MaterialTopTabView extends React.PureComponent<Props> {
static defaultProps = {
tabBarPosition: 'top',
};
private renderLazyPlaceholder = (props: { route: Route<string> }) => {
const { lazyPlaceholder } = this.props;
if (lazyPlaceholder != null) {
return lazyPlaceholder(props);
}
return null;
};
private renderTabBar = (props: SceneRendererProps) => {
const { state, descriptors } = this.props;
const route = state.routes[state.index]; const route = state.routes[state.index];
const options = descriptors[route.key].options; const options = descriptors[route.key].options;
const tabBarVisible = options.tabBarVisible !== false; const tabBarVisible = options.tabBarVisible !== false;
const {
navigation,
tabBar = (props: MaterialTopTabBarProps) => (
<MaterialTopTabBar {...props} />
),
tabBarOptions,
} = this.props;
if (tabBarVisible === false) { if (tabBarVisible === false) {
return null; return null;
} }
@@ -72,47 +50,26 @@ export default class MaterialTopTabView extends React.PureComponent<Props> {
}); });
}; };
private handleSwipeStart = () => return (
this.props.navigation.emit({ <TabView
type: 'swipeStart', {...rest}
}); onIndexChange={index =>
navigation.dispatch({
private handleSwipeEnd = () => ...TabActions.jumpTo(state.routes[index].name),
this.props.navigation.emit({ target: state.key,
type: 'swipeEnd', })
}); }
renderScene={({ route }) => descriptors[route.key].render()}
render() { navigationState={state}
const { renderTabBar={renderTabBar}
/* eslint-disable @typescript-eslint/no-unused-vars */ renderPager={pager}
lazyPlaceholder, renderLazyPlaceholder={lazyPlaceholder}
tabBar, onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
tabBarOptions, onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
/* eslint-enable @typescript-eslint/no-unused-vars */ sceneContainerStyle={[
state, { backgroundColor: colors.background },
navigation, sceneContainerStyle,
descriptors, ]}
...rest />
} = this.props; );
return (
<TabView
{...rest}
onIndexChange={index =>
navigation.dispatch({
...TabActions.jumpTo(state.routes[index].name),
target: state.key,
})
}
renderScene={({ route }) => (
<SceneContent>{descriptors[route.key].render()}</SceneContent>
)}
navigationState={state}
renderTabBar={this.renderTabBar}
renderLazyPlaceholder={this.renderLazyPlaceholder}
onSwipeStart={this.handleSwipeStart}
onSwipeEnd={this.handleSwipeEnd}
/>
);
}
} }

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.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.21...@react-navigation/native-stack@5.0.0-alpha.22) (2020-01-05)
**Note:** Version bump only for package @react-navigation/native-stack
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.20...@react-navigation/native-stack@5.0.0-alpha.21) (2020-01-03)
**Note:** Version bump only for package @react-navigation/native-stack
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.19...@react-navigation/native-stack@5.0.0-alpha.20) (2020-01-01)
**Note:** Version bump only for package @react-navigation/native-stack
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.18...@react-navigation/native-stack@5.0.0-alpha.19) (2019-12-19)
**Note:** Version bump only for package @react-navigation/native-stack
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.17...@react-navigation/native-stack@5.0.0-alpha.18) (2019-12-16)
**Note:** Version bump only for package @react-navigation/native-stack
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.16...@react-navigation/native-stack@5.0.0-alpha.17) (2019-12-14) # [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.16...@react-navigation/native-stack@5.0.0-alpha.17) (2019-12-14)

View File

@@ -2,7 +2,7 @@
Stack navigator for React Native using native primitives for navigation. Uses [`react-native-screens`](https://github.com/kmagiera/react-native-screens) under the hood. Stack navigator for React Native using native primitives for navigation. Uses [`react-native-screens`](https://github.com/kmagiera/react-native-screens) under the hood.
Expo is currently not supported as it includes an older version of `react-native-screens`. Expo SDK 35 and lower is not supported as it includes an older version of `react-native-screens`.
## Installation ## Installation
@@ -11,12 +11,30 @@ Open a Terminal in your project's folder and run,
```sh ```sh
yarn add @react-navigation/native @react-navigation/native-stack yarn add @react-navigation/native @react-navigation/native-stack
``` ```
Or with npm
Now we need to install [`react-native-screens`](https://github.com/kmagiera/react-native-screens). ```sh
npm install --save @react-navigation/native @react-navigation/native-stack
```
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh
expo install react-native-screens
```
If you are not using Expo, run the following:
```sh ```sh
yarn add react-native-screens yarn add react-native-screens
``` ```
Or with npm
```sh
npm install --save react-native-screens
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run: To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
@@ -36,9 +54,9 @@ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
Make sure to enable `react-native-screens`. This needs to be done before our app renders. To do it, add the following code in your entry file (e.g. `App.js`): Make sure to enable `react-native-screens`. This needs to be done before our app renders. To do it, add the following code in your entry file (e.g. `App.js`):
```js ```js
import { useScreens } from 'react-native-screens'; import { enableScreens } from 'react-native-screens';
useScreens(); enableScreens();
``` ```
## Usage ## Usage

View File

@@ -6,7 +6,7 @@
"react-native", "react-native",
"react-navigation" "react-navigation"
], ],
"version": "5.0.0-alpha.17", "version": "5.0.0-alpha.22",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,13 +29,13 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16" "@react-navigation/routers": "^5.0.0-alpha.20"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react-native-screens": "^2.0.0-alpha.11", "react-native-screens": "^2.0.0-alpha.19",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0", "@react-navigation/native": "^5.0.0-alpha.0",

View File

@@ -57,7 +57,7 @@ export default function HeaderConfig(props: Props) {
? headerTintColor ? headerTintColor
: colors.text : colors.text
} }
backTitle={headerBackTitleVisible ? headerBackTitle : ''} backTitle={headerBackTitleVisible ? headerBackTitle : ' '}
backTitleFontFamily={headerBackTitleStyle.fontFamily} backTitleFontFamily={headerBackTitleStyle.fontFamily}
backTitleFontSize={headerBackTitleStyle.fontSize} backTitleFontSize={headerBackTitleStyle.fontSize}
color={headerTintColor !== undefined ? headerTintColor : colors.primary} color={headerTintColor !== undefined ? headerTintColor : colors.primary}

View File

@@ -3,6 +3,33 @@
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.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.21...@react-navigation/native@5.0.0-alpha.22) (2020-01-01)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.20...@react-navigation/native@5.0.0-alpha.21) (2019-12-19)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.19...@react-navigation/native@5.0.0-alpha.20) (2019-12-16)
### Features
* add nested config in deep linking ([#210](https://github.com/react-navigation/navigation-ex/issues/210)) ([8002d51](https://github.com/react-navigation/navigation-ex/commit/8002d5179524a7211c37760a4ed45e8c12af4358)), closes [#154](https://github.com/react-navigation/navigation-ex/issues/154)
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.18...@react-navigation/native@5.0.0-alpha.19) (2019-12-14) # [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.18...@react-navigation/native@5.0.0-alpha.19) (2019-12-14)

View File

@@ -7,7 +7,7 @@
"ios", "ios",
"android" "android"
], ],
"version": "5.0.0-alpha.19", "version": "5.0.0-alpha.22",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -30,16 +30,16 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^5.0.0-alpha.27" "@react-navigation/core": "^5.0.0-alpha.30"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.60.25",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",

View File

@@ -1,17 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import { Linking } from 'react-native'; import { Linking } from 'react-native';
import { import {
getActionFromState,
getStateFromPath as getStateFromPathDefault, getStateFromPath as getStateFromPathDefault,
NavigationContainerRef, NavigationContainerRef,
NavigationState,
PartialState,
} from '@react-navigation/core'; } from '@react-navigation/core';
type Config = { type GetStateFromPath = typeof getStateFromPathDefault;
[routeName: string]:
| string type Config = Parameters<GetStateFromPath>[1];
| { path: string; parse?: Record<string, (value: string) => any> };
};
type Options = { type Options = {
/** /**
@@ -36,10 +33,7 @@ type Options = {
/** /**
* Custom function to parse the URL object to a valid navigation state (advanced). * Custom function to parse the URL object to a valid navigation state (advanced).
*/ */
getStateFromPath?: ( getStateFromPath?: GetStateFromPath;
path: string,
options?: Config
) => PartialState<NavigationState> | undefined;
}; };
export default function useLinking( export default function useLinking(
@@ -89,7 +83,13 @@ export default function useLinking(
const state = getStateFromPathRef.current(path, configRef.current); const state = getStateFromPathRef.current(path, configRef.current);
if (state) { if (state) {
navigation.resetRoot(state); const action = getActionFromState(state);
if (action.type === 'RESET_ROOT') {
navigation.resetRoot(action.payload);
} else {
navigation.dispatch(action);
}
} }
} }
}; };

View File

@@ -3,6 +3,41 @@
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.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.19...@react-navigation/routers@5.0.0-alpha.20) (2020-01-05)
### Bug Fixes
* preserve focused route in tab on changing screens list ([adbeb29](https://github.com/react-navigation/navigation-ex/commit/adbeb292f522be8d7a58dd3f84e560a6d83d01a8))
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.18...@react-navigation/routers@5.0.0-alpha.19) (2020-01-01)
**Note:** Version bump only for package @react-navigation/routers
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.17...@react-navigation/routers@5.0.0-alpha.18) (2019-12-19)
**Note:** Version bump only for package @react-navigation/routers
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.16...@react-navigation/routers@5.0.0-alpha.17) (2019-12-16)
**Note:** Version bump only for package @react-navigation/routers
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.15...@react-navigation/routers@5.0.0-alpha.16) (2019-12-11) # [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.15...@react-navigation/routers@5.0.0-alpha.16) (2019-12-11)
**Note:** Version bump only for package @react-navigation/routers **Note:** Version bump only for package @react-navigation/routers

View File

@@ -250,6 +250,89 @@ it('gets state on route names change', () => {
}); });
}); });
it('preserves focused route on route names change', () => {
const router = TabRouter({});
expect(
router.getStateForRouteNamesChange(
{
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
type: 'tab',
},
{
routeNames: ['qux', 'foo', 'fiz', 'baz'],
routeParamList: {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
}
)
).toEqual({
index: 3,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'foo', 'fiz', 'baz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
],
stale: false,
type: 'tab',
});
});
it('falls back to first route if route is removed on route names change', () => {
const router = TabRouter({});
expect(
router.getStateForRouteNamesChange(
{
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
type: 'tab',
},
{
routeNames: ['qux', 'foo', 'fiz'],
routeParamList: {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
}
)
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'foo', 'fiz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
],
stale: false,
type: 'tab',
});
});
it('handles navigate action', () => { it('handles navigate action', () => {
const router = TabRouter({}); const router = TabRouter({});
const options = { const options = {

View File

@@ -6,7 +6,7 @@
"react-native", "react-native",
"react-navigation" "react-navigation"
], ],
"version": "5.0.0-alpha.16", "version": "5.0.0-alpha.20",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,13 +29,13 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^5.0.0-alpha.27", "@react-navigation/core": "^5.0.0-alpha.30",
"shortid": "^2.2.15" "shortid": "^2.2.15"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {
"source": "src", "source": "src",

View File

@@ -147,11 +147,16 @@ export default function TabRouter({
} }
); );
const index = Math.max(
0,
routeNames.indexOf(state.routes[state.index].name)
);
return { return {
...state, ...state,
routeNames, routeNames,
routes, routes,
index: Math.min(state.index, routes.length - 1), index,
}; };
}, },

View File

@@ -3,6 +3,81 @@
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.0.0-alpha.51](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.50...@react-navigation/stack@5.0.0-alpha.51) (2020-01-05)
**Note:** Version bump only for package @react-navigation/stack
# [5.0.0-alpha.50](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.49...@react-navigation/stack@5.0.0-alpha.50) (2020-01-03)
### Bug Fixes
* keep screens for replace when animation is enabled ([7f963a7](https://github.com/react-navigation/navigation-ex/commit/7f963a74bb4d4b04134e917fe47e38e4d982afed))
* use gesture direction when using next screen's animation ([572beae](https://github.com/react-navigation/navigation-ex/commit/572beae41b326f3ef80bc2b790badf123e0071bc))
# [5.0.0-alpha.49](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.48...@react-navigation/stack@5.0.0-alpha.49) (2020-01-03)
### Bug Fixes
* dismiss keyboard on page change ([2c31d17](https://github.com/react-navigation/navigation-ex/commit/2c31d1705c4e5827b19b9cc7f3e5b05207c3238a))
* interaction manager in stack ([#237](https://github.com/react-navigation/navigation-ex/issues/237)) ([6b9b999](https://github.com/react-navigation/navigation-ex/commit/6b9b999c5b60a67ed683b84484928700d4260585))
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
# [5.0.0-alpha.48](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.47...@react-navigation/stack@5.0.0-alpha.48) (2020-01-01)
### Bug Fixes
* improve gesture performance ([59803f5](https://github.com/react-navigation/navigation-ex/commit/59803f54d64f85c8e46c1ebc70613a70a812f53a))
* use native driver for gestures ([9356598](https://github.com/react-navigation/navigation-ex/commit/935659899f1d4084c601fbefea4a935f9b6ce087))
# [5.0.0-alpha.47](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.46...@react-navigation/stack@5.0.0-alpha.47) (2020-01-01)
**Note:** Version bump only for package @react-navigation/stack
# [5.0.0-alpha.46](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.45...@react-navigation/stack@5.0.0-alpha.46) (2019-12-19)
### Bug Fixes
* fix typescript issues ([c52a8c4](https://github.com/react-navigation/navigation-ex/commit/c52a8c46a8906812651e5259a850207fc448590e))
# [5.0.0-alpha.45](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.44...@react-navigation/stack@5.0.0-alpha.45) (2019-12-16)
### Bug Fixes
* disable style interpolation for card when animation is disabled ([c110570](https://github.com/react-navigation/navigation-ex/commit/c110570d4c89a38336f19403e6f2d0870868620e))
# [5.0.0-alpha.44](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.43...@react-navigation/stack@5.0.0-alpha.44) (2019-12-14) # [5.0.0-alpha.44](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.43...@react-navigation/stack@5.0.0-alpha.44) (2019-12-14)

View File

@@ -10,18 +10,18 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/native @react-navigation/stack @react-native-community/masked-view yarn add @react-navigation/native @react-navigation/stack @react-native-community/masked-view
``` ```
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated), [`react-native-screens`](https://github.com/kmagiera/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context). Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-screens`](https://github.com/kmagiera/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run: If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh ```sh
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context expo install react-native-gesture-handler react-native-screens react-native-safe-area-context
``` ```
If you are not using Expo, run the following: If you are not using Expo, run the following:
```sh ```sh
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context yarn add react-native-gesture-handler react-native-screens react-native-safe-area-context
``` ```
If you are using Expo, you are done. Otherwise, continue to the next steps. If you are using Expo, you are done. Otherwise, continue to the next steps.

View File

@@ -10,7 +10,7 @@
"android", "android",
"stack" "stack"
], ],
"version": "5.0.0-alpha.44", "version": "5.0.0-alpha.51",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,21 +33,23 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16" "@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@react-native-community/masked-view": "^0.1.5", "@react-native-community/masked-view": "^0.1.5",
"@types/react": "^16.9.11", "@types/color": "^3.0.0",
"@types/react-native": "^0.60.22", "@types/react": "^16.9.16",
"@types/react-native": "^0.60.25",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.0", "react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0", "react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.11", "react-native-screens": "^2.0.0-alpha.19",
"typescript": "^3.7.2" "typescript": "^3.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-native-community/masked-view": "^0.1.1", "@react-native-community/masked-view": "^0.1.1",
@@ -55,8 +57,7 @@
"react": "*", "react": "*",
"react-native": "*", "react-native": "*",
"react-native-gesture-handler": "^1.0.0", "react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0", "react-native-safe-area-context": "^0.6.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0" "react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {

View File

@@ -1,10 +1,11 @@
import Animated from 'react-native-reanimated'; import { Animated } from 'react-native';
import conditional from '../utils/conditional';
import { import {
StackCardInterpolationProps, StackCardInterpolationProps,
StackCardInterpolatedStyle, StackCardInterpolatedStyle,
} from '../types'; } from '../types';
const { cond, add, multiply, interpolate } = Animated; const { add, multiply } = Animated;
/** /**
* Standard iOS-style slide in from the right. * Standard iOS-style slide in from the right.
@@ -16,7 +17,7 @@ export function forHorizontalIOS({
layouts: { screen }, layouts: { screen },
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const translateFocused = multiply( const translateFocused = multiply(
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [screen.width, 0], outputRange: [screen.width, 0],
}), }),
@@ -25,20 +26,20 @@ export function forHorizontalIOS({
const translateUnfocused = next const translateUnfocused = next
? multiply( ? multiply(
interpolate(next.progress, { next.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, multiply(screen.width, -0.3)], outputRange: [0, screen.width * -0.3],
}), }),
inverted inverted
) )
: 0; : 0;
const overlayOpacity = interpolate(current.progress, { const overlayOpacity = current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, 0.07], outputRange: [0, 0.07],
}); });
const shadowOpacity = interpolate(current.progress, { const shadowOpacity = current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, 0.3], outputRange: [0, 0.3],
}); });
@@ -66,7 +67,7 @@ export function forVerticalIOS({
layouts: { screen }, layouts: { screen },
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const translateY = multiply( const translateY = multiply(
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [screen.height, 0], outputRange: [screen.height, 0],
}), }),
@@ -102,7 +103,7 @@ export function forModalPresentationIOS({
const progress = add(current.progress, next ? next.progress : 0); const progress = add(current.progress, next ? next.progress : 0);
const translateY = multiply( const translateY = multiply(
interpolate(progress, { progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: [ outputRange: [
screen.height, screen.height,
@@ -113,14 +114,14 @@ export function forModalPresentationIOS({
inverted inverted
); );
const overlayOpacity = interpolate(progress, { const overlayOpacity = progress.interpolate({
inputRange: [0, 1, 1.0001, 2], inputRange: [0, 1, 1.0001, 2],
outputRange: [0, 0.3, 1, 1], outputRange: [0, 0.3, 1, 1],
}); });
const scale = isLandscape const scale = isLandscape
? 1 ? 1
: interpolate(progress, { : progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: [ outputRange: [
1, 1,
@@ -132,7 +133,7 @@ export function forModalPresentationIOS({
const borderRadius = isLandscape const borderRadius = isLandscape
? 0 ? 0
: index === 0 : index === 0
? interpolate(progress, { ? progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: [0, 0, 10], outputRange: [0, 0, 10],
}) })
@@ -160,17 +161,17 @@ export function forFadeFromBottomAndroid({
closing, closing,
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const translateY = multiply( const translateY = multiply(
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [multiply(screen.height, 0.08), 0], outputRange: [screen.height * 0.08, 0],
}), }),
inverted inverted
); );
const opacity = cond( const opacity = conditional(
closing, closing,
current.progress, current.progress,
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 0.5, 0.9, 1], inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1], outputRange: [0, 0.25, 0.7, 1],
}) })
@@ -194,7 +195,7 @@ export function forRevealFromBottomAndroid({
layouts: { screen }, layouts: { screen },
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const containerTranslateY = multiply( const containerTranslateY = multiply(
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [screen.height, 0], outputRange: [screen.height, 0],
}), }),
@@ -202,24 +203,24 @@ export function forRevealFromBottomAndroid({
); );
const cardTranslateYFocused = multiply( const cardTranslateYFocused = multiply(
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [multiply(screen.height, 95.9 / 100, -1), 0], outputRange: [screen.height * (95.9 / 100) * -1, 0],
}), }),
inverted inverted
); );
const cardTranslateYUnfocused = next const cardTranslateYUnfocused = next
? multiply( ? multiply(
interpolate(next.progress, { next.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, multiply(screen.height, 2 / 100, -1)], outputRange: [0, screen.height * (2 / 100) * -1],
}), }),
inverted inverted
) )
: 0; : 0;
const overlayOpacity = interpolate(current.progress, { const overlayOpacity = current.progress.interpolate({
inputRange: [0, 0.36, 1], inputRange: [0, 0.36, 1],
outputRange: [0, 0.1, 0.1], outputRange: [0, 0.1, 0.1],
}); });
@@ -249,18 +250,18 @@ export function forScaleFromCenterAndroid({
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const progress = add(current.progress, next ? next.progress : 0); const progress = add(current.progress, next ? next.progress : 0);
const opacity = interpolate(progress, { const opacity = progress.interpolate({
inputRange: [0, 0.8, 1, 1.2, 2], inputRange: [0, 0.8, 1, 1.2, 2],
outputRange: [0, 0.5, 1, 0.33, 0], outputRange: [0, 0.5, 1, 0.33, 0],
}); });
const scale = cond( const scale = conditional(
closing, closing,
interpolate(current.progress, { current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0.9, 1], outputRange: [0.9, 1],
}), }),
interpolate(progress, { progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: [0.85, 1, 1.1], outputRange: [0.85, 1, 1.1],
}) })
@@ -273,3 +274,7 @@ export function forScaleFromCenterAndroid({
}, },
}; };
} }
export function forNoAnimation(): StackCardInterpolatedStyle {
return {};
}

View File

@@ -1,11 +1,10 @@
import { I18nManager } from 'react-native'; import { Animated, I18nManager } from 'react-native';
import Animated from 'react-native-reanimated';
import { import {
StackHeaderInterpolationProps, StackHeaderInterpolationProps,
StackHeaderInterpolatedStyle, StackHeaderInterpolatedStyle,
} from '../types'; } from '../types';
const { interpolate, add } = Animated; const { add } = Animated;
/** /**
* Standard UIKit style animation for the header where the title fades into the back button label. * Standard UIKit style animation for the header where the title fades into the back button label.
@@ -38,7 +37,7 @@ export function forUIKit({
return { return {
leftButtonStyle: { leftButtonStyle: {
opacity: interpolate(progress, { opacity: progress.interpolate({
inputRange: [0.3, 1, 1.5], inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0], outputRange: [0, 1, 0],
}), }),
@@ -46,7 +45,7 @@ export function forUIKit({
leftLabelStyle: { leftLabelStyle: {
transform: [ transform: [
{ {
translateX: interpolate(progress, { translateX: progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL outputRange: I18nManager.isRTL
? [-rightOffset, 0, leftLabelOffset] ? [-rightOffset, 0, leftLabelOffset]
@@ -56,19 +55,19 @@ export function forUIKit({
], ],
}, },
rightButtonStyle: { rightButtonStyle: {
opacity: interpolate(progress, { opacity: progress.interpolate({
inputRange: [0.3, 1, 1.5], inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0], outputRange: [0, 1, 0],
}), }),
}, },
titleStyle: { titleStyle: {
opacity: interpolate(progress, { opacity: progress.interpolate({
inputRange: [0, 0.4, 1, 1.5], inputRange: [0, 0.4, 1, 1.5],
outputRange: [0, 0.1, 1, 0], outputRange: [0, 0.1, 1, 0],
}), }),
transform: [ transform: [
{ {
translateX: interpolate(progress, { translateX: progress.interpolate({
inputRange: [0.5, 1, 2], inputRange: [0.5, 1, 2],
outputRange: I18nManager.isRTL outputRange: I18nManager.isRTL
? [-titleLeftOffset, 0, rightOffset] ? [-titleLeftOffset, 0, rightOffset]
@@ -80,7 +79,7 @@ export function forUIKit({
backgroundStyle: { backgroundStyle: {
transform: [ transform: [
{ {
translateX: interpolate(progress, { translateX: progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL outputRange: I18nManager.isRTL
? [-layouts.screen.width, 0, layouts.screen.width] ? [-layouts.screen.width, 0, layouts.screen.width]
@@ -100,7 +99,7 @@ export function forFade({
next, next,
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle { }: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
const progress = add(current.progress, next ? next.progress : 0); const progress = add(current.progress, next ? next.progress : 0);
const opacity = interpolate(progress, { const opacity = progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: [0, 1, 0], outputRange: [0, 1, 0],
}); });
@@ -110,7 +109,7 @@ export function forFade({
rightButtonStyle: { opacity }, rightButtonStyle: { opacity },
titleStyle: { opacity }, titleStyle: { opacity },
backgroundStyle: { backgroundStyle: {
opacity: interpolate(progress, { opacity: progress.interpolate({
inputRange: [0, 1, 1.9, 2], inputRange: [0, 1, 1.9, 2],
outputRange: [0, 1, 1, 0], outputRange: [0, 1, 1, 0],
}), }),
@@ -127,7 +126,7 @@ export function forStatic({
layouts: { screen }, layouts: { screen },
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle { }: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
const progress = add(current.progress, next ? next.progress : 0); const progress = add(current.progress, next ? next.progress : 0);
const translateX = interpolate(progress, { const translateX = progress.interpolate({
inputRange: [0, 1, 2], inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL outputRange: I18nManager.isRTL
? [-screen.width, 0, screen.width] ? [-screen.width, 0, screen.width]

View File

@@ -1,4 +1,4 @@
import { Easing } from 'react-native-reanimated'; import { Easing } from 'react-native';
import { TransitionSpec } from '../types'; import { TransitionSpec } from '../types';
/** /**

View File

@@ -9,7 +9,9 @@ import * as TransitionPresets from './TransitionConfigs/TransitionPresets';
export { default as createStackNavigator } from './navigators/createStackNavigator'; export { default as createStackNavigator } from './navigators/createStackNavigator';
export const Assets = [ export const Assets = [
// eslint-disable-next-line import/no-commonjs
require('./views/assets/back-icon.png'), require('./views/assets/back-icon.png'),
// eslint-disable-next-line import/no-commonjs
require('./views/assets/back-icon-mask.png'), require('./views/assets/back-icon-mask.png'),
]; ];

View File

@@ -1,10 +1,11 @@
import { import {
Animated,
EasingFunction,
StyleProp, StyleProp,
TextStyle, TextStyle,
ViewStyle, ViewStyle,
LayoutChangeEvent, LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context'; import { EdgeInsets } from 'react-native-safe-area-context';
import { import {
NavigationProp, NavigationProp,
@@ -88,22 +89,24 @@ export type Scene<T> = {
/** /**
* Progress value of the current screen. * Progress value of the current screen.
*/ */
current: Animated.Node<number>; current: Animated.AnimatedInterpolation;
/** /**
* Progress value for the screen after this one in the stack. * Progress value for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one. * This can be `undefined` in case the screen animating is the last one.
*/ */
next?: Animated.Node<number>; next?: Animated.AnimatedInterpolation;
/** /**
* Progress value for the screen before this one in the stack. * Progress value for the screen before this one in the stack.
* This can be `undefined` in case the screen animating is the first one. * This can be `undefined` in case the screen animating is the first one.
*/ */
previous?: Animated.Node<number>; previous?: Animated.AnimatedInterpolation;
}; };
}; };
export type StackHeaderMode = 'float' | 'screen' | 'none'; export type StackHeaderMode = 'float' | 'screen' | 'none';
export type StackCardMode = 'card' | 'modal';
export type StackHeaderOptions = { export type StackHeaderOptions = {
/** /**
* String or a function that returns a React Element to be used by the header. * String or a function that returns a React Element to be used by the header.
@@ -271,18 +274,14 @@ export type StackNavigationOptions = StackHeaderOptions &
* Defaults to `true` on Android and `false` on iOS. * Defaults to `true` on Android and `false` on iOS.
*/ */
cardOverlayEnabled?: boolean; cardOverlayEnabled?: boolean;
/**
* Whether to use a transparent background for the card instead of a white one.
* This is useful to implement things like modal dialogs where the previous scene should still be visible underneath the current one.
* Defaults to `false`.
*
* If you use [`react-native-screens`](https://github.com/kmagiera/react-native-screens),
* you should also specify `mode: 'modal'` in the stack view config so previous screens aren't detached.
*/
cardTransparent?: boolean;
/** /**
* Style object for the card in stack. * Style object for the card in stack.
* You can provide a custom background color to use instead of the default background here. * You can provide a custom background color to use instead of the default background here.
*
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
* This is useful to implement things like modal dialogs.
* If you use [`react-native-screens`](https://github.com/kmagiera/react-native-screens), you should also specify `mode: 'modal'`
* in the stack view config when using a transparent background so previous screens aren't detached.
*/ */
cardStyle?: StyleProp<ViewStyle>; cardStyle?: StyleProp<ViewStyle>;
/** /**
@@ -326,7 +325,7 @@ export type StackNavigationOptions = StackHeaderOptions &
}; };
export type StackNavigationConfig = { export type StackNavigationConfig = {
mode?: 'card' | 'modal'; mode?: StackCardMode;
headerMode?: StackHeaderMode; headerMode?: StackHeaderMode;
/** /**
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen. * If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
@@ -437,7 +436,7 @@ export type SpringConfig = {
export type TimingConfig = { export type TimingConfig = {
duration: number; duration: number;
easing: Animated.EasingFunction; easing: EasingFunction;
}; };
export type TransitionSpec = export type TransitionSpec =
@@ -452,7 +451,7 @@ export type StackCardInterpolationProps = {
/** /**
* Animated node representing the progress value of the current screen. * Animated node representing the progress value of the current screen.
*/ */
progress: Animated.Node<number>; progress: Animated.AnimatedInterpolation;
}; };
/** /**
* Values for the current screen the screen after this one in the stack. * Values for the current screen the screen after this one in the stack.
@@ -462,24 +461,24 @@ export type StackCardInterpolationProps = {
/** /**
* Animated node representing the progress value of the next screen. * Animated node representing the progress value of the next screen.
*/ */
progress: Animated.Node<number>; progress: Animated.AnimatedInterpolation;
}; };
/** /**
* The index of the card in the stack. * The index of the card in the stack.
*/ */
index: number; index: number;
/** /**
* Animated node representing whether the card is closing. * Animated node representing whether the card is closing (1 - closing, 0 - not closing).
*/ */
closing: Animated.Node<0 | 1>; closing: Animated.AnimatedInterpolation;
/** /**
* Animated node representing whether the card is being swiped. * Animated node representing whether the card is being swiped (1 - swiping, 0 - not swiping).
*/ */
swiping: Animated.Node<0 | 1>; swiping: Animated.AnimatedInterpolation;
/** /**
* Animated node representing multiplier when direction is inverted. * Animated node representing multiplier when direction is inverted (-1 - inverted, 1 - normal).
*/ */
inverted: Animated.Node<-1 | 1>; inverted: Animated.AnimatedInterpolation;
/** /**
* Layout measurements for various items we use for animation. * Layout measurements for various items we use for animation.
*/ */
@@ -531,7 +530,7 @@ export type StackHeaderInterpolationProps = {
/** /**
* Animated node representing the progress value of the current screen. * Animated node representing the progress value of the current screen.
*/ */
progress: Animated.Node<number>; progress: Animated.AnimatedInterpolation;
}; };
/** /**
* Values for the current screen the screen after this one in the stack. * Values for the current screen the screen after this one in the stack.
@@ -541,7 +540,7 @@ export type StackHeaderInterpolationProps = {
/** /**
* Animated node representing the progress value of the next screen. * Animated node representing the progress value of the next screen.
*/ */
progress: Animated.Node<number>; progress: Animated.AnimatedInterpolation;
}; };
/** /**
* Layout measurements for various items we use for animation. * Layout measurements for various items we use for animation.

View File

@@ -0,0 +1,33 @@
import { Animated } from 'react-native';
const { add, multiply } = Animated;
/**
* Use an Animated Node based on a condition. Similar to Reanimated's `cond`.
*
* @param condition Animated Node representing the condition, must be 0 or 1, 1 means `true`, 0 means `false`
* @param main Animated Node to use if the condition is `true`
* @param fallback Animated Node to use if the condition is `false`
*/
export default function conditional(
condition: Animated.AnimatedInterpolation,
main: Animated.AnimatedInterpolation,
fallback: Animated.AnimatedInterpolation
) {
// To implement this behavior, we multiply the main node with the condition.
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.
// So if condition is 0, result will be fallback node, and if condition is 1, result will be 0,
// This way, one of them will always be 0, and other one will be the value we need.
// In the end we add them both together, 0 + value we need = value we need
return add(
multiply(condition, main),
multiply(
condition.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
fallback
)
);
}

View File

@@ -0,0 +1,18 @@
import getInvertedMultiplier from './getInvertedMultiplier';
import { GestureDirection, Layout } from '../types';
export default function getDistanceForDirection(
layout: Layout,
gestureDirection: GestureDirection
): number {
const multiplier = getInvertedMultiplier(gestureDirection);
switch (gestureDirection) {
case 'vertical':
case 'vertical-inverted':
return layout.height * multiplier;
case 'horizontal':
case 'horizontal-inverted':
return layout.width * multiplier;
}
}

View File

@@ -0,0 +1,17 @@
import { I18nManager } from 'react-native';
import { GestureDirection } from '../types';
export default function getInvertedMultiplier(
gestureDirection: GestureDirection
): 1 | -1 {
switch (gestureDirection) {
case 'vertical':
return 1;
case 'vertical-inverted':
return -1;
case 'horizontal':
return I18nManager.isRTL ? -1 : 1;
case 'horizontal-inverted':
return I18nManager.isRTL ? 1 : -1;
}
}

View File

@@ -1,4 +1,4 @@
export default function memoize<Result, Deps extends ReadonlyArray<any>>( export default function memoize<Result, Deps extends readonly any[]>(
callback: (...deps: Deps) => Result callback: (...deps: Deps) => Result
) { ) {
let previous: Deps | undefined; let previous: Deps | undefined;

View File

@@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { import {
Animated,
I18nManager, I18nManager,
Image, Image,
View, View,
@@ -7,7 +8,6 @@ import {
StyleSheet, StyleSheet,
LayoutChangeEvent, LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
import MaskedView from '../MaskedView'; import MaskedView from '../MaskedView';
import TouchableItem from '../TouchableItem'; import TouchableItem from '../TouchableItem';
@@ -52,7 +52,7 @@ export default function HeaderBackButton({
: 'rgba(0, 0, 0, .32)'; : 'rgba(0, 0, 0, .32)';
const handleLabelLayout = (e: LayoutChangeEvent) => { const handleLabelLayout = (e: LayoutChangeEvent) => {
onLabelLayout && onLabelLayout(e); onLabelLayout?.(e);
setInitialLabelWidth(e.nativeEvent.layout.x + e.nativeEvent.layout.width); setInitialLabelWidth(e.nativeEvent.layout.x + e.nativeEvent.layout.width);
}; };

View File

@@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { StyleSheet, Platform, ViewProps } from 'react-native'; import { Animated, StyleSheet, Platform, ViewProps } from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
export default function HeaderBackground({ style, ...rest }: ViewProps) { export default function HeaderBackground({ style, ...rest }: ViewProps) {

View File

@@ -21,7 +21,7 @@ export type Props = {
mode: 'float' | 'screen'; mode: 'float' | 'screen';
layout: Layout; layout: Layout;
insets: EdgeInsets; insets: EdgeInsets;
scenes: Array<Scene<Route<string>> | undefined>; scenes: (Scene<Route<string>> | undefined)[];
state: StackNavigationState; state: StackNavigationState;
getPreviousRoute: (props: { getPreviousRoute: (props: {
route: Route<string>; route: Route<string>;

View File

@@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { import {
Animated,
View, View,
StyleSheet, StyleSheet,
LayoutChangeEvent, LayoutChangeEvent,
Platform, Platform,
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context'; import { EdgeInsets } from 'react-native-safe-area-context';
import { Route } from '@react-navigation/native'; import { Route } from '@react-navigation/native';
import HeaderBackButton from './HeaderBackButton'; import HeaderBackButton from './HeaderBackButton';
@@ -114,8 +114,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
( (
styleInterpolator: StackHeaderStyleInterpolator, styleInterpolator: StackHeaderStyleInterpolator,
layout: Layout, layout: Layout,
current: Animated.Node<number>, current: Animated.AnimatedInterpolation,
next: Animated.Node<number> | undefined, next: Animated.AnimatedInterpolation | undefined,
titleLayout: Layout | undefined, titleLayout: Layout | undefined,
leftLabelLayout: Layout | undefined leftLabelLayout: Layout | undefined
) => ) =>
@@ -272,6 +272,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
// @ts-ignore // @ts-ignore
if (safeStyles[styleProp] === undefined) { if (safeStyles[styleProp] === undefined) {
// @ts-ignore // @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete safeStyles[styleProp]; delete safeStyles[styleProp];
} }
} }

View File

@@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { StyleSheet, Platform, TextProps } from 'react-native'; import { Animated, StyleSheet, Platform, TextProps } from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
type Props = TextProps & { type Props = TextProps & {

View File

@@ -11,14 +11,15 @@ type Props = {
}; };
export default class KeyboardManager extends React.Component<Props> { export default class KeyboardManager extends React.Component<Props> {
componentWillUnmount = () => { componentWillUnmount() {
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
}; }
// Numeric id of the previously focused text input // Numeric id of the previously focused text input
// When a gesture didn't change the tab, we can restore the focused input with this // When a gesture didn't change the tab, we can restore the focused input with this
private previouslyFocusedTextInput: number | null = null; private previouslyFocusedTextInput: number | null = null;
private startTimestamp: number = 0; private startTimestamp: number = 0;
private keyboardTimeout: NodeJS.Timeout | undefined; private keyboardTimeout: any;
private clearKeyboardTimeout = () => { private clearKeyboardTimeout = () => {
if (this.keyboardTimeout !== undefined) { if (this.keyboardTimeout !== undefined) {
@@ -31,6 +32,7 @@ export default class KeyboardManager extends React.Component<Props> {
if (!this.props.enabled) { if (!this.props.enabled) {
return; return;
} }
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
const input = TextInput.State.currentlyFocusedField(); const input = TextInput.State.currentlyFocusedField();
@@ -49,6 +51,7 @@ export default class KeyboardManager extends React.Component<Props> {
if (!this.props.enabled) { if (!this.props.enabled) {
return; return;
} }
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
Keyboard.dismiss(); Keyboard.dismiss();
@@ -61,6 +64,7 @@ export default class KeyboardManager extends React.Component<Props> {
if (!this.props.enabled) { if (!this.props.enabled) {
return; return;
} }
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
// The page didn't change, we should restore the focus of text input // The page didn't change, we should restore the focus of text input

Some files were not shown because too many files have changed in this diff Show More