Compare commits

..

23 Commits

Author SHA1 Message Date
satyajit.happy
5303e8ffb5 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.13
 - @react-navigation/compat@5.0.0-alpha.7
 - @react-navigation/core@5.0.0-alpha.14
 - @react-navigation/drawer@5.0.0-alpha.13
 - @react-navigation/example@5.0.0-alpha.12
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.12
 - @react-navigation/material-top-tabs@5.0.0-alpha.11
 - @react-navigation/native@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.24
2019-10-06 16:44:17 +02:00
satyajit.happy
ba6b6ae025 feat: drop header: null in favor of more explitit headerShown option 2019-10-06 15:56:30 +02:00
satyajit.happy
16079d1050 fix: actually expose gestureVelocityImpact in the public API 2019-10-06 04:17:49 +02:00
satyajit.happy
b4a76814c6 fix: use next screen's animation when not focused. fixes #87 2019-10-06 04:13:13 +02:00
Michał Osadnik
8294efc8f4 feat: add gestureVelocityImpact as a prop for stack (#123) 2019-10-06 00:05:42 +02:00
Michał Osadnik
a27ade8881 fix: handling vertical gesture in RTL (#122) 2019-10-06 00:00:43 +02:00
satyajit.happy
615b523d26 fix: don't recompute if routes didn't change 2019-10-05 22:50:09 +02:00
satyajit.happy
070c46ba64 chore: fix react and react-native versions 2019-10-04 14:36:49 +02:00
satyajit.happy
d8394cf919 chore: publish
- @react-navigation/example@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.23
2019-10-04 00:54:40 +02:00
satyajit.happy
a7c4a4d7cd fix: fix vertical gesture 2019-10-04 00:53:26 +02:00
satyajit.happy
282b465ae1 chore: add example for modal presentation 2019-10-04 00:12:56 +02:00
satyajit.happy
6f5f4b7d35 fix: fix passing insets to interpolator 2019-10-04 00:01:45 +02:00
satyajit.happy
d75915d1f3 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.12
 - @react-navigation/compat@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.13
 - @react-navigation/drawer@5.0.0-alpha.12
 - @react-navigation/example@5.0.0-alpha.10
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.11
 - @react-navigation/material-top-tabs@5.0.0-alpha.10
 - @react-navigation/native@5.0.0-alpha.10
 - @react-navigation/stack@5.0.0-alpha.22
2019-10-03 21:33:06 +02:00
satyajit.happy
832ed882bc refactor: use react-native-safe-area-context 2019-10-03 21:31:09 +02:00
satyajit.happy
8318c49331 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.11
 - @react-navigation/compat@5.0.0-alpha.5
 - @react-navigation/core@5.0.0-alpha.12
 - @react-navigation/drawer@5.0.0-alpha.11
 - @react-navigation/example@5.0.0-alpha.9
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.10
 - @react-navigation/material-top-tabs@5.0.0-alpha.9
 - @react-navigation/native@5.0.0-alpha.9
 - @react-navigation/routers@5.0.0-alpha.9
 - @react-navigation/stack@5.0.0-alpha.21
2019-10-03 19:47:41 +02:00
satyajit.happy
ece6e3899e fix: add missing React import 2019-10-03 19:07:56 +02:00
satyajit.happy
da944ccef9 fix: fix header buttons not clickable on Android. fixes #108 2019-10-03 19:07:21 +02:00
satyajit.happy
bc3586ae3e fix: keep the routes we are closing or replacing 2019-10-03 18:44:18 +02:00
satyajit.happy
19be2b4bbc fix: don't throw when switching navigator. fixes #91 2019-10-03 17:51:13 +02:00
satyajit.happy
a8851b730d chore: upgrade deps 2019-10-03 17:35:24 +02:00
Michał Osadnik
7a5bcb446b feat: add a getRootState method (#119) 2019-10-01 23:17:28 +02:00
satyajit.happy
1345a8fec6 chore: upgrade eslint config 2019-09-28 11:58:21 +02:00
satyajit.happy
7393464515 fix: don't merge state with existing state during reset. fixes #111 2019-09-28 11:49:45 +02:00
76 changed files with 3129 additions and 2244 deletions

View File

@@ -1,14 +0,0 @@
{
"git": {
"commitMessage": "chore: release %s",
"tagName": "v%s"
},
"github": {
"release": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular"
}
}
}

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = { module.exports = {
presets: [ presets: [
[ [

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = { module.exports = {
extends: ['@commitlint/config-conventional'], extends: ['@commitlint/config-conventional'],
}; };

View File

@@ -23,27 +23,26 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.5.5", "@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.5.5", "@babel/preset-env": "^7.6.2",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3", "@babel/preset-typescript": "^7.6.0",
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.6.2",
"@commitlint/config-conventional": "^8.0.0", "@commitlint/config-conventional": "^8.2.0",
"@types/jest": "^24.0.13", "@types/jest": "^24.0.13",
"codecov": "^3.5.0", "codecov": "^3.6.1",
"commitlint": "^8.0.0", "commitlint": "^8.2.0",
"core-js": "^3.2.1", "core-js": "^3.2.1",
"eslint": "^5.16.0", "eslint": "^6.5.1",
"eslint-config-satya164": "^2.4.1", "eslint-config-satya164": "^3.1.2",
"husky": "^2.4.0", "husky": "^3.0.8",
"jest": "^24.8.0", "jest": "^24.8.0",
"lerna": "^3.16.4", "lerna": "^3.16.4",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"resolutions": { "resolutions": {
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10"
"react-native-safe-area-view": "0.14.7"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.12...@react-navigation/bottom-tabs@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.11...@react-navigation/bottom-tabs@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.10...@react-navigation/bottom-tabs@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.9...@react-navigation/bottom-tabs@5.0.0-alpha.10) (2019-09-27) # [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.9...@react-navigation/bottom-tabs@5.0.0-alpha.10) (2019-09-27)

View File

@@ -10,6 +10,30 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/bottom-tabs yarn add @react-navigation/core @react-navigation/bottom-tabs
``` ```
Now we need to install [`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-safe-area-context
```
If you are not using Expo, run the following:
```sh
yarn add react-native-safe-area-context
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
## Usage ## Usage
```js ```js

View File

@@ -10,7 +10,7 @@
"android", "android",
"tab" "tab"
], ],
"version": "5.0.0-alpha.10", "version": "5.0.0-alpha.13",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,22 +33,23 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8", "@react-navigation/routers": "^5.0.0-alpha.9"
"react-native-safe-area-view": "^0.14.6"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10",
"typescript": "^3.5.3" "react-native-safe-area-context": "^0.3.6",
"typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",
"react": "*", "react": "*",
"react-native": "*" "react-native": "*",
"react-native-safe-area-context": "^0.3.6"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {
"source": "src", "source": "src",

View File

@@ -6,7 +6,6 @@ import {
TextStyle, TextStyle,
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { import {
NavigationHelpers, NavigationHelpers,
NavigationProp, NavigationProp,
@@ -234,5 +233,4 @@ export type BottomTabBarProps = BottomTabBarOptions & {
}) => React.ReactNode; }) => React.ReactNode;
activeTintColor: string; activeTintColor: string;
inactiveTintColor: string; inactiveTintColor: string;
safeAreaInset?: React.ComponentProps<typeof SafeAreaView>['forceInset'];
}; };

View File

@@ -8,8 +8,8 @@ import {
ScaledSize, ScaledSize,
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { Route, NavigationContext } from '@react-navigation/core'; import { Route, NavigationContext } from '@react-navigation/core';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import TabBarIcon from './TabBarIcon'; import TabBarIcon from './TabBarIcon';
import TouchableWithoutFeedbackWrapper from './TouchableWithoutFeedbackWrapper'; import TouchableWithoutFeedbackWrapper from './TouchableWithoutFeedbackWrapper';
@@ -22,9 +22,7 @@ type State = {
visible: Animated.Value; visible: Animated.Value;
}; };
type Props = BottomTabBarProps & { type Props = BottomTabBarProps;
safeAreaInset: React.ComponentProps<typeof SafeAreaView>['forceInset'];
};
const majorVersion = parseInt(Platform.Version as string, 10); const majorVersion = parseInt(Platform.Version as string, 10);
const isIos = Platform.OS === 'ios'; const isIos = Platform.OS === 'ios';
@@ -43,9 +41,6 @@ export default class TabBarBottom extends React.Component<Props, State> {
showIcon: true, showIcon: true,
allowFontScaling: true, allowFontScaling: true,
adaptive: isIOS11, adaptive: isIOS11,
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps<
typeof SafeAreaView
>['forceInset'],
}; };
state = { state = {
@@ -270,100 +265,101 @@ export default class TabBarBottom extends React.Component<Props, State> {
getAccessibilityStates, getAccessibilityStates,
getButtonComponent, getButtonComponent,
getTestID, getTestID,
safeAreaInset,
style, style,
tabStyle, tabStyle,
} = this.props; } = this.props;
const { routes } = state; const { routes } = state;
const tabBarStyle = [
styles.tabBar,
// @ts-ignore
this.shouldUseHorizontalLabels() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
style,
];
return ( return (
<Animated.View <SafeAreaConsumer>
style={[ {insets => (
styles.container, <Animated.View
keyboardHidesTabBar style={[
? // eslint-disable-next-line react-native/no-inline-styles styles.tabBar,
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
{ {
// When the keyboard is shown, slide down the tab bar height:
transform: [ // @ts-ignore
{ (this.shouldUseHorizontalLabels() && !Platform.isPad
translateY: this.state.visible.interpolate({ ? COMPACT_HEIGHT
inputRange: [0, 1], : DEFAULT_HEIGHT) + (insets ? insets.bottom : 0),
outputRange: [this.state.layout.height, 0], paddingBottom: insets ? insets.bottom : 0,
}), },
}, style,
], ]}
// Absolutely position the tab bar so that the content is below it pointerEvents={
// This is needed to avoid gap at bottom when the tab bar is hidden keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
position: this.state.keyboard ? 'absolute' : null, }
} onLayout={this.handleLayout}
: null, >
]} {routes.map((route, index) => {
pointerEvents={ const focused = index === state.index;
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto' const scene = { route, focused };
} const accessibilityLabel = getAccessibilityLabel({
onLayout={this.handleLayout} route,
> });
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
{routes.map((route, index) => {
const focused = index === state.index;
const scene = { route, focused };
const accessibilityLabel = getAccessibilityLabel({
route,
});
const accessibilityRole = getAccessibilityRole({ const accessibilityRole = getAccessibilityRole({
route, route,
}); });
const accessibilityStates = getAccessibilityStates(scene); const accessibilityStates = getAccessibilityStates(scene);
const testID = getTestID({ route }); const testID = getTestID({ route });
const backgroundColor = focused const backgroundColor = focused
? activeBackgroundColor ? activeBackgroundColor
: inactiveBackgroundColor; : inactiveBackgroundColor;
const ButtonComponent = const ButtonComponent =
getButtonComponent({ route }) || TouchableWithoutFeedbackWrapper; getButtonComponent({ route }) ||
TouchableWithoutFeedbackWrapper;
return ( return (
<NavigationContext.Provider <NavigationContext.Provider
key={route.key} key={route.key}
value={descriptors[route.key].navigation} value={descriptors[route.key].navigation}
>
<ButtonComponent
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
style={[
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
> >
{this.renderIcon(scene)} <ButtonComponent
{this.renderLabel(scene)} onPress={() => onTabPress({ route })}
</ButtonComponent> onLongPress={() => onTabLongPress({ route })}
</NavigationContext.Provider> testID={testID}
); accessibilityLabel={accessibilityLabel}
})} accessibilityRole={accessibilityRole}
</SafeAreaView> accessibilityStates={accessibilityStates}
</Animated.View> style={[
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this.renderIcon(scene)}
{this.renderLabel(scene)}
</ButtonComponent>
</NavigationContext.Provider>
);
})}
</Animated.View>
)}
</SafeAreaConsumer>
); );
} }
} }
@@ -373,23 +369,15 @@ const COMPACT_HEIGHT = 29;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff', backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)', borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row', flexDirection: 'row',
},
container: {
left: 0,
right: 0,
bottom: 0,
elevation: 8, elevation: 8,
}, },
tabBarCompact: {
height: COMPACT_HEIGHT,
},
tabBarRegular: {
height: DEFAULT_HEIGHT,
},
tab: { tab: {
flex: 1, flex: 1,
alignItems: isIos ? 'center' : 'stretch', alignItems: isIos ? 'center' : 'stretch',

View File

@@ -9,6 +9,7 @@ import { Route, CommonActions } from '@react-navigation/core';
import { TabNavigationState } from '@react-navigation/routers'; import { TabNavigationState } from '@react-navigation/routers';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens'; import { ScreenContainer } from 'react-native-screens';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import BottomTabBar from './BottomTabBar'; import BottomTabBar from './BottomTabBar';
import { import {
@@ -198,29 +199,31 @@ export default class BottomTabView extends React.Component<Props, State> {
const { loaded } = this.state; const { loaded } = this.state;
return ( return (
<View style={styles.container}> <SafeAreaProvider>
<ScreenContainer style={styles.pages}> <View style={styles.container}>
{routes.map((route, index) => { <ScreenContainer style={styles.pages}>
if (lazy && !loaded.includes(index)) { {routes.map((route, index) => {
// Don't render a screen if we've never navigated to it if (lazy && !loaded.includes(index)) {
return null; // Don't render a screen if we've never navigated to it
} return null;
}
const isFocused = state.index === index; const isFocused = state.index === index;
return ( return (
<ResourceSavingScene <ResourceSavingScene
key={route.key} key={route.key}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
isVisible={isFocused} isVisible={isFocused}
> >
{descriptors[route.key].render()} {descriptors[route.key].render()}
</ResourceSavingScene> </ResourceSavingScene>
); );
})} })}
</ScreenContainer> </ScreenContainer>
{this.renderTabBar()} {this.renderTabBar()}
</View> </View>
</SafeAreaProvider>
); );
} }
} }

View File

@@ -24,12 +24,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
return ( return (
<View <View
style={[ style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
styles.container,
style,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isVisible ? 1 : 0 },
]}
collapsable={false} collapsable={false}
removeClippedSubviews={ removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused // On iOS, set removeClippedSubviews to true only when not focused

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27) # [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
**Note:** Version bump only for package @react-navigation/compat **Note:** Version bump only for package @react-navigation/compat

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/compat", "name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format", "description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.4", "version": "5.0.0-alpha.7",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,12 +24,12 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8" "@react-navigation/routers": "^5.0.0-alpha.9"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.8.19", "@types/react": "^16.9.4",
"react": "^16.8.3", "react": "^16.8.3",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",

View File

@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.13...@react-navigation/core@5.0.0-alpha.14) (2019-10-06)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.12...@react-navigation/core@5.0.0-alpha.13) (2019-10-03)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.11...@react-navigation/core@5.0.0-alpha.12) (2019-10-03)
### Bug Fixes
* don't merge state with existing state during reset. fixes [#111](https://github.com/react-navigation/navigation-ex/issues/111) ([7393464](https://github.com/react-navigation/navigation-ex/commit/7393464))
* don't throw when switching navigator. fixes [#91](https://github.com/react-navigation/navigation-ex/issues/91) ([19be2b4](https://github.com/react-navigation/navigation-ex/commit/19be2b4))
### Features
* add a getRootState method ([#119](https://github.com/react-navigation/navigation-ex/issues/119)) ([7a5bcb4](https://github.com/react-navigation/navigation-ex/commit/7a5bcb4))
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.10...@react-navigation/core@5.0.0-alpha.11) (2019-09-27) # [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.10...@react-navigation/core@5.0.0-alpha.11) (2019-09-27)

View File

@@ -6,7 +6,7 @@
"react-native", "react-native",
"react-navigation" "react-navigation"
], ],
"version": "5.0.0-alpha.11", "version": "5.0.0-alpha.14",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -31,19 +31,19 @@
"dependencies": { "dependencies": {
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^2.0.0",
"query-string": "^6.8.3", "query-string": "^6.8.3",
"shortid": "^2.2.14", "shortid": "^2.2.15",
"use-subscription": "^1.0.0" "use-subscription": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.6.2",
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.19", "@types/react": "^16.9.4",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "^16.8.3", "react": "^16.8.3",
"react-native-testing-library": "^1.9.1", "react-native-testing-library": "^1.9.1",
"react-test-renderer": "16.8.3", "react-test-renderer": "16.8.3",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.3" "react": "^16.8.3"

View File

@@ -1,5 +1,5 @@
import shortid from 'shortid'; import shortid from 'shortid';
import { CommonAction, NavigationState } from './types'; import { CommonAction, NavigationState, PartialState } from './types';
/** /**
* Base router object that can be used when writing custom routers. * Base router object that can be used when writing custom routers.
@@ -9,7 +9,7 @@ const BaseRouter = {
getStateForAction<State extends NavigationState>( getStateForAction<State extends NavigationState>(
state: State, state: State,
action: CommonAction action: CommonAction
): State | null { ): State | PartialState<State> | null {
switch (action.type) { switch (action.type) {
case 'REPLACE': { case 'REPLACE': {
const index = action.source const index = action.source
@@ -56,12 +56,7 @@ const BaseRouter = {
} }
case 'RESET': case 'RESET':
return { return action.payload as PartialState<State>;
...state,
...action.payload,
key: state.key,
routeNames: state.routeNames,
};
default: default:
return null; return null;

View File

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

View File

@@ -18,25 +18,30 @@ export const SingleNavigatorContext = React.createContext<
* Component which ensures that there's only one navigator nested under it. * Component which ensures that there's only one navigator nested under it.
*/ */
export default function EnsureSingleNavigator({ children }: Props) { export default function EnsureSingleNavigator({ children }: Props) {
const [currentKey, setCurrentKey] = React.useState<string | undefined>(); const navigatorKeyRef = React.useRef<string | undefined>();
const value = React.useMemo( const value = React.useMemo(
() => ({ () => ({
register(key: string) { register(key: string) {
const currentKey = navigatorKeyRef.current;
if (currentKey !== undefined && key !== currentKey) { if (currentKey !== undefined && key !== currentKey) {
throw new Error(MULTIPLE_NAVIGATOR_ERROR); throw new Error(MULTIPLE_NAVIGATOR_ERROR);
} }
setCurrentKey(key); navigatorKeyRef.current = key;
}, },
unregister(key: string) { unregister(key: string) {
if (currentKey !== undefined && key !== currentKey) { const currentKey = navigatorKeyRef.current;
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
if (key !== currentKey) {
return;
} }
setCurrentKey(undefined); navigatorKeyRef.current = undefined;
}, },
}), }),
[currentKey] []
); );
return ( return (

View File

@@ -1,5 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import { NavigationAction, NavigationHelpers, ParamListBase } from './types'; import {
NavigationAction,
NavigationHelpers,
NavigationState,
ParamListBase,
} from './types';
export type ChildActionListener = ( export type ChildActionListener = (
action: NavigationAction, action: NavigationAction,
@@ -14,6 +19,8 @@ export type FocusedNavigationListener = <T>(
callback: FocusedNavigationCallback<T> callback: FocusedNavigationCallback<T>
) => { handled: boolean; result: T }; ) => { handled: boolean; result: T };
export type NavigatorStateGetter = () => NavigationState;
/** /**
* Context which holds the required helpers needed to build nested navigators. * Context which holds the required helpers needed to build nested navigators.
*/ */
@@ -25,6 +32,7 @@ const NavigationBuilderContext = React.createContext<{
addActionListener?: (listener: ChildActionListener) => void; addActionListener?: (listener: ChildActionListener) => void;
addFocusedListener?: (listener: FocusedNavigationListener) => void; addFocusedListener?: (listener: FocusedNavigationListener) => void;
onRouteFocus?: (key: string) => void; onRouteFocus?: (key: string) => void;
addStateGetter?: (key: string, getter: NavigatorStateGetter) => void;
trackAction: (action: NavigationAction) => void; trackAction: (action: NavigationAction) => void;
}>({ }>({
trackAction: () => undefined, trackAction: () => undefined,

View File

@@ -5,6 +5,7 @@ import NavigationBuilderContext from './NavigationBuilderContext';
import ResetRootContext from './ResetRootContext'; import ResetRootContext from './ResetRootContext';
import useFocusedListeners from './useFocusedListeners'; import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools'; import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import { import {
Route, Route,
@@ -24,7 +25,9 @@ const MISSING_CONTEXT_ERROR =
export const NavigationStateContext = React.createContext<{ export const NavigationStateContext = React.createContext<{
state?: NavigationState | PartialState<NavigationState>; state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined; getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (state: NavigationState | undefined) => void; setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
key?: string; key?: string;
performTransaction: (action: () => void) => void; performTransaction: (action: () => void) => void;
}>({ }>({
@@ -106,6 +109,8 @@ const Container = React.forwardRef(function NavigationContainer(
const { listeners, addListener: addFocusedListener } = useFocusedListeners(); const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = ( const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction) action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => { ) => {
@@ -132,13 +137,16 @@ const Container = React.forwardRef(function NavigationContainer(
[trackAction] [trackAction]
); );
const getRootState = () => {
return getStateForRoute('root');
};
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce< ...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any any
>((acc, name) => { >((acc, name) => {
acc[name] = (...args: any[]) => acc[name] = (...args: any[]) =>
dispatch( dispatch(
// eslint-disable-next-line import/namespace
CommonActions[name]( CommonActions[name](
// @ts-ignore // @ts-ignore
...args ...args
@@ -149,14 +157,16 @@ const Container = React.forwardRef(function NavigationContainer(
resetRoot, resetRoot,
dispatch, dispatch,
canGoBack, canGoBack,
getRootState,
})); }));
const builderContext = React.useMemo( const builderContext = React.useMemo(
() => ({ () => ({
addFocusedListener, addFocusedListener,
addStateGetter,
trackAction, trackAction,
}), }),
[addFocusedListener, trackAction] [addFocusedListener, trackAction, addStateGetter]
); );
const performTransaction = React.useCallback((callback: () => void) => { const performTransaction = React.useCallback((callback: () => void) => {

View File

@@ -47,7 +47,7 @@ export default function SceneView<
}, [getState, route.key]); }, [getState, route.key]);
const setCurrentState = React.useCallback( const setCurrentState = React.useCallback(
(child: NavigationState | undefined) => { (child: NavigationState | PartialState<NavigationState> | undefined) => {
const state = getState(); const state = getState();
setState({ setState({

View File

@@ -125,15 +125,5 @@ it('resets state to new state with RESET', () => {
}) })
); );
expect(result).toEqual({ ...STATE, index: 0, routes }); expect(result).toEqual({ index: 0, routes });
});
it('ignores key and routeNames when resetting with RESET', () => {
const result = BaseRouter.getStateForAction(
STATE,
// @ts-ignore
CommonActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
);
expect(result).toEqual({ ...STATE, index: 2 });
}); });

View File

@@ -321,3 +321,56 @@ it('handle resetting state with ref', () => {
expect(onStateChange).toBeCalledTimes(1); expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith(state); expect(onStateChange).lastCalledWith(state);
}); });
it('handle getRootState', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={() => null} />
</TestNavigator>
</NavigationContainer>
);
render(element);
let state;
if (ref.current) {
state = ref.current.getRootState();
}
expect(state).toEqual({
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '8',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
stale: false,
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
});
});

View File

@@ -655,6 +655,7 @@ it('throws when a React Element is not the direct children', () => {
); );
}); });
// eslint-disable-next-line jest/expect-expect
it("doesn't throw when direct children is Screen or empty element", () => { it("doesn't throw when direct children is Screen or empty element", () => {
const TestNavigator = (props: any) => { const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props); useNavigationBuilder(MockRouter, props);
@@ -694,3 +695,30 @@ it('throws when multiple screens with same name are defined', () => {
"A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named 'foo')" "A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named 'foo')"
); );
}); });
it('switches rendered navigators', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const root = render(
<NavigationContainer>
<TestNavigator key="a">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(() =>
root.update(
<NavigationContainer>
<TestNavigator key="b">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
)
).not.toThrowError(
'Another navigator is already registered for this container.'
);
});

View File

@@ -273,7 +273,11 @@ it('fires blur event when a route is removed with a delay', async () => {
const [previous, dispatch] = React.useReducer( const [previous, dispatch] = React.useReducer(
(state, action) => { (state, action) => {
return { ...state, ...action }; if (state.routes !== action.routes) {
return { ...state, ...action };
}
return state;
}, },
{ routes: state.routes, descriptors } { routes: state.routes, descriptors }
); );

View File

@@ -296,6 +296,7 @@ it("action doesn't bubble if target is specified", () => {
expect(onStateChange).not.toBeCalled(); expect(onStateChange).not.toBeCalled();
}); });
// eslint-disable-next-line jest/expect-expect
it("doesn't crash if no navigator handled the action", () => { it("doesn't crash if no navigator handled the action", () => {
const TestRouter = MockRouter; const TestRouter = MockRouter;

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions'; import * as CommonActions from './CommonActions';
import * as React from 'react'; import * as React from 'react';
@@ -170,7 +171,10 @@ export type Router<
* @param state State object to apply the action on. * @param state State object to apply the action on.
* @param action Action object to apply. * @param action Action object to apply.
*/ */
getStateForAction(state: State, action: Action): State | null; getStateForAction(
state: State,
action: Action
): State | PartialState<State> | null;
/** /**
* Whether the action should also change focus in parent navigator * Whether the action should also change focus in parent navigator
@@ -534,6 +538,7 @@ export type NavigationContainerRef =
* @param state Navigation state object. * @param state Navigation state object.
*/ */
resetRoot(state: PartialState<NavigationState> | NavigationState): void; resetRoot(state: PartialState<NavigationState> | NavigationState): void;
getRootState(): NavigationState;
} }
| undefined | undefined
| null; | null;

View File

@@ -3,6 +3,7 @@ import SceneView from './SceneView';
import NavigationBuilderContext, { import NavigationBuilderContext, {
ChildActionListener, ChildActionListener,
FocusedNavigationListener, FocusedNavigationListener,
NavigatorStateGetter,
} from './NavigationBuilderContext'; } from './NavigationBuilderContext';
import { NavigationEventEmitter } from './useEventEmitter'; import { NavigationEventEmitter } from './useEventEmitter';
import useNavigationCache from './useNavigationCache'; import useNavigationCache from './useNavigationCache';
@@ -35,6 +36,7 @@ type Options<State extends NavigationState, ScreenOptions extends object> = {
setState: (state: State) => void; setState: (state: State) => void;
addActionListener: (listener: ChildActionListener) => void; addActionListener: (listener: ChildActionListener) => void;
addFocusedListener: (listener: FocusedNavigationListener) => void; addFocusedListener: (listener: FocusedNavigationListener) => void;
addStateGetter: (key: string, getter: NavigatorStateGetter) => void;
onRouteFocus: (key: string) => void; onRouteFocus: (key: string) => void;
router: Router<State, NavigationAction>; router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter; emitter: NavigationEventEmitter;
@@ -61,6 +63,7 @@ export default function useDescriptors<
setState, setState,
addActionListener, addActionListener,
addFocusedListener, addFocusedListener,
addStateGetter,
onRouteFocus, onRouteFocus,
router, router,
emitter, emitter,
@@ -74,6 +77,7 @@ export default function useDescriptors<
onAction, onAction,
addActionListener, addActionListener,
addFocusedListener, addFocusedListener,
addStateGetter,
onRouteFocus, onRouteFocus,
trackAction, trackAction,
}), }),
@@ -83,6 +87,7 @@ export default function useDescriptors<
addActionListener, addActionListener,
addFocusedListener, addFocusedListener,
onRouteFocus, onRouteFocus,
addStateGetter,
trackAction, trackAction,
] ]
); );

View File

@@ -18,6 +18,7 @@ type DevTools = {
}; };
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS { namespace NodeJS {
interface Global { interface Global {
__REDUX_DEVTOOLS_EXTENSION__: __REDUX_DEVTOOLS_EXTENSION__:

View File

@@ -23,6 +23,8 @@ import {
PrivateValueStore, PrivateValueStore,
NavigationAction, NavigationAction,
} from './types'; } from './types';
import useStateGetters from './useStateGetters';
import useOnGetState from './useOnGetState';
// This is to make TypeScript compiler happy // This is to make TypeScript compiler happy
// eslint-disable-next-line babel/no-unused-expressions // eslint-disable-next-line babel/no-unused-expressions
@@ -227,6 +229,8 @@ export default function useNavigationBuilder<
addListener: addFocusedListener, addListener: addFocusedListener,
} = useFocusedListeners(); } = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const onAction = useOnAction({ const onAction = useOnAction({
router, router,
getState, getState,
@@ -254,6 +258,11 @@ export default function useNavigationBuilder<
focusedListeners, focusedListeners,
}); });
useOnGetState({
getState,
getStateForRoute,
});
const descriptors = useDescriptors<State, ScreenOptions>({ const descriptors = useDescriptors<State, ScreenOptions>({
state, state,
screens, screens,
@@ -265,6 +274,7 @@ export default function useNavigationBuilder<
onRouteFocus, onRouteFocus,
addActionListener, addActionListener,
addFocusedListener, addFocusedListener,
addStateGetter,
router, router,
emitter, emitter,
}); });

View File

@@ -2,13 +2,18 @@ import * as React from 'react';
import NavigationBuilderContext, { import NavigationBuilderContext, {
ChildActionListener, ChildActionListener,
} from './NavigationBuilderContext'; } from './NavigationBuilderContext';
import { NavigationAction, NavigationState, Router } from './types'; import {
NavigationAction,
NavigationState,
PartialState,
Router,
} from './types';
type Options = { type Options = {
router: Router<NavigationState, NavigationAction>; router: Router<NavigationState, NavigationAction>;
key?: string; key?: string;
getState: () => NavigationState; getState: () => NavigationState;
setState: (state: NavigationState) => void; setState: (state: NavigationState | PartialState<NavigationState>) => void;
listeners: ChildActionListener[]; listeners: ChildActionListener[];
}; };

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import NavigationBuilderContext from './NavigationBuilderContext';
import { NavigationState } from './types';
import NavigationRouteContext from './NavigationRouteContext';
export default function useOnGetState({
getStateForRoute,
getState,
}: {
getStateForRoute: (routeName: string) => NavigationState | undefined;
getState: () => NavigationState;
}) {
const { addStateGetter } = React.useContext(NavigationBuilderContext);
const route = React.useContext(NavigationRouteContext);
const key = route ? route.key : 'root';
const getter = React.useCallback(() => {
const state = getState();
return {
...state,
routes: state.routes.map(route => ({
...route,
state: getStateForRoute(route.key),
})),
};
}, [getState, getStateForRoute]);
React.useEffect(() => {
return addStateGetter && addStateGetter(key, getter);
}, [addStateGetter, getter, key]);
}

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import { NavigatorStateGetter } from './NavigationBuilderContext';
/**
* Hook which lets child navigators add getters to be called for obtaining rehydrated state.
*/
export default function useStateGetters() {
const stateGetters = React.useRef<Record<string, NavigatorStateGetter>>({});
const getStateForRoute = React.useCallback(
routeKey =>
stateGetters.current[routeKey] === undefined
? undefined
: stateGetters.current[routeKey](),
[stateGetters]
);
const addStateGetter = React.useCallback(
(key: string, getter: NavigatorStateGetter) => {
stateGetters.current[key] = getter;
return () => {
// @ts-ignore
stateGetters.current[key] = undefined;
};
},
[]
);
return {
getStateForRoute,
addStateGetter,
};
}

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.12...@react-navigation/drawer@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.11...@react-navigation/drawer@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.10...@react-navigation/drawer@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.9...@react-navigation/drawer@5.0.0-alpha.10) (2019-09-27) # [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.9...@react-navigation/drawer@5.0.0-alpha.10) (2019-09-27)

View File

@@ -10,44 +10,29 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/drawer yarn add @react-navigation/core @react-navigation/drawer
``` ```
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated). 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) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run: If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh ```sh
expo install react-native-gesture-handler react-native-reanimated expo install react-native-gesture-handler react-native-reanimated react-native-safe-area-context
``` ```
If you are not using Expo, run the following: If you are not using Expo, run the following:
```sh ```sh
yarn add react-native-reanimated react-native-gesture-handler yarn add react-native-reanimated react-native-gesture-handler react-native-safe-area-context
``` ```
If you are using Expo, you are done. Otherwise, continue to the next steps. If you are using Expo, you are done. Otherwise, continue to the next steps.
Next, we need to link these libraries. The steps depends on your React Native version: To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
- **React Native 0.60 and higher** ```sh
cd ios
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md). pod install
cd ..
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run: ```
```sh
cd ios
pod install
cd ..
```
- **React Native 0.59**
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
```sh
react-native link react-native-reanimated
react-native link react-native-gesture-handler
```
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation. **IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.

View File

@@ -11,7 +11,7 @@
"material", "material",
"drawer" "drawer"
], ],
"version": "5.0.0-alpha.10", "version": "5.0.0-alpha.13",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,20 +34,20 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8", "@react-navigation/routers": "^5.0.0-alpha.9"
"react-native-safe-area-view": "^0.14.6"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10",
"react-native-gesture-handler": "^1.3.0", "react-native-gesture-handler": "^1.3.0",
"react-native-reanimated": "^1.1.0", "react-native-reanimated": "^1.3.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.22", "react-native-screens": "^1.0.0-alpha.22",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",
@@ -55,6 +55,7 @@
"react-native": "*", "react-native": "*",
"react-native-gesture-handler": "^1.0.0", "react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0", "react-native-reanimated": "^1.0.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.0" "react-native-screens": "^1.0.0-alpha.0"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {

View File

@@ -578,7 +578,6 @@ export default class DrawerView extends React.PureComponent<Props> {
style={[ style={[
styles.container, styles.container,
right ? { right: offset } : { left: offset }, right ? { right: offset } : { left: offset },
// eslint-disable-next-line react-native/no-inline-styles
{ {
transform: [{ translateX: drawerTranslateX }], transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity, opacity: this.drawerOpacity,

View File

@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, StyleSheet } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view'; import { useSafeArea } from 'react-native-safe-area-context';
import TouchableItem from './TouchableItem'; import TouchableItem from './TouchableItem';
import { DrawerNavigationItemsProps } from '../types'; import { DrawerNavigationItemsProps } from '../types';
@@ -25,63 +25,72 @@ const DrawerNavigatorItems = ({
inactiveLabelStyle, inactiveLabelStyle,
iconContainerStyle, iconContainerStyle,
drawerPosition, drawerPosition,
}: DrawerNavigationItemsProps) => ( }: DrawerNavigationItemsProps) => {
<View style={[styles.container, itemsContainerStyle]}> const insets = useSafeArea();
{items.map((route, index: number) => {
const focused = activeItemKey === route.key; return (
const color = focused ? activeTintColor : inactiveTintColor; <View style={[styles.container, itemsContainerStyle]}>
const backgroundColor = focused {items.map((route, index: number) => {
? activeBackgroundColor const focused = activeItemKey === route.key;
: inactiveBackgroundColor; const color = focused ? activeTintColor : inactiveTintColor;
const scene = { route, index, focused, tintColor: color }; const backgroundColor = focused
const icon = renderIcon(scene); ? activeBackgroundColor
const label = getLabel(scene); : inactiveBackgroundColor;
const accessibilityLabel = typeof label === 'string' ? label : undefined; const scene = { route, index, focused, tintColor: color };
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle; const icon = renderIcon(scene);
return ( const label = getLabel(scene);
<TouchableItem const accessibilityLabel =
key={route.key} typeof label === 'string' ? label : undefined;
accessible const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
accessibilityLabel={accessibilityLabel}
onPress={() => { return (
onItemPress({ route, focused }); <TouchableItem
}} key={route.key}
delayPressIn={0} accessible
> accessibilityLabel={accessibilityLabel}
<SafeAreaView onPress={() => {
style={[{ backgroundColor }, styles.item, itemStyle]} onItemPress({ route, focused });
forceInset={{
[drawerPosition]: 'always',
[drawerPosition === 'left' ? 'right' : 'left']: 'never',
vertical: 'never',
}} }}
delayPressIn={0}
> >
{icon ? ( <View
<View style={[
style={[ {
styles.icon, backgroundColor,
focused ? null : styles.inactiveIcon, marginLeft: drawerPosition === 'left' ? insets.left : 0,
iconContainerStyle, marginRight: drawerPosition === 'right' ? insets.right : 0,
]} },
> styles.item,
{icon} itemStyle,
</View> ]}
) : null} >
{typeof label === 'string' ? ( {icon ? (
<Text <View
style={[styles.label, { color }, labelStyle, extraLabelStyle]} style={[
> styles.icon,
{label} focused ? null : styles.inactiveIcon,
</Text> iconContainerStyle,
) : ( ]}
label >
)} {icon}
</SafeAreaView> </View>
</TouchableItem> ) : null}
); {typeof label === 'string' ? (
})} <Text
</View> style={[styles.label, { color }, labelStyle, extraLabelStyle]}
); >
{label}
</Text>
) : (
label
)}
</View>
</TouchableItem>
);
})}
</View>
);
};
/* Material design specs - https://material.io/guidelines/patterns/navigation-drawer.html#navigation-drawer-specs */ /* Material design specs - https://material.io/guidelines/patterns/navigation-drawer.html#navigation-drawer-specs */
DrawerNavigatorItems.defaultProps = { DrawerNavigatorItems.defaultProps = {

View File

@@ -1,8 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native'; import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native';
import { SafeAreaProvider, useSafeArea } from 'react-native-safe-area-context';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens'; import { ScreenContainer } from 'react-native-screens';
import SafeAreaView from 'react-native-safe-area-view';
import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler'; import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler';
import { import {
DrawerNavigationState, DrawerNavigationState,
@@ -32,13 +32,18 @@ type State = {
drawerWidth: number; drawerWidth: number;
}; };
const DefaultContentComponent = (props: ContentComponentProps) => ( const DefaultContentComponent = (props: ContentComponentProps) => {
<ScrollView alwaysBounceVertical={false}> const insets = useSafeArea();
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
return (
<ScrollView
alwaysBounceVertical={false}
contentContainerStyle={{ marginTop: insets.top }}
>
<DrawerNavigatorItems {...props} /> <DrawerNavigatorItems {...props} />
</SafeAreaView> </ScrollView>
</ScrollView> );
); };
/** /**
* Component that renders the drawer. * Component that renders the drawer.
@@ -163,7 +168,6 @@ export default class DrawerView extends React.PureComponent<Props, State> {
key={route.key} key={route.key}
style={[ style={[
StyleSheet.absoluteFill, StyleSheet.absoluteFill,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isFocused ? 1 : 0 }, { opacity: isFocused ? 1 : 0 },
]} ]}
isVisible={isFocused} isVisible={isFocused}
@@ -209,35 +213,37 @@ export default class DrawerView extends React.PureComponent<Props, State> {
: state.isDrawerOpen; : state.isDrawerOpen;
return ( return (
<DrawerGestureContext.Provider value={this.drawerGestureRef}> <SafeAreaProvider>
<Drawer <DrawerGestureContext.Provider value={this.drawerGestureRef}>
open={isOpen} <Drawer
locked={ open={isOpen}
drawerLockMode === 'locked-open' || locked={
drawerLockMode === 'locked-closed' drawerLockMode === 'locked-open' ||
} drawerLockMode === 'locked-closed'
onOpen={this.handleDrawerOpen} }
onClose={this.handleDrawerClose} onOpen={this.handleDrawerOpen}
onGestureRef={this.setDrawerGestureRef} onClose={this.handleDrawerClose}
gestureHandlerProps={gestureHandlerProps} onGestureRef={this.setDrawerGestureRef}
drawerType={drawerType} gestureHandlerProps={gestureHandlerProps}
drawerPosition={drawerPosition} drawerType={drawerType}
sceneContainerStyle={sceneContainerStyle} drawerPosition={drawerPosition}
drawerStyle={{ sceneContainerStyle={sceneContainerStyle}
backgroundColor: drawerBackgroundColor || 'white', drawerStyle={{
width: this.state.drawerWidth, backgroundColor: drawerBackgroundColor || 'white',
}} width: this.state.drawerWidth,
overlayStyle={{ }}
backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)', overlayStyle={{
}} backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)',
swipeEdgeWidth={edgeWidth} }}
swipeDistanceThreshold={minSwipeDistance} swipeEdgeWidth={edgeWidth}
hideStatusBar={hideStatusBar} swipeDistanceThreshold={minSwipeDistance}
statusBarAnimation={statusBarAnimation} hideStatusBar={hideStatusBar}
renderDrawerContent={this.renderNavigationView} statusBarAnimation={statusBarAnimation}
renderSceneContent={this.renderContent} renderDrawerContent={this.renderNavigationView}
/> renderSceneContent={this.renderContent}
</DrawerGestureContext.Provider> />
</DrawerGestureContext.Provider>
</SafeAreaProvider>
); );
} }
} }

View File

@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.12](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.11...@react-navigation/example@5.0.0-alpha.12) (2019-10-06)
### Features
* drop header: null in favor of more explitit headerShown option ([ba6b6ae](https://github.com/satya164/navigation-ex/commit/ba6b6ae))
# [5.0.0-alpha.11](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.10...@react-navigation/example@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.10](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.9...@react-navigation/example@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.9](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.8...@react-navigation/example@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.7...@react-navigation/example@5.0.0-alpha.8) (2019-09-27) # [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.7...@react-navigation/example@5.0.0-alpha.8) (2019-09-27)

View File

@@ -3,14 +3,13 @@
"name": "@react-navigation/example", "name": "@react-navigation/example",
"slug": "react-navigation-example", "slug": "react-navigation-example",
"privacy": "public", "privacy": "public",
"sdkVersion": "34.0.0", "sdkVersion": "35.0.0",
"platforms": [ "platforms": [
"ios", "ios",
"android", "android",
"web" "web"
], ],
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"splash": { "splash": {
"image": "./assets/splash.png", "image": "./assets/splash.png",
@@ -30,4 +29,4 @@
}, },
"displayName": "React Navigation Example", "displayName": "React Navigation Example",
"name": "ReactNavigationExample" "name": "ReactNavigationExample"
} }

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = function(api) { module.exports = function(api) {
api.cache(true); api.cache(true);
return { return {

View File

@@ -1,4 +1,4 @@
/* eslint-disable import/no-commonjs, import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
@@ -36,7 +36,7 @@ module.exports = {
'react-native', 'react-native',
'react-native-gesture-handler', 'react-native-gesture-handler',
'react-native-reanimated', 'react-native-reanimated',
'react-native-safe-area-view', 'react-native-safe-area-context',
'react-native-screens', 'react-native-screens',
'react-native-paper', 'react-native-paper',
'react-native-tab-view', 'react-native-tab-view',

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/example", "name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation", "description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0-alpha.8", "version": "5.0.0-alpha.12",
"private": true, "private": true,
"workspaces": { "workspaces": {
"nohoist": [ "nohoist": [
@@ -18,31 +18,29 @@
"dependencies": { "dependencies": {
"@expo/vector-icons": "^10.0.0", "@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "^0.1.1", "@react-native-community/masked-view": "^0.1.1",
"expo": "^34.0.1", "expo": "^35.0.0",
"expo-asset": "~6.0.0", "expo-asset": "~7.0.0",
"query-string": "^6.8.3", "query-string": "^6.8.3",
"react": "16.8.3", "react": "^16.8.3",
"react-dom": "^16.8.3", "react-dom": "^16.8.3",
"react-native": "0.59.10", "react-native": "^0.59.10",
"react-native-gesture-handler": "~1.3.0", "react-native-gesture-handler": "~1.3.0",
"react-native-paper": "^3.0.0-alpha.3", "react-native-paper": "^3.0.0-alpha.3",
"react-native-reanimated": "~1.1.0", "react-native-reanimated": "~1.2.0",
"react-native-screens": "1.0.0-alpha.22", "react-native-safe-area-context": "~0.3.6",
"react-native-tab-view": "2.7.1", "react-native-screens": "~1.0.0-alpha.23",
"react-native-unimodules": "~0.5.2", "react-native-tab-view": "2.10.0",
"scheduler": "^0.14.0", "react-native-unimodules": "^0.7.0-rc.1",
"shortid": "^2.2.14", "scheduler": "^0.16.1",
"use-subscription": "^1.0.0" "shortid": "^2.2.15",
"use-subscription": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.5", "@babel/core": "^7.6.2",
"@types/react": "^16.8.23", "@types/react": "^16.9.4",
"@types/react-native": "^0.57.65", "@types/react-native": "^0.60.17",
"babel-preset-expo": "^6.0.0", "babel-preset-expo": "^7.0.0",
"expo-cli": "^3.0.10", "expo-cli": "^3.1.2",
"typescript": "^3.5.3" "typescript": "^3.6.3"
},
"resolutions": {
"react-native-safe-area-view": "0.14.7"
} }
} }

View File

@@ -14,11 +14,13 @@ type AuthStackParams = {
'sign-in': undefined; 'sign-in': undefined;
}; };
const SplashScreen = () => ( const SplashScreen = () => {
<View style={styles.content}> return (
<ActivityIndicator /> <View style={styles.content}>
</View> <ActivityIndicator />
); </View>
);
};
const SignInScreen = ({ const SignInScreen = ({
setUserToken, setUserToken,
@@ -74,7 +76,7 @@ export default function SimpleStackScreen({ navigation }: Props) {
}, []); }, []);
navigation.setOptions({ navigation.setOptions({
header: null, headerShown: false,
}); });
return ( return (

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/namespace, import/default */
import * as React from 'react'; import * as React from 'react';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

View File

@@ -19,27 +19,29 @@ type CompatStackParams = {
const ArticleScreen: CompatScreenType< const ArticleScreen: CompatScreenType<
StackNavigationProp<CompatStackParams, 'Article'> StackNavigationProp<CompatStackParams, 'Article'>
> = ({ navigation }) => ( > = ({ navigation }) => {
<React.Fragment> return (
<View style={styles.buttons}> <React.Fragment>
<Button <View style={styles.buttons}>
mode="contained" <Button
onPress={() => navigation.push('Album')} mode="contained"
style={styles.button} onPress={() => navigation.push('Album')}
> style={styles.button}
Push album >
</Button> Push album
<Button </Button>
mode="outlined" <Button
onPress={() => navigation.goBack()} mode="outlined"
style={styles.button} onPress={() => navigation.goBack()}
> style={styles.button}
Go back >
</Button> Go back
</View> </Button>
<Article author={{ name: navigation.getParam('author') }} /> </View>
</React.Fragment> <Article author={{ name: navigation.getParam('author') }} />
); </React.Fragment>
);
};
ArticleScreen.navigationOptions = ({ navigation }) => ({ ArticleScreen.navigationOptions = ({ navigation }) => ({
title: `Article by ${navigation.getParam('author')}`, title: `Article by ${navigation.getParam('author')}`,
@@ -47,27 +49,29 @@ ArticleScreen.navigationOptions = ({ navigation }) => ({
const AlbumsScreen: CompatScreenType< const AlbumsScreen: CompatScreenType<
StackNavigationProp<CompatStackParams> StackNavigationProp<CompatStackParams>
> = ({ navigation }) => ( > = ({ navigation }) => {
<React.Fragment> return (
<View style={styles.buttons}> <React.Fragment>
<Button <View style={styles.buttons}>
mode="contained" <Button
onPress={() => navigation.push('Article', { author: 'Babel fish' })} mode="contained"
style={styles.button} onPress={() => navigation.push('Article', { author: 'Babel fish' })}
> style={styles.button}
Push article >
</Button> Push article
<Button </Button>
mode="outlined" <Button
onPress={() => navigation.goBack()} mode="outlined"
style={styles.button} onPress={() => navigation.goBack()}
> style={styles.button}
Go back >
</Button> Go back
</View> </Button>
<Albums /> </View>
</React.Fragment> <Albums />
); </React.Fragment>
);
};
const CompatStack = createCompatNavigatorFactory(createStackNavigator)< const CompatStack = createCompatNavigatorFactory(createStackNavigator)<
StackNavigationProp<CompatStackParams> StackNavigationProp<CompatStackParams>
@@ -92,7 +96,7 @@ export default function CompatStackScreen({
navigation: StackNavigationProp<{}>; navigation: StackNavigationProp<{}>;
}) { }) {
navigation.setOptions({ navigation.setOptions({
header: null, headerShown: false,
}); });
return <CompatStack />; return <CompatStack />;

View File

@@ -0,0 +1,131 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { useSafeArea } from 'react-native-safe-area-context';
import { RouteProp, ParamListBase } from '@react-navigation/core';
import {
createStackNavigator,
StackNavigationProp,
TransitionPresets,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
article: { author: string };
album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>;
}) => {
const insets = useSafeArea();
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} />
</React.Fragment>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const insets = useSafeArea();
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums />
</React.Fragment>
);
};
const ModalPresentationStack = createStackNavigator<SimpleStackParams>();
type Props = {
options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>;
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, options }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<ModalPresentationStack.Navigator
mode="modal"
headerMode="none"
screenOptions={{
...TransitionPresets.ModalPresentationIOS,
cardOverlayEnabled: true,
gestureEnabled: true,
}}
{...options}
>
<ModalPresentationStack.Screen
name="article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
name="album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</ModalPresentationStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -22,53 +22,57 @@ const ArticleScreen = ({
}: { }: {
navigation: SimpleStackNavigation; navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>; route: RouteProp<SimpleStackParams, 'article'>;
}) => ( }) => {
<React.Fragment> return (
<View style={styles.buttons}> <React.Fragment>
<Button <View style={styles.buttons}>
mode="contained" <Button
onPress={() => navigation.push('album')} mode="contained"
style={styles.button} onPress={() => navigation.push('album')}
> style={styles.button}
Push album >
</Button> Push album
<Button </Button>
mode="outlined" <Button
onPress={() => navigation.goBack()} mode="outlined"
style={styles.button} onPress={() => navigation.goBack()}
> style={styles.button}
Go back >
</Button> Go back
</View> </Button>
<Article author={{ name: route.params.author }} /> </View>
</React.Fragment> <Article author={{ name: route.params.author }} />
); </React.Fragment>
);
};
const AlbumsScreen = ({ const AlbumsScreen = ({
navigation, navigation,
}: { }: {
navigation: SimpleStackNavigation; navigation: SimpleStackNavigation;
}) => ( }) => {
<React.Fragment> return (
<View style={styles.buttons}> <React.Fragment>
<Button <View style={styles.buttons}>
mode="contained" <Button
onPress={() => navigation.push('article', { author: 'Babel fish' })} mode="contained"
style={styles.button} onPress={() => navigation.push('article', { author: 'Babel fish' })}
> style={styles.button}
Push article >
</Button> Push article
<Button </Button>
mode="outlined" <Button
onPress={() => navigation.goBack()} mode="outlined"
style={styles.button} onPress={() => navigation.goBack()}
> style={styles.button}
Go back >
</Button> Go back
</View> </Button>
<Albums /> </View>
</React.Fragment> <Albums />
); </React.Fragment>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>(); const SimpleStack = createStackNavigator<SimpleStackParams>();
@@ -79,7 +83,7 @@ type Props = {
export default function SimpleStackScreen({ navigation, options }: Props) { export default function SimpleStackScreen({ navigation, options }: Props) {
navigation.setOptions({ navigation.setOptions({
header: null, headerShown: false,
}); });
return ( return (

View File

@@ -25,6 +25,7 @@ export default function Albums() {
contentContainerStyle={styles.content} contentContainerStyle={styles.content}
> >
{COVERS.map((source, i) => ( {COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<Image key={i} source={source} style={styles.cover} /> <Image key={i} source={source} style={styles.cover} />
))} ))}
</ScrollView> </ScrollView>

View File

@@ -32,6 +32,7 @@ export default function Chat() {
return ( return (
<View <View
// eslint-disable-next-line react/no-array-index-key
key={i} key={i}
style={[odd ? styles.odd : styles.even, styles.inverted]} style={[odd ? styles.odd : styles.even, styles.inverted]}
> >

View File

@@ -22,8 +22,9 @@ import {
StackNavigationProp, StackNavigationProp,
} from '@react-navigation/stack'; } from '@react-navigation/stack';
import SimpleStackScreen from './Screens/SimpleStack'; import SimpleStack from './Screens/SimpleStack';
import BottomTabsScreen from './Screens/BottomTabs'; import ModalPresentationStack from './Screens/ModalPresentationStack';
import BottomTabs from './Screens/BottomTabs';
import MaterialTopTabsScreen from './Screens/MaterialTopTabs'; import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
import MaterialBottomTabs from './Screens/MaterialBottomTabs'; import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import AuthFlow from './Screens/AuthFlow'; import AuthFlow from './Screens/AuthFlow';
@@ -42,21 +43,25 @@ type RootStackParamList = {
}; };
const SCREENS = { const SCREENS = {
'simple-stack': { title: 'Simple Stack', component: SimpleStackScreen }, SimpleStack: { title: 'Simple Stack', component: SimpleStack },
'bottom-tabs': { title: 'Bottom Tabs', component: BottomTabsScreen }, ModalPresentationStack: {
'material-top-tabs': { title: 'Modal Presentation Stack',
component: ModalPresentationStack,
},
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
MaterialTopTabs: {
title: 'Material Top Tabs', title: 'Material Top Tabs',
component: MaterialTopTabsScreen, component: MaterialTopTabsScreen,
}, },
'material-bottom-tabs': { MaterialBottomTabs: {
title: 'Material Bottom Tabs', title: 'Material Bottom Tabs',
component: MaterialBottomTabs, component: MaterialBottomTabs,
}, },
'auth-flow': { AuthFlow: {
title: 'Auth Flow', title: 'Auth Flow',
component: AuthFlow, component: AuthFlow,
}, },
'compat-api': { CompatAPI: {
title: 'Compat Layer', title: 'Compat Layer',
component: CompatAPI, component: CompatAPI,
}, },

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.11...@react-navigation/material-bottom-tabs@5.0.0-alpha.12) (2019-10-06)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.10...@react-navigation/material-bottom-tabs@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.9...@react-navigation/material-bottom-tabs@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.8...@react-navigation/material-bottom-tabs@5.0.0-alpha.9) (2019-09-27) # [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.8...@react-navigation/material-bottom-tabs@5.0.0-alpha.9) (2019-09-27)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs **Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -11,7 +11,7 @@
"material", "material",
"tab" "tab"
], ],
"version": "5.0.0-alpha.9", "version": "5.0.0-alpha.12",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,19 +34,19 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8" "@react-navigation/routers": "^5.0.0-alpha.9"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"@types/react-native-vector-icons": "^6.4.1", "@types/react-native-vector-icons": "^6.4.4",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10",
"react-native-paper": "^3.0.0-alpha.3", "react-native-paper": "^3.0.0-alpha.3",
"react-native-vector-icons": "^6.6.0", "react-native-vector-icons": "^6.6.0",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.10...@react-navigation/material-top-tabs@5.0.0-alpha.11) (2019-10-06)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.9...@react-navigation/material-top-tabs@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.8...@react-navigation/material-top-tabs@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.7...@react-navigation/material-top-tabs@5.0.0-alpha.8) (2019-09-27) # [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.7...@react-navigation/material-top-tabs@5.0.0-alpha.8) (2019-09-27)

View File

@@ -10,7 +10,7 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/material-top-tabs react-native-tab-view yarn add @react-navigation/core @react-navigation/material-top-tabs react-native-tab-view
``` ```
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated). Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated)..
If you are using Expo, to ensure that you get the compatible versions of the libraries, run: If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
@@ -26,28 +26,13 @@ yarn add react-native-reanimated react-native-gesture-handler
If you are using Expo, you are done. Otherwise, continue to the next steps. If you are using Expo, you are done. Otherwise, continue to the next steps.
Next, we need to link these libraries. The steps depends on your React Native version: To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
- **React Native 0.60 and higher** ```sh
cd ios
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md). pod install
cd ..
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run: ```
```sh
cd ios
pod install
cd ..
```
- **React Native 0.59**
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
```sh
react-native link react-native-reanimated
react-native link react-native-gesture-handler
```
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation. **IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.

View File

@@ -11,7 +11,7 @@
"material", "material",
"tab" "tab"
], ],
"version": "5.0.0-alpha.8", "version": "5.0.0-alpha.11",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -34,19 +34,19 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8" "@react-navigation/routers": "^5.0.0-alpha.9"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "^0.59.8", "react-native": "^0.59.10",
"react-native-gesture-handler": "^1.3.0", "react-native-gesture-handler": "^1.3.0",
"react-native-reanimated": "^1.1.0", "react-native-reanimated": "^1.3.0",
"react-native-tab-view": "^2.10.0", "react-native-tab-view": "^2.10.0",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.10...@react-navigation/native@5.0.0-alpha.11) (2019-10-06)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.9...@react-navigation/native@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.8...@react-navigation/native@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.7...@react-navigation/native@5.0.0-alpha.8) (2019-09-16) # [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.7...@react-navigation/native@5.0.0-alpha.8) (2019-09-16)

View File

@@ -7,7 +7,7 @@
"ios", "ios",
"android" "android"
], ],
"version": "5.0.0-alpha.8", "version": "5.0.0-alpha.11",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -31,12 +31,12 @@
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0", "@react-navigation/core": "^5.0.0-alpha.0",

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.8...@react-navigation/routers@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/routers
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.7...@react-navigation/routers@5.0.0-alpha.8) (2019-09-27) # [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.7...@react-navigation/routers@5.0.0-alpha.8) (2019-09-27)

View File

@@ -6,7 +6,7 @@
"react-native", "react-native",
"react-navigation" "react-navigation"
], ],
"version": "5.0.0-alpha.8", "version": "5.0.0-alpha.9",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,12 +29,12 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"shortid": "^2.2.14" "shortid": "^2.2.15"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0" "@react-navigation/core": "^5.0.0-alpha.0"

View File

@@ -3,6 +3,59 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.23...@react-navigation/stack@5.0.0-alpha.24) (2019-10-06)
### Bug Fixes
* actually expose gestureVelocityImpact in the public API ([16079d1](https://github.com/react-navigation/navigation-ex/commit/16079d1))
* don't recompute if routes didn't change ([615b523](https://github.com/react-navigation/navigation-ex/commit/615b523))
* handling vertical gesture in RTL ([#122](https://github.com/react-navigation/navigation-ex/issues/122)) ([a27ade8](https://github.com/react-navigation/navigation-ex/commit/a27ade8))
* use next screen's animation when not focused. fixes [#87](https://github.com/react-navigation/navigation-ex/issues/87) ([b4a7681](https://github.com/react-navigation/navigation-ex/commit/b4a7681))
### Features
* add gestureVelocityImpact as a prop for stack ([#123](https://github.com/react-navigation/navigation-ex/issues/123)) ([8294efc](https://github.com/react-navigation/navigation-ex/commit/8294efc))
* drop header: null in favor of more explitit headerShown option ([ba6b6ae](https://github.com/react-navigation/navigation-ex/commit/ba6b6ae))
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.22...@react-navigation/stack@5.0.0-alpha.23) (2019-10-03)
### Bug Fixes
* fix passing insets to interpolator ([6f5f4b7](https://github.com/react-navigation/navigation-ex/commit/6f5f4b7))
* fix vertical gesture ([a7c4a4d](https://github.com/react-navigation/navigation-ex/commit/a7c4a4d))
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.21...@react-navigation/stack@5.0.0-alpha.22) (2019-10-03)
**Note:** Version bump only for package @react-navigation/stack
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.20...@react-navigation/stack@5.0.0-alpha.21) (2019-10-03)
### Bug Fixes
* add missing React import ([ece6e38](https://github.com/react-navigation/navigation-ex/commit/ece6e38))
* fix header buttons not clickable on Android. fixes [#108](https://github.com/react-navigation/navigation-ex/issues/108) ([da944cc](https://github.com/react-navigation/navigation-ex/commit/da944cc))
* keep the routes we are closing or replacing ([bc3586a](https://github.com/react-navigation/navigation-ex/commit/bc3586a))
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.19...@react-navigation/stack@5.0.0-alpha.20) (2019-09-27) # [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.19...@react-navigation/stack@5.0.0-alpha.20) (2019-09-27)

View File

@@ -10,44 +10,36 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/stack @react-native-community/masked-view yarn add @react-navigation/core @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) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated). 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).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run: If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh ```sh
expo install react-native-gesture-handler react-native-reanimated expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context
``` ```
If you are not using Expo, run the following: If you are not using Expo, run the following:
```sh ```sh
yarn add react-native-reanimated react-native-gesture-handler yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context
``` ```
If you are using Expo, you are done. Otherwise, continue to the next steps. If you are using Expo, you are done. Otherwise, continue to the next steps.
Next, we need to link these libraries. The steps depends on your React Native version: To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
- **React Native 0.60 and higher** ```sh
cd ios
pod install
cd ..
```
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md). To finalize installation of `react-native-screens` for Android, add the following two lines to dependencies section in `android/app/build.gradle`:
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run: ```gradle
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
```sh implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
cd ios ```
pod install
cd ..
```
- **React Native 0.59**
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
```sh
react-native link react-native-reanimated
react-native link react-native-gesture-handler
```
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation. **IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.

View File

@@ -10,7 +10,7 @@
"android", "android",
"stack" "stack"
], ],
"version": "5.0.0-alpha.20", "version": "5.0.0-alpha.24",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,21 +33,21 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8", "@react-navigation/routers": "^5.0.0-alpha.9"
"react-native-safe-area-view": "^0.14.6"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.7.0", "@react-native-community/bob": "^0.7.0",
"@react-native-community/masked-view": "^0.1.1", "@react-native-community/masked-view": "^0.1.1",
"@types/react": "^16.8.24", "@types/react": "^16.9.4",
"@types/react-native": "^0.60.2", "@types/react-native": "^0.60.17",
"del-cli": "^2.0.0", "del-cli": "^3.0.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.10", "react-native": "0.59.10",
"react-native-gesture-handler": "^1.3.0", "react-native-gesture-handler": "^1.3.0",
"react-native-reanimated": "^1.1.0", "react-native-reanimated": "^1.3.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.22", "react-native-screens": "^1.0.0-alpha.22",
"typescript": "^3.5.3" "typescript": "^3.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"@react-native-community/masked-view": "^0.1.1", "@react-native-community/masked-view": "^0.1.1",
@@ -56,6 +56,7 @@
"react-native": "*", "react-native": "*",
"react-native-gesture-handler": "^1.0.0", "react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0", "react-native-reanimated": "^1.0.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.0" "react-native-screens": "^1.0.0-alpha.0"
}, },
"@react-native-community/bob": { "@react-native-community/bob": {

View File

@@ -1,6 +1,5 @@
import { I18nManager } from 'react-native'; import { I18nManager } from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import getStatusBarHeight from '../utils/getStatusBarHeight';
import { import {
StackCardInterpolationProps, StackCardInterpolationProps,
StackCardInterpolatedStyle, StackCardInterpolatedStyle,
@@ -84,9 +83,10 @@ export function forModalPresentationIOS({
current, current,
next, next,
layouts: { screen }, layouts: { screen },
insets,
}: StackCardInterpolationProps): StackCardInterpolatedStyle { }: StackCardInterpolationProps): StackCardInterpolatedStyle {
const topOffset = 10; const topOffset = 10;
const statusBarHeight = getStatusBarHeight(screen.width > screen.height); const statusBarHeight = insets.top;
const aspectRatio = screen.height / screen.width; const aspectRatio = screen.height / screen.width;
const progress = add(current.progress, next ? next.progress : 0); const progress = add(current.progress, next ? next.progress : 0);

View File

@@ -184,10 +184,6 @@ export type StackHeaderOptions = {
* Style object for the header. You can specify a custom background color here, for example. * Style object for the header. You can specify a custom background color here, for example.
*/ */
headerStyle?: StyleProp<ViewStyle>; headerStyle?: StyleProp<ViewStyle>;
/**
* Custom status bar height to set as the top padding in the header.
*/
headerStatusBarHeight?: number;
/** /**
* Defaults to `false`. If `true`, the header will not have a background unless you explicitly provide it with `headerBackground`. * Defaults to `false`. If `true`, the header will not have a background unless you explicitly provide it with `headerBackground`.
* The header will also float over the screen so that it overlaps the content underneath. * The header will also float over the screen so that it overlaps the content underneath.
@@ -245,7 +241,12 @@ export type StackNavigationOptions = StackHeaderOptions &
* Function that given `HeaderProps` returns a React Element to display as a header. * Function that given `HeaderProps` returns a React Element to display as a header.
* Setting to `null` hides header. * Setting to `null` hides header.
*/ */
header?: null | ((props: StackHeaderProps) => React.ReactNode); header?: (props: StackHeaderProps) => React.ReactNode;
/**
* Whether to show the header. The header is shown by default unless `headerMode` was set to `none`.
* Setting this to `false` hides the header.
*/
headerShown?: boolean;
/** /**
* Whether a shadow is visible for the card during transitions. Defaults to `true`. * Whether a shadow is visible for the card during transitions. Defaults to `true`.
*/ */
@@ -291,6 +292,11 @@ export type StackNavigationOptions = StackHeaderOptions &
*/ */
horizontal?: number; horizontal?: number;
}; };
/**
* Number which determines the relevance of velocity for the gesture.
* Defaults to 0.3.
*/
gestureVelocityImpact?: number;
}; };
export type StackNavigationConfig = { export type StackNavigationConfig = {
@@ -445,6 +451,15 @@ export type StackCardInterpolationProps = {
*/ */
screen: Layout; screen: Layout;
}; };
/**
* Safe area insets
*/
insets: {
top: number;
right: number;
bottom: number;
left: number;
};
}; };
export type StackCardInterpolatedStyle = { export type StackCardInterpolatedStyle = {

View File

@@ -1,9 +0,0 @@
import { Platform } from 'react-native';
import { getStatusBarHeight as getStatusBarHeightNative } from 'react-native-safe-area-view';
const getStatusBarHeight = Platform.select({
default: getStatusBarHeightNative,
web: () => 0,
});
export default getStatusBarHeight;

View File

@@ -1,10 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import { StackActions } from '@react-navigation/routers'; import { StackActions } from '@react-navigation/routers';
import { useSafeArea } from 'react-native-safe-area-context';
import HeaderSegment from './HeaderSegment'; import HeaderSegment from './HeaderSegment';
import { StackHeaderProps, StackHeaderTitleProps } from '../../types'; import { StackHeaderProps, StackHeaderTitleProps } from '../../types';
import HeaderTitle from './HeaderTitle'; import HeaderTitle from './HeaderTitle';
export default React.memo(function Header(props: StackHeaderProps) { export default React.memo(function Header(props: StackHeaderProps) {
const insets = useSafeArea();
const { scene, previous, layout, navigation, styleInterpolator } = props; const { scene, previous, layout, navigation, styleInterpolator } = props;
const { options } = scene.descriptor; const { options } = scene.descriptor;
const title = const title =
@@ -35,6 +39,7 @@ export default React.memo(function Header(props: StackHeaderProps) {
return ( return (
<HeaderSegment <HeaderSegment
{...options} {...options}
insets={insets}
layout={layout} layout={layout}
scene={scene} scene={scene}
title={title} title={title}

View File

@@ -77,11 +77,11 @@ export default function HeaderContainer({
const isHeaderStatic = const isHeaderStatic =
mode === 'float' mode === 'float'
? (previousScene && ? (previousScene &&
previousScene.descriptor.options.header === null && previousScene.descriptor.options.headerShown === false &&
// We still need to animate when coming back from next scene // We still need to animate when coming back from next scene
// A hacky way to check this is if the next scene exists // A hacky way to check this is if the next scene exists
!nextScene) || !nextScene) ||
(nextScene && nextScene.descriptor.options.header === null) (nextScene && nextScene.descriptor.options.headerShown === false)
: false; : false;
const props = { const props = {
@@ -121,13 +121,13 @@ export default function HeaderContainer({
: null : null
} }
> >
{options.header !== undefined ? ( {options.headerShown !== false ? (
options.header === null ? null : ( options.header !== undefined ? (
options.header(props) options.header(props)
) : (
<Header {...props} />
) )
) : ( ) : null}
<Header {...props} />
)}
</View> </View>
</NavigationContext.Provider> </NavigationContext.Provider>
); );

View File

@@ -7,10 +7,10 @@ import {
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context';
import { Route } from '@react-navigation/core'; import { Route } from '@react-navigation/core';
import HeaderBackButton from './HeaderBackButton'; import HeaderBackButton from './HeaderBackButton';
import HeaderBackground from './HeaderBackground'; import HeaderBackground from './HeaderBackground';
import getStatusBarHeight from '../../utils/getStatusBarHeight';
import memoize from '../../utils/memoize'; import memoize from '../../utils/memoize';
import { import {
Layout, Layout,
@@ -29,6 +29,7 @@ export type Scene<T> = {
type Props = StackHeaderOptions & { type Props = StackHeaderOptions & {
headerTitle: (props: StackHeaderTitleProps) => React.ReactNode; headerTitle: (props: StackHeaderTitleProps) => React.ReactNode;
layout: Layout; layout: Layout;
insets: EdgeInsets;
onGoBack?: () => void; onGoBack?: () => void;
title?: string; title?: string;
leftLabel?: string; leftLabel?: string;
@@ -57,7 +58,7 @@ const warnIfHeaderStylesDefined = (styles: { [key: string]: any }) => {
}); });
}; };
export const getDefaultHeaderHeight = (layout: Layout) => { export const getDefaultHeaderHeight = (layout: Layout, insets: EdgeInsets) => {
const isLandscape = layout.width > layout.height; const isLandscape = layout.width > layout.height;
let headerHeight; let headerHeight;
@@ -75,7 +76,7 @@ export const getDefaultHeaderHeight = (layout: Layout) => {
headerHeight = 64; headerHeight = 64;
} }
return headerHeight + getStatusBarHeight(isLandscape); return headerHeight + insets.top;
}; };
export default class HeaderSegment extends React.Component<Props, State> { export default class HeaderSegment extends React.Component<Props, State> {
@@ -135,6 +136,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
const { const {
scene, scene,
layout, layout,
insets,
title: currentTitle, title: currentTitle,
leftLabel: previousTitle, leftLabel: previousTitle,
onGoBack, onGoBack,
@@ -142,8 +144,6 @@ export default class HeaderSegment extends React.Component<Props, State> {
headerLeft: left = onGoBack headerLeft: left = onGoBack
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} /> ? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
: undefined, : undefined,
// @ts-ignore
headerStatusBarHeight = getStatusBarHeight(layout.width > layout.height),
headerTransparent, headerTransparent,
headerTintColor, headerTintColor,
headerBackground, headerBackground,
@@ -182,7 +182,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
); );
const { const {
height = getDefaultHeaderHeight(layout), height = getDefaultHeaderHeight(layout, insets),
minHeight, minHeight,
maxHeight, maxHeight,
backgroundColor, backgroundColor,
@@ -306,15 +306,17 @@ export default class HeaderSegment extends React.Component<Props, State> {
pointerEvents="box-none" pointerEvents="box-none"
style={[{ height, minHeight, maxHeight, opacity }]} style={[{ height, minHeight, maxHeight, opacity }]}
> >
<View <View pointerEvents="none" style={{ height: insets.top }} />
pointerEvents="none"
style={{ height: headerStatusBarHeight }}
/>
<View pointerEvents="box-none" style={styles.content}> <View pointerEvents="box-none" style={styles.content}>
{leftButton ? ( {leftButton ? (
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[styles.left, leftButtonStyle, leftContainerStyle]} style={[
styles.left,
{ left: insets.left },
leftButtonStyle,
leftContainerStyle,
]}
> >
{leftButton} {leftButton}
</Animated.View> </Animated.View>
@@ -341,7 +343,12 @@ export default class HeaderSegment extends React.Component<Props, State> {
{right ? ( {right ? (
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[styles.right, rightButtonStyle, rightContainerStyle]} style={[
styles.right,
{ right: insets.right },
rightButtonStyle,
rightContainerStyle,
]}
> >
{right({ tintColor: headerTintColor })} {right({ tintColor: headerTintColor })}
</Animated.View> </Animated.View>

View File

@@ -1,3 +1,5 @@
import * as React from 'react';
type Props = { type Props = {
children: React.ReactElement; children: React.ReactElement;
}; };

View File

@@ -14,6 +14,10 @@ import {
PanGestureHandler, PanGestureHandler,
State as GestureState, State as GestureState,
} from 'react-native-gesture-handler'; } from 'react-native-gesture-handler';
import { EdgeInsets } from 'react-native-safe-area-context';
import PointerEventsView from './PointerEventsView';
import memoize from '../../utils/memoize';
import StackGestureContext from '../../utils/StackGestureContext';
import { import {
TransitionSpec, TransitionSpec,
StackCardStyleInterpolator, StackCardStyleInterpolator,
@@ -21,9 +25,6 @@ import {
SpringConfig, SpringConfig,
TimingConfig, TimingConfig,
} from '../../types'; } from '../../types';
import memoize from '../../utils/memoize';
import StackGestureContext from '../../utils/StackGestureContext';
import PointerEventsView from './PointerEventsView';
type Props = ViewProps & { type Props = ViewProps & {
index: number; index: number;
@@ -33,6 +34,7 @@ type Props = ViewProps & {
next?: Animated.Node<number>; next?: Animated.Node<number>;
current: Animated.Value<number>; current: Animated.Value<number>;
layout: Layout; layout: Layout;
insets: EdgeInsets;
gestureDirection: 'horizontal' | 'vertical'; gestureDirection: 'horizontal' | 'vertical';
onOpen: (isFinished: boolean) => void; onOpen: (isFinished: boolean) => void;
onClose: (isFinished: boolean) => void; onClose: (isFinished: boolean) => void;
@@ -48,6 +50,7 @@ type Props = ViewProps & {
vertical?: number; vertical?: number;
horizontal?: number; horizontal?: number;
}; };
gestureVelocityImpact: number;
transitionSpec: { transitionSpec: {
open: TransitionSpec; open: TransitionSpec;
close: TransitionSpec; close: TransitionSpec;
@@ -86,7 +89,7 @@ const MINUS_ONE_NODE = UNSET_NODE;
const DIRECTION_VERTICAL = -1; const DIRECTION_VERTICAL = -1;
const DIRECTION_HORIZONTAL = 1; const DIRECTION_HORIZONTAL = 1;
const SWIPE_VELOCITY_IMPACT = 0.3; const GESTURE_VELOCITY_IMPACT = 0.3;
/** /**
* The distance of touch start from the edge of the screen where the gesture will be recognized * The distance of touch start from the edge of the screen where the gesture will be recognized
@@ -122,9 +125,8 @@ const {
// We need to be prepared for both version of reanimated. With and w/out proc // We need to be prepared for both version of reanimated. With and w/out proc
let memoizedSpring = spring; let memoizedSpring = spring;
// @ts-ignore
if (Animated.proc) { if (Animated.proc) {
// @ts-ignore
const springHelper = Animated.proc( const springHelper = Animated.proc(
( (
finished: Animated.Value<number>, finished: Animated.Value<number>,
@@ -136,7 +138,7 @@ if (Animated.proc) {
damping: Animated.Adaptable<number>, damping: Animated.Adaptable<number>,
mass: Animated.Adaptable<number>, mass: Animated.Adaptable<number>,
stiffness: Animated.Adaptable<number>, stiffness: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<boolean>, overshootClamping: Animated.Adaptable<number>,
restSpeedThreshold: Animated.Adaptable<number>, restSpeedThreshold: Animated.Adaptable<number>,
restDisplacementThreshold: Animated.Adaptable<number>, restDisplacementThreshold: Animated.Adaptable<number>,
clock: Animated.Clock clock: Animated.Clock
@@ -177,7 +179,7 @@ if (Animated.proc) {
damping: Animated.Adaptable<number>; damping: Animated.Adaptable<number>;
mass: Animated.Adaptable<number>; mass: Animated.Adaptable<number>;
stiffness: Animated.Adaptable<number>; stiffness: Animated.Adaptable<number>;
overshootClamping: Animated.Adaptable<boolean>; overshootClamping: Animated.Adaptable<number>;
restSpeedThreshold: Animated.Adaptable<number>; restSpeedThreshold: Animated.Adaptable<number>;
restDisplacementThreshold: Animated.Adaptable<number>; restDisplacementThreshold: Animated.Adaptable<number>;
} }
@@ -229,10 +231,16 @@ export default class Card extends React.Component<Props> {
overlayEnabled: Platform.OS !== 'ios', overlayEnabled: Platform.OS !== 'ios',
shadowEnabled: true, shadowEnabled: true,
gestureEnabled: true, gestureEnabled: true,
gestureVelocityImpact: GESTURE_VELOCITY_IMPACT,
}; };
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
const { layout, gestureDirection, closing } = this.props; const {
layout,
gestureDirection,
gestureVelocityImpact,
closing,
} = this.props;
const { width, height } = layout; const { width, height } = layout;
if (width !== prevProps.layout.width) { if (width !== prevProps.layout.width) {
@@ -243,6 +251,10 @@ export default class Card extends React.Component<Props> {
this.layout.height.setValue(height); this.layout.height.setValue(height);
} }
if (gestureVelocityImpact !== prevProps.gestureVelocityImpact) {
this.gestureVelocityImpact.setValue(gestureVelocityImpact);
}
if (gestureDirection !== prevProps.gestureDirection) { if (gestureDirection !== prevProps.gestureDirection) {
this.direction.setValue( this.direction.setValue(
gestureDirection === 'vertical' gestureDirection === 'vertical'
@@ -278,6 +290,9 @@ export default class Card extends React.Component<Props> {
} }
private isVisible = new Value<Binary>(TRUE); private isVisible = new Value<Binary>(TRUE);
private gestureVelocityImpact = new Value<number>(
this.props.gestureVelocityImpact
);
private isVisibleValue: Binary = TRUE; private isVisibleValue: Binary = TRUE;
private nextIsVisible = new Value<Binary | -1>(UNSET); private nextIsVisible = new Value<Binary | -1>(UNSET);
@@ -369,7 +384,8 @@ export default class Card extends React.Component<Props> {
this.props.index, this.props.index,
this.props.current, this.props.current,
this.props.next, this.props.next,
this.props.layout this.props.layout,
this.props.insets
); );
}; };
@@ -472,18 +488,19 @@ export default class Card extends React.Component<Props> {
private extrapolatedPosition = add( private extrapolatedPosition = add(
this.gesture, this.gesture,
multiply(this.velocity, SWIPE_VELOCITY_IMPACT) multiply(this.velocity, this.gestureVelocityImpact)
); );
private exec = [ private exec = [
cond( set(
eq(this.direction, DIRECTION_HORIZONTAL), this.gesture,
set( cond(
this.gesture, eq(this.direction, DIRECTION_HORIZONTAL),
multiply( multiply(
this.gestureUntraversed, this.gestureUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
) ),
this.gestureUntraversed
) )
), ),
set( set(
@@ -578,9 +595,13 @@ export default class Card extends React.Component<Props> {
cond( cond(
this.distance, this.distance,
divide( divide(
multiply( cond(
this.gestureUntraversed, eq(this.direction, DIRECTION_HORIZONTAL),
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE multiply(
this.gestureUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
),
this.gestureUntraversed
), ),
this.distance this.distance
), ),
@@ -646,8 +667,8 @@ export default class Card extends React.Component<Props> {
private handleGestureEventVertical = Animated.event([ private handleGestureEventVertical = Animated.event([
{ {
nativeEvent: { nativeEvent: {
translationY: this.gesture, translationY: this.gestureUntraversed,
velocityY: this.velocity, velocityY: this.velocityUntraversed,
state: this.gestureState, state: this.gestureState,
}, },
}, },
@@ -662,7 +683,8 @@ export default class Card extends React.Component<Props> {
index: number, index: number,
current: Animated.Node<number>, current: Animated.Node<number>,
next: Animated.Node<number> | undefined, next: Animated.Node<number> | undefined,
layout: Layout layout: Layout,
insets: EdgeInsets
) => ) =>
styleInterpolator({ styleInterpolator({
index, index,
@@ -672,6 +694,7 @@ export default class Card extends React.Component<Props> {
layouts: { layouts: {
screen: layout, screen: layout,
}, },
insets,
}) })
); );
@@ -684,7 +707,8 @@ export default class Card extends React.Component<Props> {
this.props.index, this.props.index,
this.props.current, this.props.current,
this.props.next, this.props.next,
this.props.layout this.props.layout,
this.props.insets
); );
private gestureActivationCriteria() { private gestureActivationCriteria() {
@@ -734,6 +758,7 @@ export default class Card extends React.Component<Props> {
current, current,
next, next,
layout, layout,
insets,
overlayEnabled, overlayEnabled,
shadowEnabled, shadowEnabled,
gestureEnabled, gestureEnabled,
@@ -750,7 +775,8 @@ export default class Card extends React.Component<Props> {
index, index,
current, current,
next, next,
layout layout,
insets
); );
} }

View File

@@ -8,6 +8,7 @@ import {
ViewProps, ViewProps,
} from 'react-native'; } from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import * as Screens from 'react-native-screens'; // Import with * as to prevent getters being called import * as Screens from 'react-native-screens'; // Import with * as to prevent getters being called
import { Route } from '@react-navigation/core'; import { Route } from '@react-navigation/core';
@@ -36,12 +37,13 @@ type ProgressValues = {
type Props = { type Props = {
mode: 'card' | 'modal'; mode: 'card' | 'modal';
insets: EdgeInsets;
state: StackNavigationState; state: StackNavigationState;
navigation: StackNavigationHelpers; navigation: StackNavigationHelpers;
descriptors: StackDescriptorMap; descriptors: StackDescriptorMap;
routes: Route<string>[]; routes: Route<string>[];
openingRoutes: string[]; openingRouteKeys: string[];
closingRoutes: string[]; closingRouteKeys: string[];
onGoBack: (props: { route: Route<string> }) => void; onGoBack: (props: { route: Route<string> }) => void;
onOpenRoute: (props: { route: Route<string> }) => void; onOpenRoute: (props: { route: Route<string> }) => void;
onCloseRoute: (props: { route: Route<string> }) => void; onCloseRoute: (props: { route: Route<string> }) => void;
@@ -114,10 +116,11 @@ const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
const getFloatingHeaderHeights = ( const getFloatingHeaderHeights = (
routes: Route<string>[], routes: Route<string>[],
insets: EdgeInsets,
layout: Layout, layout: Layout,
previous: { [key: string]: number } previous: { [key: string]: number }
) => { ) => {
const defaultHeaderHeight = getDefaultHeaderHeight(layout); const defaultHeaderHeight = getDefaultHeaderHeight(layout, insets);
return routes.reduce( return routes.reduce(
(acc, curr) => { (acc, curr) => {
@@ -145,7 +148,7 @@ export default class Stack extends React.Component<Props, State> {
acc[curr.key] = acc[curr.key] =
state.progress[curr.key] || state.progress[curr.key] ||
new Animated.Value( new Animated.Value(
props.openingRoutes.includes(curr.key) && props.openingRouteKeys.includes(curr.key) &&
descriptor && descriptor &&
descriptor.options.animationEnabled !== false descriptor.options.animationEnabled !== false
? 0 ? 0
@@ -201,6 +204,7 @@ export default class Stack extends React.Component<Props, State> {
descriptors: props.descriptors, descriptors: props.descriptors,
floatingHeaderHeights: getFloatingHeaderHeights( floatingHeaderHeights: getFloatingHeaderHeights(
props.routes, props.routes,
props.insets,
state.layout, state.layout,
state.floatingHeaderHeights state.floatingHeaderHeights
), ),
@@ -237,6 +241,7 @@ export default class Stack extends React.Component<Props, State> {
layout, layout,
floatingHeaderHeights: getFloatingHeaderHeights( floatingHeaderHeights: getFloatingHeaderHeights(
this.props.routes, this.props.routes,
this.props.insets,
layout, layout,
{} {}
), ),
@@ -287,11 +292,12 @@ export default class Stack extends React.Component<Props, State> {
render() { render() {
const { const {
mode, mode,
insets,
descriptors, descriptors,
state, state,
navigation, navigation,
routes, routes,
closingRoutes, closingRouteKeys,
onOpenRoute, onOpenRoute,
onCloseRoute, onCloseRoute,
onGoBack, onGoBack,
@@ -332,7 +338,6 @@ export default class Stack extends React.Component<Props, State> {
const focused = focusedRoute.key === route.key; const focused = focusedRoute.key === route.key;
const current = progress[route.key]; const current = progress[route.key];
const scene = scenes[index]; const scene = scenes[index];
const descriptor = scene.descriptor;
const next = self[index + 1] const next = self[index + 1]
? progress[self[index + 1].key] ? progress[self[index + 1].key]
: ANIMATED_ONE; : ANIMATED_ONE;
@@ -348,21 +353,54 @@ export default class Stack extends React.Component<Props, State> {
: 0; : 0;
const { const {
header, headerShown,
headerTransparent, headerTransparent,
cardTransparent, cardTransparent,
cardShadowEnabled, cardShadowEnabled,
cardOverlayEnabled, cardOverlayEnabled,
cardStyle, cardStyle,
gestureResponseDistance, gestureResponseDistance,
gestureVelocityImpact,
gestureDirection = defaultTransitionPreset.gestureDirection, gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec, transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = defaultTransitionPreset.cardStyleInterpolator, cardStyleInterpolator = defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator, headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
} = descriptor } = scene.descriptor
? descriptor.options ? scene.descriptor.options
: ({} as StackNavigationOptions); : ({} as StackNavigationOptions);
let transitionConfig = {
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
};
// When a screen is not the last, it should use next screen's transition config
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
// For example combining a slide and a modal transition would look wrong otherwise
// With this approach, combining different transition styles in the same navigator mostly looks right
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
// but majority of the transitions look alright
if (index !== self.length - 1) {
const nextScene = scenes[index + 1];
if (nextScene) {
const {
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
} = nextScene.descriptor
? nextScene.descriptor.options
: ({} as StackNavigationOptions);
transitionConfig = {
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
};
}
}
return ( return (
<MaybeScreen <MaybeScreen
key={route.key} key={route.key}
@@ -375,8 +413,9 @@ export default class Stack extends React.Component<Props, State> {
index={index} index={index}
active={index === self.length - 1} active={index === self.length - 1}
focused={focused} focused={focused}
closing={closingRoutes.includes(route.key)} closing={closingRouteKeys.includes(route.key)}
layout={layout} layout={layout}
insets={insets}
current={current} current={current}
scene={scene} scene={scene}
previousScene={scenes[index - 1]} previousScene={scenes[index - 1]}
@@ -386,15 +425,14 @@ export default class Stack extends React.Component<Props, State> {
cardOverlayEnabled={cardOverlayEnabled} cardOverlayEnabled={cardOverlayEnabled}
cardShadowEnabled={cardShadowEnabled} cardShadowEnabled={cardShadowEnabled}
cardStyle={cardStyle} cardStyle={cardStyle}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
onPageChangeStart={onPageChangeStart} onPageChangeStart={onPageChangeStart}
onPageChangeConfirm={onPageChangeConfirm} onPageChangeConfirm={onPageChangeConfirm}
onPageChangeCancel={onPageChangeCancel} onPageChangeCancel={onPageChangeCancel}
gestureResponseDistance={gestureResponseDistance} gestureResponseDistance={gestureResponseDistance}
floatingHeaderHeight={floatingHeaderHeights[route.key]} floatingHeaderHeight={floatingHeaderHeights[route.key]}
hasCustomHeader={header === null}
getPreviousRoute={getPreviousRoute} getPreviousRoute={getPreviousRoute}
headerMode={headerMode} headerMode={headerMode}
headerShown={headerShown}
headerTransparent={headerTransparent} headerTransparent={headerTransparent}
renderHeader={renderHeader} renderHeader={renderHeader}
renderScene={renderScene} renderScene={renderScene}
@@ -403,10 +441,10 @@ export default class Stack extends React.Component<Props, State> {
onTransitionStart={this.handleTransitionStart} onTransitionStart={this.handleTransitionStart}
onTransitionEnd={this.handleTransitionEnd} onTransitionEnd={this.handleTransitionEnd}
onGoBack={onGoBack} onGoBack={onGoBack}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
gestureVelocityImpact={gestureVelocityImpact}
gestureDirection={gestureDirection} gestureDirection={gestureDirection}
transitionSpec={transitionSpec} {...transitionConfig}
cardStyleInterpolator={cardStyleInterpolator}
headerStyleInterpolator={headerStyleInterpolator}
/> />
</MaybeScreen> </MaybeScreen>
); );

View File

@@ -1,6 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { View, StyleSheet, Platform, StyleProp, ViewStyle } from 'react-native'; import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import { EdgeInsets } from 'react-native-safe-area-context';
import { StackNavigationState } from '@react-navigation/routers'; import { StackNavigationState } from '@react-navigation/routers';
import { Route } from '@react-navigation/core'; import { Route } from '@react-navigation/core';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer'; import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
@@ -19,6 +20,7 @@ type Props = TransitionPreset & {
focused: boolean; focused: boolean;
closing: boolean; closing: boolean;
layout: Layout; layout: Layout;
insets: EdgeInsets;
current: Animated.Value<number>; current: Animated.Value<number>;
previousScene?: Scene<Route<string>>; previousScene?: Scene<Route<string>>;
scene: Scene<Route<string>>; scene: Scene<Route<string>>;
@@ -28,7 +30,6 @@ type Props = TransitionPreset & {
cardOverlayEnabled?: boolean; cardOverlayEnabled?: boolean;
cardShadowEnabled?: boolean; cardShadowEnabled?: boolean;
cardStyle?: StyleProp<ViewStyle>; cardStyle?: StyleProp<ViewStyle>;
gestureEnabled?: boolean;
getPreviousRoute: (props: { getPreviousRoute: (props: {
route: Route<string>; route: Route<string>;
}) => Route<string> | undefined; }) => Route<string> | undefined;
@@ -45,14 +46,16 @@ type Props = TransitionPreset & {
onPageChangeStart?: () => void; onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void; onPageChangeConfirm?: () => void;
onPageChangeCancel?: () => void; onPageChangeCancel?: () => void;
gestureEnabled?: boolean;
gestureResponseDistance?: { gestureResponseDistance?: {
vertical?: number; vertical?: number;
horizontal?: number; horizontal?: number;
}; };
gestureVelocityImpact?: number;
headerMode: StackHeaderMode; headerMode: StackHeaderMode;
headerShown?: boolean;
headerTransparent?: boolean; headerTransparent?: boolean;
floatingHeaderHeight: number; floatingHeaderHeight: number;
hasCustomHeader: boolean;
}; };
export default class StackItem extends React.PureComponent<Props> { export default class StackItem extends React.PureComponent<Props> {
@@ -93,6 +96,7 @@ export default class StackItem extends React.PureComponent<Props> {
const { const {
index, index,
layout, layout,
insets,
active, active,
focused, focused,
closing, closing,
@@ -104,12 +108,13 @@ export default class StackItem extends React.PureComponent<Props> {
cardOverlayEnabled, cardOverlayEnabled,
cardShadowEnabled, cardShadowEnabled,
cardStyle, cardStyle,
gestureEnabled,
onPageChangeStart, onPageChangeStart,
onPageChangeCancel, onPageChangeCancel,
gestureEnabled,
gestureResponseDistance, gestureResponseDistance,
gestureVelocityImpact,
floatingHeaderHeight, floatingHeaderHeight,
hasCustomHeader, headerShown,
getPreviousRoute, getPreviousRoute,
headerMode, headerMode,
headerTransparent, headerTransparent,
@@ -128,6 +133,7 @@ export default class StackItem extends React.PureComponent<Props> {
transparent={cardTransparent} transparent={cardTransparent}
gestureDirection={gestureDirection} gestureDirection={gestureDirection}
layout={layout} layout={layout}
insets={insets}
current={current} current={current}
next={scene.progress.next} next={scene.progress.next}
closing={closing} closing={closing}
@@ -135,18 +141,19 @@ export default class StackItem extends React.PureComponent<Props> {
onClose={this.handleClose} onClose={this.handleClose}
overlayEnabled={cardOverlayEnabled} overlayEnabled={cardOverlayEnabled}
shadowEnabled={cardShadowEnabled} shadowEnabled={cardShadowEnabled}
gestureEnabled={gestureEnabled}
onTransitionStart={this.handleTransitionStart} onTransitionStart={this.handleTransitionStart}
onGestureBegin={onPageChangeStart} onGestureBegin={onPageChangeStart}
onGestureCanceled={onPageChangeCancel} onGestureCanceled={onPageChangeCancel}
gestureEnabled={gestureEnabled}
gestureResponseDistance={gestureResponseDistance} gestureResponseDistance={gestureResponseDistance}
gestureVelocityImpact={gestureVelocityImpact}
transitionSpec={transitionSpec} transitionSpec={transitionSpec}
styleInterpolator={cardStyleInterpolator} styleInterpolator={cardStyleInterpolator}
accessibilityElementsHidden={!focused} accessibilityElementsHidden={!focused}
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'} importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
pointerEvents="box-none" pointerEvents="box-none"
containerStyle={ containerStyle={
headerMode === 'float' && !headerTransparent && !hasCustomHeader headerMode === 'float' && !headerTransparent && headerShown !== false
? { marginTop: floatingHeaderHeight } ? { marginTop: floatingHeaderHeight }
: null : null
} }
@@ -165,7 +172,6 @@ export default class StackItem extends React.PureComponent<Props> {
state, state,
getPreviousRoute, getPreviousRoute,
styleInterpolator: headerStyleInterpolator, styleInterpolator: headerStyleInterpolator,
style: styles.header,
}) })
: null} : null}
</View> </View>
@@ -182,8 +188,4 @@ const styles = StyleSheet.create({
scene: { scene: {
flex: 1, flex: 1,
}, },
header: {
// This is needed to show elevation shadow
zIndex: Platform.OS === 'android' ? 1 : 0,
},
}); });

View File

@@ -1,5 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import {
SafeAreaProvider,
SafeAreaConsumer,
EdgeInsets,
} from 'react-native-safe-area-context';
import { Route } from '@react-navigation/core'; import { Route } from '@react-navigation/core';
import { StackActions, StackNavigationState } from '@react-navigation/routers'; import { StackActions, StackNavigationState } from '@react-navigation/routers';
@@ -25,12 +30,14 @@ type Props = StackNavigationConfig & {
type State = { type State = {
// Local copy of the routes which are actually rendered // Local copy of the routes which are actually rendered
routes: Route<string>[]; routes: Route<string>[];
// Previous routes, to compare whether routes have changed or not
previousRoutes: Route<string>[];
// List of routes being opened, we need to animate pushing of these new routes // List of routes being opened, we need to animate pushing of these new routes
opening: string[]; openingRouteKeys: string[];
// List of routes being closed, we need to animate popping of these routes // List of routes being closed, we need to animate popping of these routes
closing: string[]; closingRouteKeys: string[];
// List of routes being replaced, we need to keep a copy until the new route animates in // List of routes being replaced, we need to keep a copy until the new route animates in
replacing: string[]; replacingRouteKeys: string[];
// Since the local routes can vary from the routes from props, we need to keep the descriptors for old routes // Since the local routes can vary from the routes from props, we need to keep the descriptors for old routes
// Otherwise we won't be able to access the options for routes that were removed // Otherwise we won't be able to access the options for routes that were removed
descriptors: StackDescriptorMap; descriptors: StackDescriptorMap;
@@ -41,6 +48,11 @@ class StackView extends React.Component<Props, State> {
props: Readonly<Props>, props: Readonly<Props>,
state: Readonly<State> state: Readonly<State>
) { ) {
// If there was no change in routes, we don't need to compute anything
if (props.state.routes === state.previousRoutes && state.routes.length) {
return null;
}
// Here we determine which routes were added or removed to animate them // Here we determine which routes were added or removed to animate them
// We keep a copy of the route being removed in local state to be able to animate it // We keep a copy of the route being removed in local state to be able to animate it
@@ -51,35 +63,23 @@ class StackView extends React.Component<Props, State> {
props.state.routes.slice(0, props.state.index + 1) props.state.routes.slice(0, props.state.index + 1)
: props.state.routes; : props.state.routes;
if (props.state.index < props.state.routes.length - 1) {
console.warn(
'StackRouter provided invalid state, index should always be the last route in the stack.'
);
}
if (!routes.length) {
throw new Error(`There should always be at least one route.`);
}
// If there was no change in routes, we don't need to compute anything
if (routes === state.routes || !state.routes.length) {
return {
routes,
descriptors: props.descriptors,
};
}
// Now we need to determine which routes were added and removed // Now we need to determine which routes were added and removed
let { opening, closing, replacing } = state; let {
openingRouteKeys,
closingRouteKeys,
replacingRouteKeys,
previousRoutes,
} = state;
const previousRoutes = state.routes.filter( const previousFocusedRoute = previousRoutes[previousRoutes.length - 1] as
route => !closing.includes(route.key) && !replacing.includes(route.key) | Route<string>
); | undefined;
const previousFocusedRoute = previousRoutes[previousRoutes.length - 1];
const nextFocusedRoute = routes[routes.length - 1]; const nextFocusedRoute = routes[routes.length - 1];
if (previousFocusedRoute.key !== nextFocusedRoute.key) { if (
previousFocusedRoute &&
previousFocusedRoute.key !== nextFocusedRoute.key
) {
// We only need to animate routes if the focused route changed // We only need to animate routes if the focused route changed
// Animating previous routes won't be visible coz the focused route is on top of everything // Animating previous routes won't be visible coz the focused route is on top of everything
@@ -98,22 +98,33 @@ class StackView extends React.Component<Props, State> {
if ( if (
isAnimationEnabled(nextFocusedRoute) && isAnimationEnabled(nextFocusedRoute) &&
!opening.includes(nextFocusedRoute.key) !openingRouteKeys.includes(nextFocusedRoute.key)
) { ) {
// In this case, we need to animate pushing the focused route // In this case, we need to animate pushing the focused route
// We don't care about animating any other added routes because they won't be visible // We don't care about animating any other added routes because they won't be visible
opening = [...opening, nextFocusedRoute.key]; openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
closing = closing.filter(key => key !== nextFocusedRoute.key); closingRouteKeys = closingRouteKeys.filter(
replacing = replacing.filter(key => key !== nextFocusedRoute.key); key => key !== nextFocusedRoute.key
);
replacingRouteKeys = replacingRouteKeys.filter(
key => key !== nextFocusedRoute.key
);
if (!routes.find(r => r.key === previousFocusedRoute.key)) { if (!routes.find(r => r.key === previousFocusedRoute.key)) {
// The previous focused route isn't present in state, we treat this as a replace // The previous focused route isn't present in state, we treat this as a replace
replacing = [...replacing, previousFocusedRoute.key]; replacingRouteKeys = [
...replacingRouteKeys,
previousFocusedRoute.key,
];
opening = opening.filter(key => key !== previousFocusedRoute.key); openingRouteKeys = openingRouteKeys.filter(
closing = closing.filter(key => key !== previousFocusedRoute.key); key => key !== previousFocusedRoute.key
);
closingRouteKeys = closingRouteKeys.filter(
key => key !== previousFocusedRoute.key
);
// Keep the old route in state because it's visible under the new route, and removing it will feel abrupt // Keep the old route in state because it's visible under the new route, and removing it will feel abrupt
// We need to insert it just before the focused one (the route being pushed) // We need to insert it just before the focused one (the route being pushed)
@@ -127,14 +138,18 @@ class StackView extends React.Component<Props, State> {
if ( if (
isAnimationEnabled(previousFocusedRoute) && isAnimationEnabled(previousFocusedRoute) &&
!closing.includes(previousFocusedRoute.key) !closingRouteKeys.includes(previousFocusedRoute.key)
) { ) {
// Sometimes a route can be closed before the opening animation finishes // Sometimes a route can be closed before the opening animation finishes
// So we also need to remove it from the opening list // So we also need to remove it from the opening list
closing = [...closing, previousFocusedRoute.key]; closingRouteKeys = [...closingRouteKeys, previousFocusedRoute.key];
opening = opening.filter(key => key !== previousFocusedRoute.key); openingRouteKeys = openingRouteKeys.filter(
replacing = replacing.filter(key => key !== previousFocusedRoute.key); key => key !== previousFocusedRoute.key
);
replacingRouteKeys = replacingRouteKeys.filter(
key => key !== previousFocusedRoute.key
);
// Keep a copy of route being removed in the state to be able to animate it // Keep a copy of route being removed in the state to be able to animate it
routes = [...routes, previousFocusedRoute]; routes = [...routes, previousFocusedRoute];
@@ -144,6 +159,21 @@ class StackView extends React.Component<Props, State> {
// i.e. the currently focused route already existed and the previously focused route still exists // i.e. the currently focused route already existed and the previously focused route still exists
// We don't know how to animate this // We don't know how to animate this
} }
} else if (replacingRouteKeys.length || closingRouteKeys.length) {
// Keep the routes we are closing or replacing
routes = routes.slice();
routes.splice(
routes.length - 1,
0,
...state.routes.filter(
({ key }) =>
replacingRouteKeys.includes(key) || closingRouteKeys.includes(key)
)
);
}
if (!routes.length) {
throw new Error(`There should always be at least one route.`);
} }
const descriptors = routes.reduce( const descriptors = routes.reduce(
@@ -158,18 +188,20 @@ class StackView extends React.Component<Props, State> {
return { return {
routes, routes,
opening, previousRoutes: props.state.routes,
closing, openingRouteKeys,
replacing, closingRouteKeys,
replacingRouteKeys,
descriptors, descriptors,
}; };
} }
state: State = { state: State = {
routes: [], routes: [],
opening: [], previousRoutes: [],
closing: [], openingRouteKeys: [],
replacing: [], closingRouteKeys: [],
replacingRouteKeys: [],
descriptors: {}, descriptors: {},
}; };
@@ -194,11 +226,12 @@ class StackView extends React.Component<Props, State> {
}; };
private getPreviousRoute = ({ route }: { route: Route<string> }) => { private getPreviousRoute = ({ route }: { route: Route<string> }) => {
const { closing, replacing } = this.state; const { closingRouteKeys, replacingRouteKeys } = this.state;
const routes = this.state.routes.filter( const routes = this.state.routes.filter(
r => r =>
r.key === route.key || r.key === route.key ||
(!closing.includes(r.key) && !replacing.includes(r.key)) (!closingRouteKeys.includes(r.key) &&
!replacingRouteKeys.includes(r.key))
); );
const index = routes.findIndex(r => r.key === route.key); const index = routes.findIndex(r => r.key === route.key);
@@ -234,12 +267,12 @@ class StackView extends React.Component<Props, State> {
private handleOpenRoute = ({ route }: { route: Route<string> }) => { private handleOpenRoute = ({ route }: { route: Route<string> }) => {
this.setState(state => ({ this.setState(state => ({
routes: state.replacing.length routes: state.replacingRouteKeys.length
? state.routes.filter(r => !state.replacing.includes(r.key)) ? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key))
: state.routes, : state.routes,
opening: state.opening.filter(key => key !== route.key), openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
closing: state.closing.filter(key => key !== route.key), closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key),
replacing: [], replacingRouteKeys: [],
})); }));
}; };
@@ -250,8 +283,8 @@ class StackView extends React.Component<Props, State> {
// @ts-ignore // @ts-ignore
this.setState(state => ({ this.setState(state => ({
routes: state.routes.filter(r => r.key !== route.key), routes: state.routes.filter(r => r.key !== route.key),
opening: state.opening.filter(key => key !== route.key), openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
closing: state.closing.filter(key => key !== route.key), closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key),
})); }));
}; };
@@ -266,33 +299,45 @@ class StackView extends React.Component<Props, State> {
...rest ...rest
} = this.props; } = this.props;
const { routes, descriptors, opening, closing } = this.state; const {
routes,
descriptors,
openingRouteKeys,
closingRouteKeys,
} = this.state;
const headerMode = const headerMode =
mode !== 'modal' && Platform.OS === 'ios' ? 'float' : 'screen'; mode !== 'modal' && Platform.OS === 'ios' ? 'float' : 'screen';
return ( return (
<Stack <SafeAreaProvider>
mode={mode} <SafeAreaConsumer>
getPreviousRoute={this.getPreviousRoute} {insets => (
getGesturesEnabled={this.getGesturesEnabled} <Stack
routes={routes} mode={mode}
openingRoutes={opening} insets={insets as EdgeInsets}
closingRoutes={closing} getPreviousRoute={this.getPreviousRoute}
onGoBack={this.handleGoBack} getGesturesEnabled={this.getGesturesEnabled}
onOpenRoute={this.handleOpenRoute} routes={routes}
onCloseRoute={this.handleCloseRoute} openingRouteKeys={openingRouteKeys}
onPageChangeStart={onPageChangeStart} closingRouteKeys={closingRouteKeys}
onPageChangeConfirm={onPageChangeConfirm} onGoBack={this.handleGoBack}
onPageChangeCancel={onPageChangeCancel} onOpenRoute={this.handleOpenRoute}
renderHeader={this.renderHeader} onCloseRoute={this.handleCloseRoute}
renderScene={this.renderScene} onPageChangeStart={onPageChangeStart}
headerMode={headerMode} onPageChangeConfirm={onPageChangeConfirm}
state={state} onPageChangeCancel={onPageChangeCancel}
navigation={navigation} renderHeader={this.renderHeader}
descriptors={descriptors} renderScene={this.renderScene}
{...rest} headerMode={headerMode}
/> state={state}
navigation={navigation}
descriptors={descriptors}
{...rest}
/>
)}
</SafeAreaConsumer>
</SafeAreaProvider>
); );
} }
} }

3265
yarn.lock

File diff suppressed because it is too large Load Diff