Compare commits

...

30 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
104 changed files with 1415 additions and 1385 deletions

View File

@@ -13,5 +13,8 @@ module.exports = {
'@babel/preset-react',
'@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,6 +28,7 @@
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "~1.5.0",
"react-native-iphone-x-helper": "^1.2.1",
"react-native-paper": "^3.3.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ import {
import { Asset } from 'expo-asset';
import {
InitialState,
getStateFromPath,
useLinking,
NavigationContainerRef,
NavigationNativeContainer,
@@ -104,20 +103,19 @@ export default function App() {
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes,
getStateFromPath: path => {
const state = getStateFromPath(path);
config: {
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 {
routes: [
{
name: 'root',
state: {
...state,
routes: [{ name: 'home' }, ...(state ? state.routes : [])],
},
},
],
};
return acc;
},
{}
),
},
});
@@ -252,19 +250,19 @@ export default function App() {
/>
</View>
<Divider />
{(Object.keys(SCREENS) as Array<
keyof typeof SCREENS
>).map(name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
/>
))}
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
/>
)
)}
</ScrollView>
)}
</Stack.Screen>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
<Stack.Screen
key={name}

View File

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

View File

@@ -2,7 +2,7 @@ const error = console.error;
console.error = (...args) =>
// 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]
)
? void 0

View File

@@ -24,6 +24,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.2",
@@ -34,7 +35,7 @@
"commitlint": "^8.2.0",
"core-js": "^3.5.0",
"eslint": "^6.7.2",
"eslint-config-satya164": "^3.1.2",
"eslint-config-satya164": "^3.1.5",
"husky": "^3.0.9",
"jest": "^24.8.0",
"lerna": "^3.18.4",

View File

@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.28",
"version": "5.0.0-alpha.32",
"license": "MIT",
"repository": {
"type": "git",
@@ -33,8 +33,9 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18",
"color": "^3.1.2"
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
@@ -51,7 +52,7 @@
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": "^0.3.6"
"react-native-safe-area-context": "^0.6.0"
},
"@react-native-community/bob": {
"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> {
render() {
if (screensEnabled && screensEnabled()) {
if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider,
SafeAreaConsumer,
} 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 = {
children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.19",
"version": "5.0.0-alpha.21",
"license": "MIT",
"repository": {
"type": "git",
@@ -24,7 +24,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18"
"@react-navigation/routers": "^5.0.0-alpha.20"
},
"devDependencies": {
"@types/react": "^16.9.16",

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.29",
"version": "5.0.0-alpha.30",
"license": "MIT",
"repository": {
"type": "git",

View File

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

View File

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

@@ -357,6 +357,8 @@ it("doesn't update state if action wasn't handled", () => {
const onStateChange = jest.fn();
const spy = jest.spyOn(console, 'error').mockImplementation();
render(
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
@@ -367,6 +369,12 @@ it("doesn't update state if action wasn't handled", () => {
);
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', () => {

View File

@@ -305,8 +305,7 @@ it("action doesn't bubble if target is specified", () => {
expect(onStateChange).not.toBeCalled();
});
// eslint-disable-next-line jest/expect-expect
it("doesn't crash if no navigator handled the action", () => {
it('logs error if no navigator handled the action', () => {
const TestRouter = MockRouter;
const TestNavigator = (props: any) => {
@@ -366,5 +365,13 @@ it("doesn't crash if no navigator handled the action", () => {
</NavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
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 route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1);
expect(route?.params?.x).toEqual(1);
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<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config && config[key] ? config[key](value) : String(value);
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
@@ -80,6 +80,7 @@ export default function getPathFromState(
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// 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];
return encodeURIComponent(value);
}

View File

@@ -15,6 +15,10 @@ type RouteConfig = {
parse: Record<string, (value: string) => any> | undefined;
};
type ResultState = PartialState<NavigationState> & {
state?: ResultState;
};
/**
* Utility to parse a path string to initial state object accepted by the container.
* This is useful for deep linking when we need to handle the incoming URL.
@@ -37,7 +41,7 @@ type RouteConfig = {
export default function getStateFromPath(
path: string,
options: Options = {}
): PartialState<NavigationState> | undefined {
): ResultState | undefined {
// Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat(
...Object.keys(options).map(key => createNormalizedConfigs(key, options))

View File

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

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions';
import * as React from 'react';
@@ -20,9 +19,9 @@ export type NavigationState = {
/**
* List of rendered routes.
*/
routes: Array<
Route<string> & { state?: NavigationState | PartialState<NavigationState> }
>;
routes: (Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
})[];
/**
* 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.
@@ -38,7 +37,7 @@ export type NavigationState = {
export type InitialState = Partial<
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<
@@ -46,9 +45,10 @@ export type PartialState<State extends NavigationState> = Partial<
> & {
stale?: true;
type?: string;
routes: Array<
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState }
>;
routes: (Omit<Route<string>, 'key'> & {
key?: string;
state?: InitialState;
})[];
};
export type Route<RouteName extends string> = {
@@ -71,6 +71,10 @@ export type NavigationAction = {
* Type of the action (e.g. `NAVIGATE`)
*/
type: string;
/**
* Additional data for the action
*/
payload?: object;
/**
* Key of the route which dispatched this action.
*/

View File

@@ -44,16 +44,15 @@ export default function useDevTools({ name, reset, state }: Options) {
const devTools = devToolsRef.current;
const lastStateRef = React.useRef<State>(state);
const actions = React.useRef<Array<NavigationAction | string>>([]);
const actions = React.useRef<(NavigationAction | string)[]>([]);
React.useEffect(() => {
devTools && devTools.init(lastStateRef.current);
devTools?.init(lastStateRef.current);
}, [devTools]);
React.useEffect(
() =>
devTools &&
devTools.subscribe(message => {
devTools?.subscribe(message => {
if (message.type === 'DISPATCH' && 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>>;
};
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.
@@ -69,7 +69,7 @@ export default function useEventEmitter(): NavigationEventEmitter {
},
};
callbacks && callbacks.forEach(cb => cb(event));
callbacks?.forEach(cb => cb(event));
return event;
},

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
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`.
@@ -15,7 +15,7 @@ export default function useFocusEffect(callback: EffectCallback) {
React.useEffect(() => {
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
if (navigation.isFocused()) {
@@ -30,19 +30,19 @@ export default function useFocusEffect(callback: EffectCallback) {
return;
}
cleanup && cleanup();
cleanup?.();
cleanup = callback();
isFocused = true;
});
const unsubscribeBlur = navigation.addListener('blur', () => {
cleanup && cleanup();
cleanup?.();
cleanup = undefined;
isFocused = false;
});
return () => {
cleanup && cleanup();
cleanup?.();
unsubscribeFocus();
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
React.useEffect(
() =>
navigation &&
navigation.addListener('focus', () =>
navigation?.addListener('focus', () =>
emitter.emit({ type: 'focus', target: currentFocusedKey })
),
[currentFocusedKey, emitter, navigation]
@@ -30,8 +29,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
React.useEffect(
() =>
navigation &&
navigation.addListener('blur', () =>
navigation?.addListener('blur', () =>
emitter.emit({ type: 'blur', target: currentFocusedKey })
),
[currentFocusedKey, emitter, navigation]

View File

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

View File

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

View File

@@ -42,13 +42,22 @@ export default function useNavigationHelpers<
const { performTransaction } = React.useContext(NavigationStateContext);
return React.useMemo(() => {
const dispatch = (action: Action | ((state: State) => Action)) =>
const dispatch = (action: Action | ((state: State) => Action)) => {
performTransaction(() => {
const payload =
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 = {
...router.actionCreators,
@@ -81,7 +90,7 @@ export default function useNavigationHelpers<
routeNames: state.routeNames,
routeParamList: {},
}) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
parentNavigationHelpers?.canGoBack() ||
false
);
},

View File

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

View File

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

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
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)

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.30",
"version": "5.0.0-alpha.34",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,8 +34,9 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18",
"color": "^3.1.2"
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -23,6 +23,7 @@ type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
function DrawerNavigator({
initialRouteName,
backBehavior,
children,
screenOptions,
...rest
@@ -34,6 +35,7 @@ function DrawerNavigator({
DrawerNavigationEventMap
>(DrawerRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});

View File

@@ -9,6 +9,7 @@ import {
StatusBar,
StyleProp,
View,
InteractionManager,
} from 'react-native';
import {
PanGestureHandler,
@@ -161,9 +162,24 @@ export default class DrawerView extends React.PureComponent<Props> {
componentWillUnmount() {
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 interactionHandle: number | undefined;
private isDrawerTypeFront = new Value<Binary>(
this.props.drawerType === 'front' ? TRUE : FALSE
@@ -277,6 +293,7 @@ export default class DrawerView extends React.PureComponent<Props> {
set(state.velocity, this.velocityX),
set(this.isOpen, isOpen),
startClock(this.clock),
call([], this.handleStartInteraction),
set(this.manuallyTriggerSpring, FALSE),
]),
spring(this.clock, state, { ...SPRING_CONFIG, toValue }),
@@ -288,8 +305,9 @@ export default class DrawerView extends React.PureComponent<Props> {
set(this.offsetX, 0),
// When the animation finishes, stop the clock
stopClock(this.clock),
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value);
this.handleEndInteraction();
if (open !== this.props.open) {
// Sync drawer's state after animation finished
@@ -304,7 +322,7 @@ export default class DrawerView extends React.PureComponent<Props> {
private dragX = block([
onChange(
this.isOpen,
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value);
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
// 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
call([this.isSwiping], ([value]: ReadonlyArray<Binary>) => {
call([this.isSwiping], ([value]: readonly Binary[]) => {
const { keyboardDismissMode } = this.props;
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(
eq(this.gestureState, State.ACTIVE),
[

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> {
render() {
if (screensEnabled && screensEnabled()) {
if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props;
// @ts-ignore

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider,
SafeAreaConsumer,
} 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 = {
children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.27",
"version": "5.0.0-alpha.29",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18"
"@react-navigation/routers": "^5.0.0-alpha.20"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
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)

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.25",
"version": "5.0.0-alpha.27",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18",
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2"
},
"devDependencies": {

View File

@@ -113,9 +113,15 @@ export type MaterialTopTabNavigationConfig = Partial<
| 'onSwipeEnd'
| 'renderScene'
| 'renderTabBar'
| 'renderPager'
| '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.
* Receives an object containing the route as the prop.

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
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 MaterialTopTabBar from './MaterialTopTabBar';
@@ -19,6 +19,7 @@ type Props = MaterialTopTabNavigationConfig & {
};
export default function MaterialTopTabView({
pager,
lazyPlaceholder,
tabBar = (props: MaterialTopTabBarProps) => <MaterialTopTabBar {...props} />,
tabBarOptions,
@@ -30,14 +31,6 @@ export default function MaterialTopTabView({
}: Props) {
const { colors } = useTheme();
const renderLazyPlaceholder = (props: { route: Route<string> }) => {
if (lazyPlaceholder != null) {
return lazyPlaceholder(props);
}
return null;
};
const renderTabBar = (props: SceneRendererProps) => {
const route = state.routes[state.index];
const options = descriptors[route.key].options;
@@ -69,7 +62,8 @@ export default function MaterialTopTabView({
renderScene={({ route }) => descriptors[route.key].render()}
navigationState={state}
renderTabBar={renderTabBar}
renderLazyPlaceholder={renderLazyPlaceholder}
renderPager={pager}
renderLazyPlaceholder={lazyPlaceholder}
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
sceneContainerStyle={[

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.19",
"version": "5.0.0-alpha.22",
"license": "MIT",
"repository": {
"type": "git",
@@ -29,7 +29,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18"
"@react-navigation/routers": "^5.0.0-alpha.20"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
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

View File

@@ -7,7 +7,7 @@
"ios",
"android"
],
"version": "5.0.0-alpha.21",
"version": "5.0.0-alpha.22",
"license": "MIT",
"repository": {
"type": "git",
@@ -30,7 +30,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.0.0-alpha.29"
"@react-navigation/core": "^5.0.0-alpha.30"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { Linking } from 'react-native';
import {
getActionFromState,
getStateFromPath as getStateFromPathDefault,
NavigationContainerRef,
} from '@react-navigation/core';
@@ -82,7 +83,13 @@ export default function useLinking(
const state = getStateFromPathRef.current(path, configRef.current);
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,25 @@
All notable changes to this project will be documented in this file.
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

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', () => {
const router = TabRouter({});
const options = {

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.18",
"version": "5.0.0-alpha.20",
"license": "MIT",
"repository": {
"type": "git",
@@ -29,7 +29,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.0.0-alpha.29",
"@react-navigation/core": "^5.0.0-alpha.30",
"shortid": "^2.2.15"
},
"devDependencies": {

View File

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

View File

@@ -3,6 +3,59 @@
All notable changes to this project will be documented in this file.
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)

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
```
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:
```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:
```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.

View File

@@ -10,7 +10,7 @@
"android",
"stack"
],
"version": "5.0.0-alpha.46",
"version": "5.0.0-alpha.51",
"license": "MIT",
"repository": {
"type": "git",
@@ -33,8 +33,9 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.18",
"color": "^3.1.2"
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
@@ -46,7 +47,6 @@
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.19",
"typescript": "^3.7.3"
@@ -57,7 +57,6 @@
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
},

View File

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

View File

@@ -1,11 +1,10 @@
import { I18nManager } from 'react-native';
import Animated from 'react-native-reanimated';
import { Animated, I18nManager } from 'react-native';
import {
StackHeaderInterpolationProps,
StackHeaderInterpolatedStyle,
} 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.
@@ -38,7 +37,7 @@ export function forUIKit({
return {
leftButtonStyle: {
opacity: interpolate(progress, {
opacity: progress.interpolate({
inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0],
}),
@@ -46,7 +45,7 @@ export function forUIKit({
leftLabelStyle: {
transform: [
{
translateX: interpolate(progress, {
translateX: progress.interpolate({
inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL
? [-rightOffset, 0, leftLabelOffset]
@@ -56,19 +55,19 @@ export function forUIKit({
],
},
rightButtonStyle: {
opacity: interpolate(progress, {
opacity: progress.interpolate({
inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0],
}),
},
titleStyle: {
opacity: interpolate(progress, {
opacity: progress.interpolate({
inputRange: [0, 0.4, 1, 1.5],
outputRange: [0, 0.1, 1, 0],
}),
transform: [
{
translateX: interpolate(progress, {
translateX: progress.interpolate({
inputRange: [0.5, 1, 2],
outputRange: I18nManager.isRTL
? [-titleLeftOffset, 0, rightOffset]
@@ -80,7 +79,7 @@ export function forUIKit({
backgroundStyle: {
transform: [
{
translateX: interpolate(progress, {
translateX: progress.interpolate({
inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL
? [-layouts.screen.width, 0, layouts.screen.width]
@@ -100,7 +99,7 @@ export function forFade({
next,
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
const progress = add(current.progress, next ? next.progress : 0);
const opacity = interpolate(progress, {
const opacity = progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [0, 1, 0],
});
@@ -110,7 +109,7 @@ export function forFade({
rightButtonStyle: { opacity },
titleStyle: { opacity },
backgroundStyle: {
opacity: interpolate(progress, {
opacity: progress.interpolate({
inputRange: [0, 1, 1.9, 2],
outputRange: [0, 1, 1, 0],
}),
@@ -127,7 +126,7 @@ export function forStatic({
layouts: { screen },
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
const progress = add(current.progress, next ? next.progress : 0);
const translateX = interpolate(progress, {
const translateX = progress.interpolate({
inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL
? [-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';
/**

View File

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

View File

@@ -1,10 +1,11 @@
import {
Animated,
EasingFunction,
StyleProp,
TextStyle,
ViewStyle,
LayoutChangeEvent,
} from 'react-native';
import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context';
import {
NavigationProp,
@@ -88,22 +89,24 @@ export type Scene<T> = {
/**
* Progress value of the current screen.
*/
current: Animated.Node<number>;
current: Animated.AnimatedInterpolation;
/**
* Progress value for the screen after this one in the stack.
* 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.
* 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 StackCardMode = 'card' | 'modal';
export type StackHeaderOptions = {
/**
* String or a function that returns a React Element to be used by the header.
@@ -322,7 +325,7 @@ export type StackNavigationOptions = StackHeaderOptions &
};
export type StackNavigationConfig = {
mode?: 'card' | 'modal';
mode?: StackCardMode;
headerMode?: StackHeaderMode;
/**
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
@@ -433,7 +436,7 @@ export type SpringConfig = {
export type TimingConfig = {
duration: number;
easing: Animated.EasingFunction;
easing: EasingFunction;
};
export type TransitionSpec =
@@ -448,7 +451,7 @@ export type StackCardInterpolationProps = {
/**
* 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.
@@ -458,24 +461,24 @@ export type StackCardInterpolationProps = {
/**
* 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.
*/
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.
*/
@@ -527,7 +530,7 @@ export type StackHeaderInterpolationProps = {
/**
* 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.
@@ -537,7 +540,7 @@ export type StackHeaderInterpolationProps = {
/**
* 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.

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
) {
let previous: Deps | undefined;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider,
SafeAreaConsumer,
} 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 = {
children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import {
Animated,
View,
I18nManager,
StyleSheet,
ViewProps,
StyleProp,
@@ -9,35 +9,36 @@ import {
Platform,
InteractionManager,
} from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
import {
PanGestureHandler,
State as GestureState,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import { EdgeInsets } from 'react-native-safe-area-context';
import Color from 'color';
import animate, { Binary } from './CardAnimation';
import PointerEventsView from './PointerEventsView';
import memoize from '../../utils/memoize';
import StackGestureContext from '../../utils/StackGestureContext';
import StackCardAnimationContext from '../../utils/StackCardAnimationContext';
import getDistanceForDirection from '../../utils/getDistanceForDirection';
import getInvertedMultiplier from '../../utils/getInvertedMultiplier';
import memoize from '../../utils/memoize';
import {
TransitionSpec,
StackCardStyleInterpolator,
GestureDirection,
Layout,
TimingConfig,
SpringConfig,
} from '../../types';
type Props = ViewProps & {
index: number;
active: boolean;
closing?: boolean;
next?: Animated.Node<number>;
current: Animated.Value<number>;
next?: Animated.AnimatedInterpolation;
current: Animated.AnimatedInterpolation;
gesture: Animated.Value;
layout: Layout;
insets: EdgeInsets;
gestureDirection: GestureDirection;
onOpen: (isFinished: boolean) => void;
onClose: (isFinished: boolean) => void;
onOpen: () => void;
onClose: () => void;
onTransitionStart?: (props: { closing: boolean }) => void;
onGestureBegin?: () => void;
onGestureCanceled?: () => void;
@@ -60,30 +61,10 @@ type Props = ViewProps & {
contentStyle?: StyleProp<ViewStyle>;
};
type AnimatedSpringConfig = {
damping: Animated.Value<number>;
mass: Animated.Value<number>;
stiffness: Animated.Value<number>;
restSpeedThreshold: Animated.Value<number>;
restDisplacementThreshold: Animated.Value<number>;
overshootClamping: Animated.Value<boolean>;
};
type AnimatedTimingConfig = {
duration: Animated.Value<number>;
};
const ANIMATION_SPRING = 0;
const ANIMATION_TIMING = 1;
const DIRECTION_VERTICAL = -1;
const DIRECTION_HORIZONTAL = 1;
const GESTURE_VELOCITY_IMPACT = 0.3;
const TRUE = 1;
const FALSE = 0;
const UNSET = -1;
/**
* The distance of touch start from the edge of the screen where the gesture will be recognized
@@ -91,169 +72,6 @@ const UNSET = -1;
const GESTURE_RESPONSE_DISTANCE_HORIZONTAL = 50;
const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
const {
clockRunning,
startClock,
stopClock,
spring,
timing,
call,
set,
multiply,
cond,
eq,
Clock,
Value,
} = Animated;
const springHelper = Animated.proc(
(
clock: Animated.Clock,
prevPosition: Animated.Value<number>,
position: Animated.Value<number>,
finished: Animated.Value<number>,
velocity: Animated.Value<number>,
time: Animated.Value<number>,
toValue: Animated.Adaptable<number>,
damping: Animated.Adaptable<number>,
mass: Animated.Adaptable<number>,
stiffness: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<number>,
restSpeedThreshold: Animated.Adaptable<number>,
restDisplacementThreshold: Animated.Adaptable<number>
) =>
spring(
clock,
{
// @ts-ignore
prevPosition,
position,
finished,
velocity,
time,
},
{
toValue,
damping,
mass,
stiffness,
overshootClamping,
restDisplacementThreshold,
restSpeedThreshold,
}
)
);
const runSpring = (
clock: Animated.Clock,
state: {
finished: Animated.Value<number>;
velocity: Animated.Value<number>;
position: Animated.Value<number>;
time: Animated.Value<number>;
},
config: {
toValue: Animated.Adaptable<number>;
damping: Animated.Adaptable<number>;
mass: Animated.Adaptable<number>;
stiffness: Animated.Adaptable<number>;
overshootClamping: Animated.Adaptable<boolean>;
restSpeedThreshold: Animated.Adaptable<number>;
restDisplacementThreshold: Animated.Adaptable<number>;
}
) =>
springHelper(
clock,
new Value(0),
state.position,
state.finished,
state.velocity,
state.time,
config.toValue,
config.damping,
config.mass,
config.stiffness,
config.overshootClamping as any,
config.restSpeedThreshold,
config.restDisplacementThreshold
);
function transformSpringConfigToAnimatedValues(
spec: TransitionSpec,
current?: AnimatedSpringConfig
): AnimatedSpringConfig {
if (current) {
if (spec.animation !== 'spring') {
return current;
}
const { config } = spec;
current.damping.setValue(config.damping);
current.stiffness.setValue(config.stiffness);
current.mass.setValue(config.mass);
current.restDisplacementThreshold.setValue(
config.restDisplacementThreshold
);
current.restSpeedThreshold.setValue(config.restSpeedThreshold);
current.overshootClamping.setValue(config.overshootClamping);
return current;
} else {
const config: Partial<SpringConfig> =
spec.animation !== 'spring' ? {} : spec.config;
return {
damping: new Animated.Value(config.damping || 0),
mass: new Animated.Value(config.mass || 0),
overshootClamping: new Animated.Value(config.overshootClamping !== false),
restDisplacementThreshold: new Animated.Value(
config.restDisplacementThreshold || 0
),
restSpeedThreshold: new Animated.Value(config.restSpeedThreshold || 0),
stiffness: new Animated.Value(config.stiffness || 0),
};
}
}
function transformTimingConfigToAnimatedValues(
spec: TransitionSpec,
current?: AnimatedTimingConfig & { easing: Animated.EasingFunction }
): AnimatedTimingConfig & { easing: Animated.EasingFunction } {
if (current) {
if (spec.animation !== 'timing') {
return current;
}
const { config } = spec;
current.duration.setValue(config.duration);
return current;
} else {
const config: Partial<TimingConfig> =
spec.animation !== 'timing' ? {} : spec.config;
return {
duration: new Animated.Value(config.duration || 0),
easing: config.easing !== undefined ? config.easing : Easing.linear,
};
}
}
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;
}
}
export default class Card extends React.Component<Props> {
static defaultProps = {
overlayEnabled: Platform.OS !== 'ios',
@@ -262,15 +80,12 @@ export default class Card extends React.Component<Props> {
gestureVelocityImpact: GESTURE_VELOCITY_IMPACT,
};
componentDidMount() {
this.animate({ closing: this.props.closing });
}
componentDidUpdate(prevProps: Props) {
const {
layout,
gestureEnabled,
gestureDirection,
gestureVelocityImpact,
transitionSpec,
closing,
} = this.props;
const { layout, gestureDirection, closing } = this.props;
const { width, height } = layout;
if (width !== prevProps.layout.width) {
@@ -281,179 +96,114 @@ export default class Card extends React.Component<Props> {
this.layout.height.setValue(height);
}
if (gestureEnabled !== prevProps.gestureEnabled) {
this.isGestureEnabled.setValue(gestureEnabled ? TRUE : FALSE);
}
if (gestureVelocityImpact !== prevProps.gestureVelocityImpact) {
this.gestureVelocityImpact.setValue(gestureVelocityImpact);
}
if (gestureDirection !== prevProps.gestureDirection) {
this.direction.setValue(
gestureDirection === 'vertical' ||
gestureDirection === 'vertical-inverted'
? DIRECTION_VERTICAL
: DIRECTION_HORIZONTAL
);
this.inverted.setValue(getInvertedMultiplier(gestureDirection));
}
if (transitionSpec.open !== prevProps.transitionSpec.open) {
if (
transitionSpec.open.animation !==
prevProps.transitionSpec.open.animation
) {
this.animationTypeOpen.setValue(
transitionSpec.open.animation === 'spring'
? ANIMATION_SPRING
: ANIMATION_TIMING
);
}
transformTimingConfigToAnimatedValues(
transitionSpec.open,
this.timingConfigOpen
);
transformSpringConfigToAnimatedValues(
transitionSpec.open,
this.springConfigOpen
);
}
if (transitionSpec.close !== prevProps.transitionSpec.close) {
if (
transitionSpec.close.animation !==
prevProps.transitionSpec.close.animation
) {
this.animationTypeClose.setValue(
transitionSpec.close.animation === 'spring'
? ANIMATION_SPRING
: ANIMATION_TIMING
);
}
transformTimingConfigToAnimatedValues(
transitionSpec.close,
this.timingConfigClose
);
transformSpringConfigToAnimatedValues(
transitionSpec.close,
this.springConfigClose
);
}
if (closing !== prevProps.closing) {
// If the style updates during render, setting the value here doesn't work
// We need to defer it a bit so the animation starts properly
requestAnimationFrame(() =>
requestAnimationFrame(() =>
this.isClosing.setValue(closing ? TRUE : FALSE)
)
);
if (
this.getAnimateToValue(this.props) !== this.getAnimateToValue(prevProps)
) {
// We need to trigger the animation when route was closed
// Thr route might have been closed by a `POP` action or by a gesture
// When route was closed due to a gesture, the animation would've happened already
// It's still important to trigger the animation so that `onClose` is called
// If `onClose` is not called, cleanup step won't be performed for gestures
this.animate({ closing });
}
}
componentWillUnmount(): void {
componentWillUnmount() {
this.handleEndInteraction();
// It might sometimes happen than animation will be unmounted
// during running. However, we need to invoke listener onClose
// manually in this case
if (this.isRunningAnimation || this.noAnimationStartedSoFar) {
this.props.onClose(false);
}
}
private isVisible = new Value<Binary>(TRUE);
private nextIsVisible = new Value<Binary | -1>(UNSET);
private isClosing = new Animated.Value(FALSE);
private animationTypeOpen = new Value<Binary>(
this.props.transitionSpec.open.animation === 'spring'
? ANIMATION_SPRING
: ANIMATION_TIMING
);
private animationTypeClose = new Value<Binary>(
this.props.transitionSpec.open.animation === 'spring'
? ANIMATION_SPRING
: ANIMATION_TIMING
);
private isGestureEnabled = new Value<Binary>(
this.props.gestureEnabled ? TRUE : FALSE
);
private isClosing = new Value<Binary>(FALSE);
private noAnimationStartedSoFar = true;
private isRunningAnimation = false;
private clock = new Clock();
private direction = new Value(
this.props.gestureDirection === 'vertical' ||
this.props.gestureDirection === 'vertical-inverted'
? DIRECTION_VERTICAL
: DIRECTION_HORIZONTAL
);
private inverted = new Value(
private inverted = new Animated.Value(
getInvertedMultiplier(this.props.gestureDirection)
);
private layout = {
width: new Value(this.props.layout.width),
height: new Value(this.props.layout.height),
width: new Animated.Value(this.props.layout.width),
height: new Animated.Value(this.props.layout.height),
};
private gestureVelocityImpact = new Value<number>(
this.props.gestureVelocityImpact
);
private springConfigOpen = transformSpringConfigToAnimatedValues(
this.props.transitionSpec.open
);
private springConfigClose = transformSpringConfigToAnimatedValues(
this.props.transitionSpec.close
);
private timingConfigOpen = transformTimingConfigToAnimatedValues(
this.props.transitionSpec.open
);
private timingConfigClose = transformTimingConfigToAnimatedValues(
this.props.transitionSpec.close
);
private distance = cond(
eq(this.direction, DIRECTION_VERTICAL),
this.layout.height,
this.layout.width
);
private gesture = new Value(0);
private offset = new Value(0);
private velocity = new Value(0);
private gestureState = new Value(0);
private isSwiping = new Value(FALSE);
private isSwipeCancelled = new Value(FALSE);
private isSwipeGesture = new Value(FALSE);
private transitionConfig = {
time: new Value(0),
frameTime: new Value(0),
finished: new Value(FALSE),
velocity: new Value(0),
};
private isSwiping = new Animated.Value(FALSE);
private interactionHandle: number | undefined;
private animate = ({
closing,
velocity,
}: {
closing?: boolean;
velocity?: number;
}) => {
const {
gesture,
transitionSpec,
onOpen,
onClose,
onTransitionStart,
} = this.props;
const toValue = this.getAnimateToValue({
...this.props,
closing,
});
const spec = closing ? transitionSpec.close : transitionSpec.open;
const animation =
spec.animation === 'spring' ? Animated.spring : Animated.timing;
this.setPointerEventsEnabled(!closing);
this.handleStartInteraction();
onTransitionStart?.({ closing: Boolean(closing) });
animation(gesture, {
...spec.config,
velocity,
toValue,
useNativeDriver: true,
isInteraction: false,
}).start(({ finished }) => {
this.handleEndInteraction();
if (finished) {
if (closing) {
onClose();
} else {
onOpen();
}
}
});
};
private getAnimateToValue = ({
closing,
layout,
gestureDirection,
}: {
closing?: boolean;
layout: Layout;
gestureDirection: GestureDirection;
}) => {
if (!closing) {
return 0;
}
return getDistanceForDirection(layout, gestureDirection);
};
private setPointerEventsEnabled = (enabled: boolean) => {
const pointerEvents = enabled ? 'box-none' : 'none';
this.content.current &&
this.content.current.setNativeProps({ pointerEvents });
};
private content = React.createRef<View>();
private handleStartInteraction = () => {
if (this.interactionHandle === undefined) {
this.interactionHandle = InteractionManager.createInteractionHandle();
@@ -467,221 +217,68 @@ export default class Card extends React.Component<Props> {
}
};
private handleTransitionEnd = () => {
this.handleEndInteraction();
private handleGestureStateChange = ({
nativeEvent,
}: PanGestureHandlerGestureEvent) => {
const {
layout,
onGestureBegin,
onGestureCanceled,
onGestureEnd,
gestureDirection,
gestureVelocityImpact,
} = this.props;
this.isRunningAnimation = false;
this.interpolatedStyle = this.getInterpolatedStyle(
this.props.styleInterpolator,
this.props.index,
this.props.current,
this.props.next,
this.props.layout,
this.props.insets.top,
this.props.insets.right,
this.props.insets.bottom,
this.props.insets.left
);
};
private listenerOnStart = call(
[this.isVisible],
([value]: ReadonlyArray<Binary>) => {
this.handleStartInteraction();
const { onTransitionStart } = this.props;
this.noAnimationStartedSoFar = false;
this.isRunningAnimation = true;
onTransitionStart && onTransitionStart({ closing: !value });
}
);
private listenerOnEnd = call(
[this.isVisible],
([value]: ReadonlyArray<Binary>) => {
const isOpen = Boolean(value);
const { onOpen, onClose } = this.props;
this.handleTransitionEnd();
if (isOpen) {
onOpen(true);
} else {
onClose(true);
}
}
);
private listenerOnCancel = call([], () => {
this.isRunningAnimation = false;
});
private listenerOnSwipe = call(
[this.isSwiping, this.isSwipeCancelled],
([isSwiping, isSwipeCancelled]: readonly Binary[]) => {
const { onGestureBegin, onGestureEnd, onGestureCanceled } = this.props;
if (isSwiping === TRUE) {
switch (nativeEvent.state) {
case GestureState.BEGAN:
this.isSwiping.setValue(TRUE);
this.handleStartInteraction();
onGestureBegin && onGestureBegin();
} else {
onGestureBegin?.();
break;
case GestureState.CANCELLED:
this.isSwiping.setValue(FALSE);
this.handleEndInteraction();
onGestureCanceled?.();
break;
case GestureState.END: {
this.isSwiping.setValue(FALSE);
if (isSwipeCancelled === TRUE) {
onGestureCanceled && onGestureCanceled();
let distance;
let translation;
let velocity;
if (
gestureDirection === 'vertical' ||
gestureDirection === 'vertical-inverted'
) {
distance = layout.height;
translation = nativeEvent.translationY;
velocity = nativeEvent.velocityY;
} else {
onGestureEnd && onGestureEnd();
distance = layout.width;
translation = nativeEvent.translationX;
velocity = nativeEvent.velocityX;
}
const closing =
Math.abs(translation + velocity * gestureVelocityImpact) >
distance / 2
? velocity !== 0 || translation !== 0
: false;
this.animate({ closing, velocity });
onGestureEnd?.();
break;
}
}
);
private listenerOnTransitionEnd = call([], this.handleTransitionEnd);
private runAnimation = () => {
const state = {
position: this.props.current,
...this.transitionConfig,
};
return cond(
eq(this.isVisible, TRUE),
cond(
eq(this.animationTypeOpen, ANIMATION_SPRING),
runSpring(this.clock, state, {
...this.springConfigOpen,
toValue: this.isVisible,
}),
timing(this.clock, state, {
...this.timingConfigOpen,
toValue: this.isVisible,
})
),
cond(
eq(this.animationTypeClose, ANIMATION_SPRING),
runSpring(this.clock, state, {
...this.springConfigClose,
toValue: this.isVisible,
}),
timing(this.clock, state, {
...this.timingConfigClose,
toValue: this.isVisible,
})
)
);
};
private exec = animate(
clockRunning(this.clock),
startClock(this.clock),
stopClock(this.clock),
this.distance,
this.gesture,
this.gestureState,
this.gestureVelocityImpact,
this.isClosing,
this.isGestureEnabled,
this.isSwipeCancelled,
this.isSwipeGesture,
this.isSwiping,
this.isVisible,
this.listenerOnCancel,
this.listenerOnEnd,
this.listenerOnStart,
this.listenerOnSwipe,
this.listenerOnTransitionEnd,
this.nextIsVisible,
this.offset,
this.props.current,
this.runAnimation(),
this.transitionConfig.finished,
this.transitionConfig.frameTime,
this.transitionConfig.time,
this.transitionConfig.velocity,
this.velocity
);
private handleGestureEventHorizontal = Animated.event([
{
nativeEvent: {
translationX: (x: Animated.Node<number>) =>
set(this.gesture, multiply(x, this.inverted)),
velocityX: (x: Animated.Node<number>) =>
set(this.velocity, multiply(x, this.inverted)),
state: this.gestureState,
},
},
]);
private handleGestureEventVertical = Animated.event([
{
nativeEvent: {
translationY: (y: Animated.Node<number>) =>
set(this.gesture, multiply(y, this.inverted)),
velocityY: (y: Animated.Node<number>) =>
set(this.velocity, multiply(y, this.inverted)),
state: this.gestureState,
},
},
]);
// We need to ensure that this style doesn't change unless absolutely needs to
// Changing it too often will result in huge frame drops due to detaching and attaching
// Changing it during an animations can result in unexpected results
private getInterpolatedStyle = memoize(
(
styleInterpolator: StackCardStyleInterpolator,
index: number,
current: Animated.Node<number>,
next: Animated.Node<number> | undefined,
layout: Layout,
insetTop: number,
insetRight: number,
insetBottom: number,
insetLeft: number
) =>
styleInterpolator({
index,
current: { progress: current },
next: next && { progress: next },
closing: this.isClosing,
swiping: this.isSwiping,
inverted: this.inverted,
layouts: {
screen: layout,
},
insets: {
top: insetTop,
right: insetRight,
bottom: insetBottom,
left: insetLeft,
},
})
);
// Keep track of the style in a property to avoid changing the animated node when deps change
// The style shouldn't change in the middle of the animation and should refer to what was there at the start of it
// Which will be the last value when just before the render which started the animation
// We need to make sure to update this when the running animation ends
private interpolatedStyle = this.getInterpolatedStyle(
this.props.styleInterpolator,
this.props.index,
this.props.current,
this.props.next,
this.props.layout,
this.props.insets.top,
this.props.insets.right,
this.props.insets.bottom,
this.props.insets.left
);
// Keep track of the animation context when deps changes.
private getCardAnimationContext = memoize(
(
index: number,
current: Animated.Node<number>,
next: Animated.Node<number> | undefined,
current: Animated.AnimatedInterpolation,
next: Animated.AnimatedInterpolation | undefined,
layout: Layout,
insetTop: number,
insetRight: number,
@@ -712,12 +309,10 @@ export default class Card extends React.Component<Props> {
const distance =
gestureDirection === 'vertical' ||
gestureDirection === 'vertical-inverted'
? gestureResponseDistance &&
gestureResponseDistance.vertical !== undefined
? gestureResponseDistance?.vertical !== undefined
? gestureResponseDistance.vertical
: GESTURE_RESPONSE_DISTANCE_VERTICAL
: gestureResponseDistance &&
gestureResponseDistance.horizontal !== undefined
: gestureResponseDistance?.horizontal !== undefined
? gestureResponseDistance.horizontal
: GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
@@ -757,10 +352,10 @@ export default class Card extends React.Component<Props> {
render() {
const {
active,
styleInterpolator,
index,
current,
gesture,
next,
layout,
insets,
@@ -774,19 +369,18 @@ export default class Card extends React.Component<Props> {
...rest
} = this.props;
if (!this.isRunningAnimation) {
this.interpolatedStyle = this.getInterpolatedStyle(
styleInterpolator,
index,
current,
next,
layout,
insets.top,
insets.right,
insets.bottom,
insets.left
);
}
const interpolatedStyle = styleInterpolator({
index,
current: { progress: current },
next: next && { progress: next },
closing: this.isClosing,
swiping: this.isSwiping,
inverted: this.inverted,
layouts: {
screen: layout,
},
insets,
});
const animationContext = this.getCardAnimationContext(
index,
@@ -804,12 +398,21 @@ export default class Card extends React.Component<Props> {
cardStyle,
overlayStyle,
shadowStyle,
} = this.interpolatedStyle;
} = interpolatedStyle;
const handleGestureEvent = gestureEnabled
? gestureDirection === 'vertical'
? this.handleGestureEventVertical
: this.handleGestureEventHorizontal
? Animated.event(
[
{
nativeEvent:
gestureDirection === 'vertical' ||
gestureDirection === 'vertical-inverted'
? { translationY: gesture }
: { translationX: gesture },
},
],
{ useNativeDriver: true }
)
: undefined;
const { backgroundColor } = StyleSheet.flatten(contentStyle || {});
@@ -820,10 +423,6 @@ export default class Card extends React.Component<Props> {
return (
<StackGestureContext.Provider value={this.gestureRef}>
<View pointerEvents="box-none" {...rest}>
<Animated.Code
key={gestureEnabled ? 'gesture-code' : 'no-gesture-code'}
exec={this.exec}
/>
{overlayEnabled && overlayStyle ? (
<Animated.View
pointerEvents="none"
@@ -838,7 +437,7 @@ export default class Card extends React.Component<Props> {
ref={this.gestureRef}
enabled={layout.width !== 0 && gestureEnabled}
onGestureEvent={handleGestureEvent}
onHandlerStateChange={handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
{...this.gestureActivationCriteria()}
>
<Animated.View style={[styles.container, cardStyle]}>
@@ -854,15 +453,11 @@ export default class Card extends React.Component<Props> {
pointerEvents="none"
/>
) : null}
<PointerEventsView
active={active}
progress={this.props.current}
style={[styles.content, contentStyle]}
>
<View ref={this.content} style={[styles.content, contentStyle]}>
<StackCardAnimationContext.Provider value={animationContext}>
{children}
</StackCardAnimationContext.Provider>
</PointerEventsView>
</View>
</Animated.View>
</PanGestureHandler>
</Animated.View>

View File

@@ -1,250 +0,0 @@
import Animated from 'react-native-reanimated';
import { State as GestureState } from 'react-native-gesture-handler';
export type Binary = 0 | 1;
const TRUE_NODE = new Animated.Value(1);
const FALSE_NODE = new Animated.Value(0);
const UNSET_NODE = new Animated.Value(-1);
const NOOP_NODE = FALSE_NODE;
const {
abs,
add,
and,
block,
cond,
divide,
eq,
greaterThan,
lessThan,
max,
min,
multiply,
neq,
onChange,
set,
sub,
proc,
} = Animated;
const proc2 = proc(
(
clockIsRunning: Animated.Node<number>,
clockStart: Animated.Node<number>,
clockStop: Animated.Node<number>,
distance: Animated.Node<number>,
gesture: Animated.Value<number>,
gestureState: Animated.Node<number>,
gestureVelocityImpact: Animated.Node<number>,
isGestureEnabled: Animated.Node<number>,
isSwipeCancelled: Animated.Value<number>,
isSwipeGesture: Animated.Value<number>,
isSwiping: Animated.Value<number>,
isVisible: Animated.Value<number>,
listenerOnCancel: Animated.Node<number>,
listenerOnEnd: Animated.Node<number>,
listenerOnStart: Animated.Node<number>,
listenerOnSwipe: Animated.Node<number>,
offset: Animated.Value<number>,
position: Animated.Value<number>,
runAnimation: Animated.Node<number>,
transitionFinished: Animated.Value<number>,
transitionFrameTime: Animated.Value<number>,
transitionTime: Animated.Value<number>,
transitionVelocity: Animated.Value<number>,
velocity: Animated.Value<number>
) => {
const runTransition = (transitionTo: Binary | Animated.Node<number>) => {
return cond(eq(position, transitionTo), NOOP_NODE, [
cond(clockIsRunning, NOOP_NODE, [
// The velocity value is ideal for translating the whole screen
// But since we have 0-1 scale, we need to adjust the velocity
set(
transitionVelocity,
multiply(cond(distance, divide(velocity, distance), 0), -1)
),
// Animation wasn't running before
// Set the initial values and start the clock
set(transitionFrameTime, 0),
set(transitionTime, 0),
set(transitionFinished, FALSE_NODE),
set(isVisible, transitionTo),
clockStart,
listenerOnStart,
]),
runAnimation,
cond(transitionFinished, [
// Reset values
set(isSwipeGesture, FALSE_NODE),
set(gesture, FALSE_NODE),
set(velocity, FALSE_NODE),
// When the animation finishes, stop the clock
clockStop,
listenerOnEnd,
]),
]);
};
return block([
cond(
isGestureEnabled,
[
onChange(isSwiping, listenerOnSwipe),
cond(
eq(gestureState, GestureState.ACTIVE),
[
cond(isSwiping, NOOP_NODE, [
// We weren't dragging before, set it to true
set(isSwipeCancelled, FALSE_NODE),
set(isSwiping, TRUE_NODE),
set(isSwipeGesture, TRUE_NODE),
// Also update the drag offset to the last position
set(offset, position),
]),
// Update position with next offset + gesture distance
set(
position,
min(
max(
sub(
offset,
cond(distance, divide(gesture, distance), TRUE_NODE)
),
FALSE_NODE
),
TRUE_NODE
)
),
// Stop animations while we're dragging
cond(clockIsRunning, listenerOnCancel),
clockStop,
],
[
set(isSwipeCancelled, eq(gestureState, GestureState.CANCELLED)),
set(isSwiping, FALSE_NODE),
runTransition(
cond(
greaterThan(
abs(
add(gesture, multiply(velocity, gestureVelocityImpact))
),
divide(distance, 2)
),
cond(
lessThan(cond(eq(velocity, 0), gesture, velocity), 0),
TRUE_NODE,
FALSE_NODE
),
isVisible
)
),
]
),
],
runTransition(isVisible)
),
]);
}
);
const proc1 = proc(
(
nextIsVisible: Animated.Value<number>,
isVisible: Animated.Value<number>,
clockIsRunning: Animated.Node<number>,
listenerOnTransitionEnd: Animated.Node<number>,
clockStop: Animated.Node<number>,
gesture: Animated.Value<number>
) => {
return cond(
and(neq(nextIsVisible, UNSET_NODE), neq(nextIsVisible, isVisible)),
[
// Stop any running animations
cond(clockIsRunning, [listenerOnTransitionEnd, clockStop]),
set(gesture, FALSE_NODE),
// Update the index to trigger the transition
set(isVisible, nextIsVisible),
set(nextIsVisible, UNSET_NODE),
]
);
}
);
// For a reason which I'm currently not understanding
// Animated nodes behave incorrectly in procs in some cases, so
// by bisecting I figured out that some lines need to be outside procs.
// I split the rest of lines into 3 procs in order to make it work correctly.
// Probably it's an error in native reanimated code,
// but I have no proof for that.
export default (
clockIsRunning: Animated.Node<number>,
clockStart: Animated.Node<number>,
clockStop: Animated.Node<number>,
distance: Animated.Node<number>,
gesture: Animated.Value<number>,
gestureState: Animated.Node<number>,
gestureVelocityImpact: Animated.Node<number>,
isClosing: Animated.Node<number>,
isGestureEnabled: Animated.Node<number>,
isSwipeCancelled: Animated.Value<number>,
isSwipeGesture: Animated.Value<number>,
isSwiping: Animated.Value<number>,
isVisible: Animated.Value<number>,
listenerOnCancel: Animated.Node<number>,
listenerOnEnd: Animated.Node<number>,
listenerOnStart: Animated.Node<number>,
listenerOnSwipe: Animated.Node<number>,
listenerOnTransitionEnd: Animated.Node<number>,
nextIsVisible: Animated.Value<number>,
offset: Animated.Value<number>,
position: Animated.Value<number>,
runAnimation: Animated.Node<number>,
transitionFinished: Animated.Value<number>,
transitionFrameTime: Animated.Value<number>,
transitionTime: Animated.Value<number>,
transitionVelocity: Animated.Value<number>,
velocity: Animated.Value<number>
) => {
return block([
onChange(isClosing, cond(isClosing, set(nextIsVisible, FALSE_NODE))),
onChange(
nextIsVisible,
proc1(
nextIsVisible,
isVisible,
clockIsRunning,
listenerOnTransitionEnd,
clockStop,
gesture
)
),
proc2(
clockIsRunning,
clockStart,
clockStop,
distance,
gesture,
gestureState,
gestureVelocityImpact,
isGestureEnabled,
isSwipeCancelled,
isSwipeGesture,
isSwiping,
isVisible,
listenerOnCancel,
listenerOnEnd,
listenerOnStart,
listenerOnSwipe,
offset,
position,
runAnimation,
transitionFinished,
transitionFrameTime,
transitionTime,
transitionVelocity,
velocity
),
]);
};

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import Animated from 'react-native-reanimated';
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import { StackNavigationState } from '@react-navigation/routers';
import { Route, useTheme } from '@react-navigation/native';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
@@ -13,7 +12,7 @@ type Props = TransitionPreset & {
focused: boolean;
closing: boolean;
layout: Layout;
current: Animated.Value<number>;
gesture: Animated.Value;
previousScene?: Scene<Route<string>>;
scene: Scene<Route<string>>;
state: StackNavigationState;
@@ -31,7 +30,6 @@ type Props = TransitionPreset & {
renderScene: (props: { route: Route<string> }) => React.ReactNode;
onOpenRoute: (props: { route: Route<string> }) => void;
onCloseRoute: (props: { route: Route<string> }) => void;
onGoBack: (props: { route: Route<string> }) => void;
onTransitionStart?: (
props: { route: Route<string> },
closing: boolean
@@ -59,7 +57,7 @@ export default function CardContainer({
cardStyle,
cardStyleInterpolator,
closing,
current,
gesture,
floatingHeaderHeight,
focused,
gestureDirection,
@@ -74,7 +72,6 @@ export default function CardContainer({
index,
layout,
onCloseRoute,
onGoBack,
onOpenRoute,
onPageChangeCancel,
onPageChangeConfirm,
@@ -92,25 +89,28 @@ export default function CardContainer({
state,
transitionSpec,
}: Props) {
React.useEffect(() => {
onPageChangeConfirm?.();
}, [active, onPageChangeConfirm]);
const handleOpen = () => {
onTransitionEnd && onTransitionEnd({ route: scene.route }, false);
onTransitionEnd?.({ route: scene.route }, false);
onOpenRoute({ route: scene.route });
};
const handleClose = () => {
onTransitionEnd && onTransitionEnd({ route: scene.route }, true);
onTransitionEnd?.({ route: scene.route }, true);
onCloseRoute({ route: scene.route });
};
const handleTransitionStart = ({ closing }: { closing: boolean }) => {
if (closing) {
onPageChangeConfirm && onPageChangeConfirm();
if (active && closing) {
onPageChangeConfirm?.();
} else {
onPageChangeCancel && onPageChangeCancel();
onPageChangeCancel?.();
}
onTransitionStart && onTransitionStart({ route: scene.route }, closing);
closing && onGoBack({ route: scene.route });
onTransitionStart?.({ route: scene.route }, closing);
};
const insets = {
@@ -125,11 +125,11 @@ export default function CardContainer({
return (
<Card
index={index}
active={active}
gestureDirection={gestureDirection}
layout={layout}
insets={insets}
current={current}
gesture={gesture}
current={scene.progress.current}
next={scene.progress.next}
closing={closing}
onOpen={handleOpen}

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import {
Animated,
View,
StyleSheet,
LayoutChangeEvent,
@@ -7,10 +8,9 @@ import {
Platform,
ViewProps,
} from 'react-native';
import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context';
// eslint-disable-next-line import/no-unresolved
import * as Screens from 'react-native-screens'; // Import with * as to prevent getters being called
import { ScreenContainer, Screen, screensEnabled } from 'react-native-screens'; // Import with * as to prevent getters being called
import { Route } from '@react-navigation/native';
import { StackNavigationState } from '@react-navigation/routers';
@@ -23,27 +23,29 @@ import {
} from '../../TransitionConfigs/TransitionPresets';
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
import getDistanceForDirection from '../../utils/getDistanceForDirection';
import {
Layout,
StackHeaderMode,
StackCardMode,
Scene,
StackDescriptorMap,
StackNavigationOptions,
StackDescriptor,
} from '../../types';
type ProgressValues = {
[key: string]: Animated.Value<number>;
type GestureValues = {
[key: string]: Animated.Value;
};
type Props = {
mode: 'card' | 'modal';
mode: StackCardMode;
insets: EdgeInsets;
state: StackNavigationState;
descriptors: StackDescriptorMap;
routes: Route<string>[];
openingRouteKeys: string[];
closingRouteKeys: string[];
onGoBack: (props: { route: Route<string> }) => void;
onOpenRoute: (props: { route: Route<string> }) => void;
onCloseRoute: (props: { route: Route<string> }) => void;
getPreviousRoute: (props: {
@@ -67,18 +69,16 @@ type State = {
routes: Route<string>[];
descriptors: StackDescriptorMap;
scenes: Scene<Route<string>>[];
progress: ProgressValues;
gestures: GestureValues;
layout: Layout;
floatingHeaderHeights: Record<string, number>;
};
const EPSILON = 1e-5;
const dimensions = Dimensions.get('window');
const layout = { width: dimensions.width, height: dimensions.height };
let AnimatedScreen: React.ComponentType<ViewProps & {
active: number | Animated.Node<number>;
}>;
const MaybeScreenContainer = ({
enabled,
...rest
@@ -86,8 +86,8 @@ const MaybeScreenContainer = ({
enabled: boolean;
children: React.ReactNode;
}) => {
if (Platform.OS !== 'ios' && enabled && Screens.screensEnabled()) {
return <Screens.ScreenContainer {...rest} />;
if (Platform.OS !== 'ios' && enabled && screensEnabled()) {
return <ScreenContainer {...rest} />;
}
return <View {...rest} />;
@@ -99,23 +99,17 @@ const MaybeScreen = ({
...rest
}: ViewProps & {
enabled: boolean;
active: number | Animated.Node<number>;
active: number | Animated.AnimatedInterpolation;
children: React.ReactNode;
}) => {
if (Platform.OS !== 'ios' && enabled && Screens.screensEnabled()) {
AnimatedScreen =
AnimatedScreen || Animated.createAnimatedComponent(Screens.NativeScreen);
return <AnimatedScreen active={active} {...rest} />;
if (Platform.OS !== 'ios' && enabled && screensEnabled()) {
// @ts-ignore
return <Screen active={active} {...rest} />;
}
return <View {...rest} />;
};
const { cond, eq } = Animated;
const ANIMATED_ONE = new Animated.Value(1);
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
const getFloatingHeaderHeights = (
@@ -143,6 +137,43 @@ const getFloatingHeaderHeights = (
}, {});
};
const getDistanceFromOptions = (
mode: StackCardMode,
layout: Layout,
descriptor?: StackDescriptor
) => {
const {
gestureDirection = mode === 'modal'
? ModalTransition.gestureDirection
: DefaultTransition.gestureDirection,
} = descriptor?.options || {};
return getDistanceForDirection(layout, gestureDirection);
};
const getProgressFromGesture = (
mode: StackCardMode,
gesture: Animated.Value,
layout: Layout,
descriptor?: StackDescriptor
) => {
const distance = getDistanceFromOptions(mode, layout, descriptor);
if (distance > 0) {
return gesture.interpolate({
inputRange: [0, distance],
outputRange: [1, 0],
extrapolate: 'clamp',
});
}
return gesture.interpolate({
inputRange: [distance, 0],
outputRange: [0, 1],
extrapolate: 'clamp',
});
};
export default class CardStack extends React.Component<Props, State> {
static getDerivedStateFromProps(props: Props, state: State) {
if (
@@ -152,17 +183,17 @@ export default class CardStack extends React.Component<Props, State> {
return null;
}
const progress = props.routes.reduce<ProgressValues>((acc, curr) => {
const gestures = props.routes.reduce<GestureValues>((acc, curr) => {
const descriptor = props.descriptors[curr.key];
const { animationEnabled } = descriptor?.options || {};
acc[curr.key] =
state.progress[curr.key] ||
state.gestures[curr.key] ||
new Animated.Value(
props.openingRouteKeys.includes(curr.key) &&
descriptor &&
descriptor.options.animationEnabled !== false
? 0
: 1
animationEnabled !== false
? getDistanceFromOptions(props.mode, state.layout, descriptor)
: 0
);
return acc;
@@ -174,41 +205,79 @@ export default class CardStack extends React.Component<Props, State> {
const previousRoute = self[index - 1];
const nextRoute = self[index + 1];
const current = progress[route.key];
const previous = previousRoute
? progress[previousRoute.key]
: undefined;
const next = nextRoute ? progress[nextRoute.key] : undefined;
const oldScene = state.scenes[index];
const currentGesture = gestures[route.key];
const previousGesture = previousRoute
? gestures[previousRoute.key]
: undefined;
const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
const descriptor =
props.descriptors[route.key] ||
state.descriptors[route.key] ||
(oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
const nextDescriptor =
props.descriptors[nextRoute?.key] ||
state.descriptors[nextRoute?.key];
const previousDescriptor =
props.descriptors[previousRoute?.key] ||
state.descriptors[previousRoute?.key];
const scene = {
route,
previous: previousRoute,
descriptor:
props.descriptors[route.key] ||
state.descriptors[route.key] ||
(oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR),
descriptor,
progress: {
current,
next,
previous,
current: getProgressFromGesture(
props.mode,
currentGesture,
state.layout,
descriptor
),
next: nextGesture
? getProgressFromGesture(
props.mode,
nextGesture,
state.layout,
nextDescriptor
)
: undefined,
previous: previousGesture
? getProgressFromGesture(
props.mode,
previousGesture,
state.layout,
previousDescriptor
)
: undefined,
},
__memo: [
route,
state.layout,
descriptor,
nextDescriptor,
previousDescriptor,
currentGesture,
nextGesture,
previousGesture,
],
};
if (
oldScene &&
scene.route === oldScene.route &&
scene.progress.current === oldScene.progress.current &&
scene.progress.next === oldScene.progress.next &&
scene.progress.previous === oldScene.progress.previous &&
scene.descriptor === oldScene.descriptor
scene.__memo.every((it, i) => {
// @ts-ignore
return oldScene.__memo[i] === it;
})
) {
return oldScene;
}
return scene;
}),
progress,
gestures,
descriptors: props.descriptors,
floatingHeaderHeights: getFloatingHeaderHeights(
props.routes,
@@ -223,7 +292,7 @@ export default class CardStack extends React.Component<Props, State> {
state: State = {
routes: [],
scenes: [],
progress: {},
gestures: {},
layout,
descriptors: this.props.descriptors,
// Used when card's header is null and mode is float to make transition
@@ -290,7 +359,6 @@ export default class CardStack extends React.Component<Props, State> {
closingRouteKeys,
onOpenRoute,
onCloseRoute,
onGoBack,
getPreviousRoute,
getGesturesEnabled,
renderHeader,
@@ -303,7 +371,7 @@ export default class CardStack extends React.Component<Props, State> {
onPageChangeCancel,
} = this.props;
const { scenes, layout, progress, floatingHeaderHeights } = this.state;
const { scenes, layout, gestures, floatingHeaderHeights } = this.state;
const focusedRoute = state.routes[state.index];
const focusedDescriptor = descriptors[focusedRoute.key];
@@ -335,18 +403,22 @@ export default class CardStack extends React.Component<Props, State> {
>
{routes.map((route, index, self) => {
const focused = focusedRoute.key === route.key;
const current = progress[route.key];
const gesture = gestures[route.key];
const scene = scenes[index];
const next = self[index + 1]
? progress[self[index + 1].key]
: ANIMATED_ONE;
// Display current screen and a screen beneath. On Android screen beneath is hidden on animation finished bs of RNS's issue.
// Display current screen and a screen beneath.
// On Android screen beneath is hidden on animation finished bs of RNS's issue.
const isScreenActive =
index === self.length - 1
? 1
: Platform.OS === 'android'
? cond(eq(next, 1), 0, 1)
? scene.progress.next
? scene.progress.next.interpolate({
inputRange: [0, 1 - EPSILON, 1],
outputRange: [1, 1, 0],
extrapolate: 'clamp',
})
: 1
: index === self.length - 2
? 1
: 0;
@@ -372,6 +444,7 @@ export default class CardStack extends React.Component<Props, State> {
: ({} as StackNavigationOptions);
let transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
@@ -389,6 +462,7 @@ export default class CardStack extends React.Component<Props, State> {
if (nextScene) {
const {
animationEnabled,
gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = animationEnabled === false
? forNoAnimationCard
@@ -399,6 +473,7 @@ export default class CardStack extends React.Component<Props, State> {
: ({} as StackNavigationOptions);
transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
@@ -427,7 +502,7 @@ export default class CardStack extends React.Component<Props, State> {
focused={focused}
closing={closingRouteKeys.includes(route.key)}
layout={layout}
current={current}
gesture={gesture}
scene={scene}
previousScene={scenes[index - 1]}
state={state}
@@ -453,10 +528,8 @@ export default class CardStack extends React.Component<Props, State> {
onCloseRoute={onCloseRoute}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
onGoBack={onGoBack}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
gestureVelocityImpact={gestureVelocityImpact}
gestureDirection={gestureDirection}
{...transitionConfig}
/>
</MaybeScreen>

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