Compare commits

...

27 Commits

Author SHA1 Message Date
osdnk
a7e0c193cd chore: publish
- react-navigation-animated-switch@0.5.8
 - @react-navigation/core@3.7.3
 - react-navigation-drawer@2.4.9
 - react-navigation-material-bottom-tabs@2.2.8
 - @react-navigation/native@3.7.9
 - react-navigation@4.3.5
 - react-navigation-stack@2.3.9
 - react-navigation-tabs@2.8.9
2020-03-28 22:34:14 +01:00
Satyajit Sahoo
ac98c0a668 fix: always emit didFocus/didBlur for root navigator 2020-03-28 17:20:33 +01:00
Satyajit Sahoo
14a6538cc8 fix: emit didFocus and didBlur events based on parent's transition 2020-03-28 16:04:06 +01:00
Satyajit Sahoo
c9313a1419 chore: publish
- react-navigation-animated-switch@0.5.7
 - @react-navigation/core@3.7.2
 - react-navigation-drawer@2.4.8
 - react-navigation-material-bottom-tabs@2.2.7
 - @react-navigation/native@3.7.8
 - react-navigation@4.3.4
 - react-navigation-stack@2.3.8
 - react-navigation-tabs@2.8.8
2020-03-28 00:45:14 +01:00
Satyajit Sahoo
ec39721a74 chore: update yarn.lock 2020-03-27 21:55:06 +01:00
Satyajit Sahoo
a54d48b650 chore: add babel-jest 2020-03-27 21:44:39 +01:00
Satyajit Sahoo
8a9a9cd7d1 chore: sync latest stack 2020-03-27 21:34:40 +01:00
Satyajit Sahoo
cd08338186 fix: rework focus and blur events to make them more reliable
closes #4867, #6187, #6451, #7628, #7749
2020-03-27 21:28:30 +01:00
Satyajit Sahoo
d2433f0ab8 fix: don't handle prune if there's only one route
This was preventing the action from bubbling up which would prevent the screen from getting removed
2020-03-27 20:09:52 +01:00
osdnk
66374db859 chore: publish
- react-navigation-stack@2.3.7
2020-03-27 10:25:42 +01:00
osdnk
8dbe0299a8 chore: sync latest stack 2020-03-27 10:24:28 +01:00
Satyajit Sahoo
e4bc6dd506 chore: publish
- react-navigation-stack@2.3.6
2020-03-23 11:45:26 +01:00
Satyajit Sahoo
4bac3bfc1a chore: sync latest stack 2020-03-23 11:44:28 +01:00
Satyajit Sahoo
7656b35ee8 chore: publish
- react-navigation-animated-switch@0.5.6
 - @react-navigation/core@3.7.1
 - react-navigation-drawer@2.4.7
 - react-navigation-material-bottom-tabs@2.2.6
 - @react-navigation/native@3.7.7
 - react-navigation@4.3.3
 - react-navigation-stack@2.3.5
 - react-navigation-tabs@2.8.7
2020-03-23 00:09:22 +01:00
Satyajit Sahoo
597aa51fad chore: sync latest stack 2020-03-23 00:08:30 +01:00
Satyajit Sahoo
a929933bde chore: update prettier 2020-03-23 00:02:47 +01:00
Satyajit Sahoo
911d6bb2f4 chore: explicitely set tag for core and native packages 2020-03-22 17:08:01 +01:00
Satyajit Sahoo
5afc82b11a chore: publish
- react-navigation-animated-switch@0.5.5
 - react-navigation-drawer@2.4.6
 - react-navigation-material-bottom-tabs@2.2.5
 - @react-navigation/native@3.7.6
 - react-navigation@4.3.2
 - react-navigation-stack@2.3.4
 - react-navigation-tabs@2.8.6
2020-03-20 14:41:05 +01:00
Satyajit Sahoo
80c8c9d1de fix: don't mix import and module.exports. closes #7660 2020-03-20 14:28:24 +01:00
Satyajit Sahoo
fd7c5ca9b2 chore: publish
- react-navigation-stack@2.3.3
2020-03-20 11:49:37 +01:00
Satyajit Sahoo
72bbebc80e chore: sync latest stack 2020-03-20 11:40:21 +01:00
Satyajit Sahoo
915861e601 chore: publish
- react-navigation-stack@2.3.2
2020-03-19 20:12:07 +01:00
Satyajit Sahoo
8152ae1212 chore: sync latest stack 2020-03-19 20:11:34 +01:00
Satyajit Sahoo
79125bfab9 chore: publish
- react-navigation-drawer@2.4.5
 - react-navigation-tabs@2.8.5
2020-03-19 16:44:44 +01:00
Satyajit Sahoo
44c390075f fix: don't use react-native-screens on web
seems `react-native-screens` doesn't handle active screens properly and shows a blank page
 instead on web when a number is specified in the `active` prop.

closes #7485
2020-03-19 16:43:28 +01:00
Satyajit Sahoo
1c5e7a5ff2 chore: publish
- react-navigation-animated-switch@0.5.4
 - react-navigation-drawer@2.4.4
 - react-navigation-material-bottom-tabs@2.2.4
 - @react-navigation/native@3.7.5
 - react-navigation@4.3.1
 - react-navigation-stack@2.3.1
 - react-navigation-tabs@2.8.4
2020-03-16 19:22:04 +01:00
Satyajit Sahoo
cfc1bac4e1 fix: downgrade react-native-safe-area-view
closes #7813
2020-03-16 19:21:05 +01:00
87 changed files with 3217 additions and 2643 deletions

View File

@@ -335,7 +335,7 @@ class MainScreen extends React.Component<any, State> {
}} }}
> >
{(Object.keys(ExampleInfo) as (keyof typeof ExampleInfo)[]).map( {(Object.keys(ExampleInfo) as (keyof typeof ExampleInfo)[]).map(
routeName => ( (routeName) => (
<RectButton <RectButton
key={routeName} key={routeName}
underlayColor="#ccc" underlayColor="#ccc"

View File

@@ -1,4 +1,4 @@
module.exports = function(api) { module.exports = function (api) {
api.cache(true); api.cache(true);
return { return {
presets: ['babel-preset-expo'], presets: ['babel-preset-expo'],

View File

@@ -11,8 +11,8 @@ const modules = ['@expo/vector-icons']
// List all packages under `packages/` // List all packages under `packages/`
.readdirSync(packages) .readdirSync(packages)
// Ignore hidden files such as .DS_Store // Ignore hidden files such as .DS_Store
.filter(p => !p.startsWith('.')) .filter((p) => !p.startsWith('.'))
.map(p => { .map((p) => {
const pak = JSON.parse( const pak = JSON.parse(
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8') fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
); );
@@ -50,7 +50,7 @@ module.exports = {
}, },
server: { server: {
enhanceMiddleware: middleware => { enhanceMiddleware: (middleware) => {
return (req, res, next) => { return (req, res, next) => {
// When an asset is imported outside the project root, it has wrong path on Android // When an asset is imported outside the project root, it has wrong path on Android
// This happens for the back button in stack, so we fix the path to correct one // This happens for the back button in stack, so we fix the path to correct one

View File

@@ -62,7 +62,7 @@ const CustomTabBar = ({
const { routes } = navigation.state; const { routes } = navigation.state;
return ( return (
<SafeAreaView style={styles.tabContainer}> <SafeAreaView style={styles.tabContainer}>
{routes.map(route => ( {routes.map((route) => (
<BorderlessButton <BorderlessButton
onPress={() => navigation.navigate(route.routeName)} onPress={() => navigation.navigate(route.routeName)}
style={styles.tab} style={styles.tab}

View File

@@ -44,7 +44,7 @@ const ContainerWithButtons = withNavigation(
const MapScreen = () => ( const MapScreen = () => (
<ContainerWithButtons> <ContainerWithButtons>
<DrawerGestureContext.Consumer> <DrawerGestureContext.Consumer>
{ref => ( {(ref) => (
<NativeViewGestureHandler waitFor={ref}> <NativeViewGestureHandler waitFor={ref}>
<MapView style={{ flex: 1 }} /> <MapView style={{ flex: 1 }} />
</NativeViewGestureHandler> </NativeViewGestureHandler>
@@ -60,7 +60,7 @@ MapScreen.navigationOptions = {
const WebViewScreen = () => ( const WebViewScreen = () => (
<ContainerWithButtons> <ContainerWithButtons>
<DrawerGestureContext.Consumer> <DrawerGestureContext.Consumer>
{ref => ( {(ref) => (
<NativeViewGestureHandler waitFor={ref}> <NativeViewGestureHandler waitFor={ref}>
<WebView <WebView
style={{ flex: 1 }} style={{ flex: 1 }}

View File

@@ -33,7 +33,7 @@ const BarCodeScreenBase = (
<BarCodeScanner <BarCodeScanner
onBarCodeScanned={ onBarCodeScanned={
props.isFocused props.isFocused
? data => { ? (data) => {
console.log('scanned...'); console.log('scanned...');
props.navigation.navigate('Info', { data }); props.navigation.navigate('Info', { data });
} }
@@ -51,7 +51,7 @@ BarCodeScreenBase.navigationOptions = {
const BarCodeScreen = withNavigationFocus(BarCodeScreenBase); const BarCodeScreen = withNavigationFocus(BarCodeScreenBase);
const InfoScreen: NavigationStackScreenComponent = props => { const InfoScreen: NavigationStackScreenComponent = (props) => {
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<Text>{JSON.stringify(props.navigation.getParam('data'))}</Text> <Text>{JSON.stringify(props.navigation.getParam('data'))}</Text>

View File

@@ -82,7 +82,7 @@ class ListScreen extends React.Component<
state = { isInverted: false }; state = { isInverted: false };
onSwitch = () => onSwitch = () =>
this.setState(prevState => ({ isInverted: !prevState.isInverted })); this.setState((prevState) => ({ isInverted: !prevState.isInverted }));
render() { render() {
return ( return (

View File

@@ -158,7 +158,7 @@ const DrawerContents = ({
<Animated.View style={{ transform: [{ translateX }] }}> <Animated.View style={{ transform: [{ translateX }] }}>
<ScrollView> <ScrollView>
<SafeAreaView forceInset={{ top: 'always' }}> <SafeAreaView forceInset={{ top: 'always' }}>
{navigation.state.routes.map(route => ( {navigation.state.routes.map((route) => (
<DrawerItem <DrawerItem
key={route.key} key={route.key}
navigation={descriptors[route.key].navigation} navigation={descriptors[route.key].navigation}

View File

@@ -32,7 +32,7 @@ class RightDrawer extends Component<DrawerContentComponentProps> {
<ScrollView <ScrollView
style={{ height: '100%', width: '100%', backgroundColor: '#333333' }} style={{ height: '100%', width: '100%', backgroundColor: '#333333' }}
> >
{this.state.categories.map(key => { {this.state.categories.map((key) => {
return ( return (
<TouchableOpacity <TouchableOpacity
key={key.n} key={key.n}

View File

@@ -59,7 +59,7 @@ export default function PhotoGrid({ id }: { id: string }) {
return ( return (
<NavigationAwareScrollView contentContainerStyle={styles.content}> <NavigationAwareScrollView contentContainerStyle={styles.content}>
{PHOTOS.map(uri => ( {PHOTOS.map((uri) => (
<View key={uri} style={styles.item}> <View key={uri} style={styles.item}>
<Image source={{ uri }} style={styles.photo} /> <Image source={{ uri }} style={styles.photo} />
</View> </View>

View File

@@ -25,7 +25,7 @@ const ListScreen = (props: NavigationStackScreenProps) => (
const AnotherScreen = () => ( const AnotherScreen = () => (
<CardAnimationContext.Consumer> <CardAnimationContext.Consumer>
{value => { {(value) => {
const scale = value const scale = value
? value.current.progress.interpolate({ ? value.current.progress.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
@@ -67,7 +67,7 @@ const YetAnotherScreen = () => (
}} }}
> >
<CardAnimationContext.Consumer> <CardAnimationContext.Consumer>
{value => ( {(value) => (
<Animated.Text <Animated.Text
style={{ style={{
fontSize: 24, fontSize: 24,
@@ -84,7 +84,7 @@ const YetAnotherScreen = () => (
)} )}
</CardAnimationContext.Consumer> </CardAnimationContext.Consumer>
<CardAnimationContext.Consumer> <CardAnimationContext.Consumer>
{value => ( {(value) => (
<Animated.Text <Animated.Text
style={{ style={{
fontSize: 24, fontSize: 24,

View File

@@ -59,7 +59,7 @@ const Drawer = createDrawerNavigator(
}, },
}, },
{ {
contentComponent: props => <Menu {...props} />, contentComponent: (props) => <Menu {...props} />,
navigationOptions: { title: 'Example' }, navigationOptions: { title: 'Example' },
} }
); );

View File

@@ -5,7 +5,7 @@ import {
NavigationStackScreenComponent, NavigationStackScreenComponent,
} from 'react-navigation-stack'; } from 'react-navigation-stack';
const ListScreen: NavigationStackScreenComponent = function(props) { const ListScreen: NavigationStackScreenComponent = function (props) {
return ( return (
<View <View
style={{ style={{
@@ -29,7 +29,7 @@ const ListScreen: NavigationStackScreenComponent = function(props) {
); );
}; };
const ModalDialogScreen: NavigationStackScreenComponent = function(props) { const ModalDialogScreen: NavigationStackScreenComponent = function (props) {
return ( return (
<View <View
style={{ style={{

View File

@@ -33,6 +33,7 @@
"@babel/runtime": "^7.8.7", "@babel/runtime": "^7.8.7",
"@commitlint/config-conventional": "^8.3.4", "@commitlint/config-conventional": "^8.3.4",
"@types/jest": "^25.1.4", "@types/jest": "^25.1.4",
"babel-jest": "^25.2.3",
"commitlint": "^8.3.5", "commitlint": "^8.3.5",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"eslint": "^6.8.0", "eslint": "^6.8.0",
@@ -40,7 +41,7 @@
"husky": "^4.2.1", "husky": "^4.2.1",
"jest": "^25.1.0", "jest": "^25.1.0",
"lerna": "^3.20.2", "lerna": "^3.20.2",
"prettier": "^1.19.1", "prettier": "^2.0.1",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },
"resolutions": { "resolutions": {

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.8](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.6...react-navigation-animated-switch@0.5.8) (2020-03-28)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.5.7](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.6...react-navigation-animated-switch@0.5.7) (2020-03-27)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.5.6](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.5...react-navigation-animated-switch@0.5.6) (2020-03-22)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.5.5](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.4...react-navigation-animated-switch@0.5.5) (2020-03-20)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.5.4](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.3...react-navigation-animated-switch@0.5.4) (2020-03-16)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.5.3](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.2...react-navigation-animated-switch@0.5.3) (2020-03-16) ## [0.5.3](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.2...react-navigation-animated-switch@0.5.3) (2020-03-16)
**Note:** Version bump only for package react-navigation-animated-switch **Note:** Version bump only for package react-navigation-animated-switch

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-animated-switch", "name": "react-navigation-animated-switch",
"version": "0.5.3", "version": "0.5.8",
"description": "Animated switch for React Navigation", "description": "Animated switch for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -28,7 +28,7 @@
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-reanimated": "~1.7.0", "react-native-reanimated": "~1.7.0",
"react-navigation": "^4.3.0", "react-navigation": "^4.3.5",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -3,6 +3,40 @@
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.
## [3.7.3](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.1...@react-navigation/core@3.7.3) (2020-03-28)
### Bug Fixes
* always emit didFocus/didBlur for root navigator ([ac98c0a](https://github.com/react-navigation/react-navigation-core/commit/ac98c0a668fe21200d0c6f62ae3043f92cc5aa7e))
* don't handle prune if there's only one route ([d2433f0](https://github.com/react-navigation/react-navigation-core/commit/d2433f0ab8f9791df8169de4ddfdeed9bc699e3e))
* emit didFocus and didBlur events based on parent's transition ([14a6538](https://github.com/react-navigation/react-navigation-core/commit/14a6538cc8e12c50d5d10722d75c9395a0a281ec))
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-core/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-core/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-core/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-core/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-core/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-core/issues/7749)
## [3.7.2](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.1...@react-navigation/core@3.7.2) (2020-03-27)
### Bug Fixes
* don't handle prune if there's only one route ([d2433f0](https://github.com/react-navigation/react-navigation-core/commit/d2433f0ab8f9791df8169de4ddfdeed9bc699e3e))
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-core/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-core/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-core/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-core/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-core/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-core/issues/7749)
## [3.7.1](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.0...@react-navigation/core@3.7.1) (2020-03-22)
**Note:** Version bump only for package @react-navigation/core
# [3.7.0](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.6.1...@react-navigation/core@3.7.0) (2020-03-16) # [3.7.0](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.6.1...@react-navigation/core@3.7.0) (2020-03-16)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@react-navigation/core", "name": "@react-navigation/core",
"version": "3.7.0", "version": "3.7.3",
"description": "Core utilities for the react-navigation framework", "description": "Core utilities for the react-navigation framework",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -15,7 +15,8 @@
}, },
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
"access": "public" "access": "public",
"tag": "4.x"
}, },
"keywords": [ "keywords": [
"react-navigation", "react-navigation",

View File

@@ -13,7 +13,7 @@ const StateUtils = {
* Gets a route by key. If the route isn't found, returns `null`. * Gets a route by key. If the route isn't found, returns `null`.
*/ */
get(state, key) { get(state, key) {
return state.routes.find(route => route.key === key) || null; return state.routes.find((route) => route.key === key) || null;
}, },
/** /**
@@ -21,7 +21,7 @@ const StateUtils = {
* routes of the navigation state, or -1 if it is not present. * routes of the navigation state, or -1 if it is not present.
*/ */
indexOf(state, key) { indexOf(state, key) {
return state.routes.findIndex(route => route.key === key); return state.routes.findIndex((route) => route.key === key);
}, },
/** /**
@@ -29,7 +29,7 @@ const StateUtils = {
* routes of the navigation state. * routes of the navigation state.
*/ */
has(state, key) { has(state, key) {
return !!state.routes.some(route => route.key === key); return !!state.routes.some((route) => route.key === key);
}, },
/** /**

View File

@@ -0,0 +1,148 @@
/* eslint-disable react/sort-comp */
import React from 'react';
import { NavigationActions, getNavigation, NavigationProvider } from '../index';
export default function createNavigationContainer(Component) {
class NavigationContainer extends React.Component {
static router = Component.router;
static navigationOptions = null;
constructor(props) {
super(props);
this._initialAction = NavigationActions.init();
this.state = {
nav: !props.loadNavigationState
? Component.router.getStateForAction(this._initialAction)
: null,
};
}
_actionEventSubscribers = new Set();
_onNavigationStateChange(prevNav, nav, action) {
if (typeof this.props.onNavigationStateChange === 'function') {
this.props.onNavigationStateChange(prevNav, nav, action);
}
}
componentDidUpdate() {
// Clear cached _navState every tick
if (this._navState === this.state.nav) {
this._navState = null;
}
}
async componentDidMount() {
// Initialize state. This must be done *after* any async code
// so we don't end up with a different value for this.state.nav
// due to changes while async function was resolving
let action = this._initialAction;
// eslint-disable-next-line react/no-access-state-in-setstate
let startupState = this.state.nav;
if (!startupState) {
startupState = Component.router.getStateForAction(action);
}
const dispatchActions = () =>
this._actionEventSubscribers.forEach((subscriber) =>
subscriber({
type: 'action',
action,
state: this.state.nav,
lastState: null,
})
);
if (startupState === this.state.nav) {
dispatchActions();
return;
}
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ nav: startupState }, () => {
dispatchActions();
});
}
dispatch = (action) => {
if (this.props.navigation) {
return this.props.navigation.dispatch(action);
}
// navState will have the most up-to-date value, because setState sometimes behaves asyncronously
this._navState = this._navState || this.state.nav;
const lastNavState = this._navState;
const reducedState = Component.router.getStateForAction(
action,
lastNavState
);
const navState = reducedState === null ? lastNavState : reducedState;
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach((subscriber) =>
subscriber({
type: 'action',
action,
state: navState,
lastState: lastNavState,
})
);
};
if (reducedState === null) {
// The router will return null when action has been handled and the state hasn't changed.
// dispatch returns true when something has been handled.
dispatchActionEvents();
return true;
}
if (navState !== lastNavState) {
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
this._navState = navState;
this.setState({ nav: navState }, () => {
this._onNavigationStateChange(lastNavState, navState, action);
dispatchActionEvents();
});
return true;
}
dispatchActionEvents();
return false;
};
render() {
let navigation = this.props.navigation;
const navState = this.state.nav;
if (!navState) {
return null;
}
if (!this._navigation || this._navigation.state !== navState) {
this._navigation = getNavigation(
Component.router,
navState,
this.dispatch,
this._actionEventSubscribers,
this._getScreenProps,
() => this._navigation
);
}
navigation = this._navigation;
return (
<NavigationProvider value={navigation}>
<Component {...this.props} navigation={navigation} />
</NavigationProvider>
);
}
}
return NavigationContainer;
}

View File

@@ -0,0 +1,241 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import { navigate } from '../NavigationActions';
import TabRouter from '../routers/TabRouter';
import createNavigator from '../navigators/createNavigator';
import createNavigationContainer from '../__fixtures__/createNavigationContainer';
it('fires focus and blur events in root navigator', async () => {
function createTestNavigator(routeConfigMap, config = {}) {
const router = TabRouter(routeConfigMap, config);
return createNavigator(
({ descriptors, navigation }) =>
navigation.state.routes.map((route) => {
const Comp = descriptors[route.key].getComponent();
return (
<Comp
key={route.key}
navigation={descriptors[route.key].navigation}
/>
);
}),
router,
config
);
}
const firstFocusCallback = jest.fn();
const firstBlurCallback = jest.fn();
const secondFocusCallback = jest.fn();
const secondBlurCallback = jest.fn();
const thirdFocusCallback = jest.fn();
const thirdBlurCallback = jest.fn();
const fourthFocusCallback = jest.fn();
const fourthBlurCallback = jest.fn();
const createComponent = (focusCallback, blurCallback) =>
class TestComponent extends React.Component {
componentDidMount() {
const { navigation } = this.props;
this.focusSub = navigation.addListener('willFocus', focusCallback);
this.blurSub = navigation.addListener('willBlur', blurCallback);
}
componentWillUnmount() {
this.focusSub?.remove();
this.blurSub?.remove();
}
render() {
return null;
}
};
const navigation = React.createRef();
const Navigator = createNavigationContainer(
createTestNavigator({
first: createComponent(firstFocusCallback, firstBlurCallback),
second: createComponent(secondFocusCallback, secondBlurCallback),
third: createComponent(thirdFocusCallback, thirdBlurCallback),
fourth: createComponent(fourthFocusCallback, fourthBlurCallback),
})
);
const element = <Navigator ref={navigation} />;
render(element);
expect(firstFocusCallback).toBeCalledTimes(1);
expect(firstBlurCallback).toBeCalledTimes(0);
expect(secondFocusCallback).toBeCalledTimes(0);
expect(secondBlurCallback).toBeCalledTimes(0);
expect(thirdFocusCallback).toBeCalledTimes(0);
expect(thirdBlurCallback).toBeCalledTimes(0);
expect(fourthFocusCallback).toBeCalledTimes(0);
expect(fourthBlurCallback).toBeCalledTimes(0);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'second' }));
});
expect(firstBlurCallback).toBeCalledTimes(1);
expect(secondFocusCallback).toBeCalledTimes(1);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'fourth' }));
});
expect(firstFocusCallback).toBeCalledTimes(1);
expect(firstBlurCallback).toBeCalledTimes(1);
expect(secondFocusCallback).toBeCalledTimes(1);
expect(secondBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(0);
expect(thirdBlurCallback).toBeCalledTimes(0);
expect(fourthFocusCallback).toBeCalledTimes(1);
expect(fourthBlurCallback).toBeCalledTimes(0);
});
it('fires focus and blur events in nested navigator', () => {
function createTestNavigator(routeConfigMap, config = {}) {
const router = TabRouter(routeConfigMap, config);
return createNavigator(
({ descriptors, navigation }) =>
navigation.state.routes.map((route) => {
const Comp = descriptors[route.key].getComponent();
return (
<Comp
key={route.key}
navigation={descriptors[route.key].navigation}
/>
);
}),
router,
config
);
}
const firstFocusCallback = jest.fn();
const firstBlurCallback = jest.fn();
const secondFocusCallback = jest.fn();
const secondBlurCallback = jest.fn();
const thirdFocusCallback = jest.fn();
const thirdBlurCallback = jest.fn();
const fourthFocusCallback = jest.fn();
const fourthBlurCallback = jest.fn();
const createComponent = (focusCallback, blurCallback) =>
class TestComponent extends React.Component {
componentDidMount() {
const { navigation } = this.props;
this.focusSub = navigation.addListener('willFocus', focusCallback);
this.blurSub = navigation.addListener('willBlur', blurCallback);
}
componentWillUnmount() {
this.focusSub?.remove();
this.blurSub?.remove();
}
render() {
return null;
}
};
const Navigator = createNavigationContainer(
createTestNavigator({
first: createComponent(firstFocusCallback, firstBlurCallback),
second: createComponent(secondFocusCallback, secondBlurCallback),
nested: createTestNavigator({
third: createComponent(thirdFocusCallback, thirdBlurCallback),
fourth: createComponent(fourthFocusCallback, fourthBlurCallback),
}),
})
);
const navigation = React.createRef();
const element = <Navigator ref={navigation} />;
render(element);
expect(thirdFocusCallback).toBeCalledTimes(0);
expect(firstFocusCallback).toBeCalledTimes(1);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'nested' }));
});
expect(firstFocusCallback).toBeCalledTimes(1);
expect(fourthFocusCallback).toBeCalledTimes(0);
expect(thirdFocusCallback).toBeCalledTimes(1);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'second' }));
});
expect(thirdFocusCallback).toBeCalledTimes(1);
expect(secondFocusCallback).toBeCalledTimes(1);
expect(fourthBlurCallback).toBeCalledTimes(0);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'nested' }));
});
expect(firstBlurCallback).toBeCalledTimes(1);
expect(secondBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(2);
expect(fourthFocusCallback).toBeCalledTimes(0);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'third' }));
});
expect(fourthBlurCallback).toBeCalledTimes(0);
expect(thirdFocusCallback).toBeCalledTimes(2);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'first' }));
});
expect(firstFocusCallback).toBeCalledTimes(2);
expect(thirdBlurCallback).toBeCalledTimes(2);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'fourth' }));
});
expect(fourthFocusCallback).toBeCalledTimes(1);
expect(thirdBlurCallback).toBeCalledTimes(2);
expect(firstBlurCallback).toBeCalledTimes(2);
act(() => {
navigation.current.dispatch(navigate({ routeName: 'third' }));
});
expect(thirdFocusCallback).toBeCalledTimes(3);
expect(fourthBlurCallback).toBeCalledTimes(1);
// Make sure nothing else has changed
expect(firstFocusCallback).toBeCalledTimes(2);
expect(firstBlurCallback).toBeCalledTimes(2);
expect(secondFocusCallback).toBeCalledTimes(1);
expect(secondBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(3);
expect(thirdBlurCallback).toBeCalledTimes(2);
expect(fourthFocusCallback).toBeCalledTimes(1);
expect(fourthBlurCallback).toBeCalledTimes(1);
});

View File

@@ -1,543 +0,0 @@
import getChildEventSubscriber from '../getChildEventSubscriber';
it('child action events only flow when focused', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const focusedTestState = {
...testState,
index: 1,
};
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
emitParentAction({
type: 'action',
state: focusedTestState,
lastState: testState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
state: focusedTestState,
lastState: focusedTestState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(1);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
});
it('grandchildren subscription', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const parentBlurState = {
key: 'foo',
routeName: 'FooRoute',
routes: [
{ key: 'aunt' },
{
key: 'parent',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 1,
isTransitioning: false,
},
],
index: 0,
isTransitioning: false,
};
const parentTransitionState = {
...parentBlurState,
index: 1,
isTransitioning: true,
};
const parentFocusState = {
...parentTransitionState,
isTransitioning: false,
};
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
emitGrandParentAction({
type: 'action',
state: parentTransitionState,
lastState: parentBlurState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: parentFocusState,
lastState: parentTransitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
});
it('grandchildren transitions', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: 1,
isTransitioning: false,
routes: [
{ key: 'nothing' },
{
key: 'parent',
index: childIndex,
isTransitioning: childIsTransitioning,
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
},
],
});
const blurredState = makeFakeState(0, false);
const transitionState = makeFakeState(1, true);
const focusState = makeFakeState(1, false);
const transition2State = makeFakeState(2, true);
const blurred2State = makeFakeState(2, false);
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitGrandParentAction({
type: 'action',
state: transitionState,
lastState: blurredState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: transitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: focusState,
action: { type: 'TestAction' },
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: transition2State,
lastState: focusState,
action: { type: 'CauseWillBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(0);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: blurred2State,
lastState: transition2State,
action: { type: 'CauseDidBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
});
it('grandchildren pass through transitions', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: childIndex,
isTransitioning: childIsTransitioning,
routes: [
{ key: 'nothing' },
{
key: 'parent',
index: 1,
isTransitioning: false,
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
},
].slice(0, childIndex + 1),
});
const blurredState = makeFakeState(0, false);
const transitionState = makeFakeState(1, true);
const focusState = makeFakeState(1, false);
const transition2State = makeFakeState(0, true);
const blurred2State = makeFakeState(0, false);
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitGrandParentAction({
type: 'action',
state: transitionState,
lastState: blurredState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: transitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: focusState,
action: { type: 'TestAction' },
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: transition2State,
lastState: focusState,
action: { type: 'CauseWillBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(0);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: blurred2State,
lastState: transition2State,
action: { type: 'CauseDidBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
});
it('child focus with transition', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitParentAction({
type: 'didFocus',
action: randomAction,
lastState: testState,
state: testState,
});
emitParentAction({
type: 'action',
action: randomAction,
lastState: testState,
state: {
...testState,
index: 1,
isTransitioning: true,
},
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
isTransitioning: true,
},
state: {
...testState,
index: 1,
isTransitioning: false,
},
});
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
isTransitioning: false,
},
state: {
...testState,
index: 0,
isTransitioning: true,
},
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 0,
isTransitioning: true,
},
state: {
...testState,
index: 0,
isTransitioning: false,
},
});
expect(childDidBlurHandler.mock.calls.length).toBe(1);
});
it('child focus with immediate transition', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitParentAction({
type: 'didFocus',
action: randomAction,
lastState: testState,
state: testState,
});
emitParentAction({
type: 'action',
action: randomAction,
lastState: testState,
state: {
...testState,
index: 1,
},
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
},
state: {
...testState,
index: 0,
},
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
});
const setupEventTest = (subscriptionKey, initialLastFocusEvent) => {
const parentSubscriber = jest.fn();
const emitEvent = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const evtProvider = getChildEventSubscriber(
parentSubscriber,
subscriptionKey,
initialLastFocusEvent
);
const handlers = {};
evtProvider.addListener('action', (handlers.action = jest.fn()));
evtProvider.addListener('willFocus', (handlers.willFocus = jest.fn()));
evtProvider.addListener('didFocus', (handlers.didFocus = jest.fn()));
evtProvider.addListener('willBlur', (handlers.willBlur = jest.fn()));
evtProvider.addListener('didBlur', (handlers.didBlur = jest.fn()));
return { emitEvent, handlers, evtProvider };
};
it('immediate back with uncompleted transition will focus first screen again', () => {
const { handlers, emitEvent } = setupEventTest('key0', 'didFocus');
emitEvent({
type: 'action',
state: {
index: 1,
routes: [{ key: 'key0' }, { key: 'key1' }],
isTransitioning: true,
},
lastState: {
index: 0,
routes: [{ key: 'key0' }],
isTransitioning: false,
},
action: { type: 'Any action, does not matter here' },
});
expect(handlers.willFocus.mock.calls.length).toBe(0);
expect(handlers.didFocus.mock.calls.length).toBe(0);
expect(handlers.willBlur.mock.calls.length).toBe(1);
expect(handlers.didBlur.mock.calls.length).toBe(0);
emitEvent({
type: 'action',
state: {
index: 0,
routes: [{ key: 'key0' }],
isTransitioning: true,
},
lastState: {
index: 1,
routes: [{ key: 'key0' }, { key: 'key1' }],
isTransitioning: true,
},
action: { type: 'Any action, does not matter here' },
});
expect(handlers.willFocus.mock.calls.length).toBe(1);
expect(handlers.didFocus.mock.calls.length).toBe(0);
expect(handlers.willBlur.mock.calls.length).toBe(1);
expect(handlers.didBlur.mock.calls.length).toBe(0);
emitEvent({
type: 'action',
state: {
index: 0,
routes: [{ key: 'key0' }],
isTransitioning: false,
},
lastState: {
index: 0,
routes: [{ key: 'key0' }],
isTransitioning: true,
},
action: { type: 'Any action, does not matter here' },
});
expect(handlers.willFocus.mock.calls.length).toBe(1);
expect(handlers.didFocus.mock.calls.length).toBe(1);
expect(handlers.willBlur.mock.calls.length).toBe(1);
expect(handlers.didBlur.mock.calls.length).toBe(0);
});

View File

@@ -30,7 +30,7 @@ it('getNavigation provides default action helpers', () => {
it.skip('getNavigation provides router action helpers', () => { it.skip('getNavigation provides router action helpers', () => {
const router = { const router = {
getActionCreators: () => ({ getActionCreators: () => ({
foo: bar => ({ type: 'FooBarAction', bar }), foo: (bar) => ({ type: 'FooBarAction', bar }),
}), }),
getStateForAction(action, lastState = {}) { getStateForAction(action, lastState = {}) {
return lastState; return lastState;

View File

@@ -1,196 +0,0 @@
/*
* This is used to extract one children's worth of events from a stream of navigation action events
*
* Based on the 'action' events that get fired for this navigation state, this utility will fire
* focus and blur events for this child
*/
export default function getChildEventSubscriber(
addListener,
key,
initialLastFocusEvent = 'didBlur'
) {
const actionSubscribers = new Set();
const willFocusSubscribers = new Set();
const didFocusSubscribers = new Set();
const willBlurSubscribers = new Set();
const didBlurSubscribers = new Set();
const refocusSubscribers = new Set();
const removeAll = () => {
[
actionSubscribers,
willFocusSubscribers,
didFocusSubscribers,
willBlurSubscribers,
didBlurSubscribers,
refocusSubscribers,
].forEach(set => set.clear());
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const getChildSubscribers = evtName => {
switch (evtName) {
case 'action':
return actionSubscribers;
case 'willFocus':
return willFocusSubscribers;
case 'didFocus':
return didFocusSubscribers;
case 'willBlur':
return willBlurSubscribers;
case 'didBlur':
return didBlurSubscribers;
case 'refocus':
return refocusSubscribers;
default:
return null;
}
};
const emit = (type, payload) => {
const payloadWithType = { ...payload, type };
const subscribers = getChildSubscribers(type);
subscribers &&
subscribers.forEach(subs => {
subs(payloadWithType);
});
};
// lastFocusEvent keeps track of focus state for one route. First we assume
// we are blurred. If we are focused on initialization, the first 'action'
// event will cause onFocus+willFocus events because we had previously been
// considered blurred
let lastFocusEvent = initialLastFocusEvent;
const upstreamEvents = [
'willFocus',
'didFocus',
'willBlur',
'didBlur',
'refocus',
'action',
];
const upstreamSubscribers = upstreamEvents.map(eventName =>
addListener(eventName, payload => {
if (eventName === 'refocus') {
emit(eventName, payload);
return;
}
const { state, lastState, action } = payload;
const lastRoutes = lastState && lastState.routes;
const routes = state && state.routes;
// const lastFocusKey =
// lastState && lastState.routes && lastState.routes[lastState.index].key;
const focusKey = routes && routes[state.index].key;
const isChildFocused = focusKey === key;
const lastRoute =
lastRoutes && lastRoutes.find(route => route.key === key);
const newRoute = routes && routes.find(route => route.key === key);
const childPayload = {
context: `${key}:${action.type}_${payload.context || 'Root'}`,
state: newRoute,
lastState: lastRoute,
action,
type: eventName,
};
const isTransitioning = !!state && state.isTransitioning;
const previouslylastFocusEvent = lastFocusEvent;
if (lastFocusEvent === 'didBlur') {
// The child is currently blurred. Look for willFocus conditions
if (eventName === 'willFocus' && isChildFocused) {
emit((lastFocusEvent = 'willFocus'), childPayload);
} else if (eventName === 'action' && isChildFocused) {
emit((lastFocusEvent = 'willFocus'), childPayload);
}
}
if (lastFocusEvent === 'willFocus') {
// We are currently mid-focus. Look for didFocus conditions.
// If state.isTransitioning is false, this child event happens immediately after willFocus
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
emit((lastFocusEvent = 'didFocus'), childPayload);
} else if (
eventName === 'action' &&
isChildFocused &&
!isTransitioning
) {
emit((lastFocusEvent = 'didFocus'), childPayload);
}
}
if (lastFocusEvent === 'didFocus') {
// The child is currently focused. Look for blurring events
if (!isChildFocused) {
// The child is no longer focused within this navigation state
emit((lastFocusEvent = 'willBlur'), childPayload);
} else if (eventName === 'willBlur') {
// The parent is getting a willBlur event
emit((lastFocusEvent = 'willBlur'), childPayload);
} else if (
eventName === 'action' &&
previouslylastFocusEvent === 'didFocus'
) {
// While focused, pass action events to children for grandchildren focus
emit('action', childPayload);
}
}
if (lastFocusEvent === 'willBlur') {
// The child is mid-blur. Wait for transition to end
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
// The child is done blurring because transitioning is over, or isTransitioning
// never began and didBlur fires immediately after willBlur
emit((lastFocusEvent = 'didBlur'), childPayload);
} else if (eventName === 'didBlur') {
// Pass through the parent didBlur event if it happens
emit((lastFocusEvent = 'didBlur'), childPayload);
} else if (
eventName === 'action' &&
isChildFocused &&
!isTransitioning
) {
emit((lastFocusEvent = 'didFocus'), childPayload);
} else if (
eventName === 'action' &&
isChildFocused &&
isTransitioning
) {
emit((lastFocusEvent = 'willFocus'), childPayload);
}
}
if (lastFocusEvent === 'didBlur' && !newRoute) {
removeAll();
}
})
);
return {
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
},
emit(eventName, payload) {
if (eventName !== 'refocus') {
console.error(
`navigation.emit only supports the 'refocus' event currently.`
);
return;
}
emit(eventName, payload);
},
};
}

View File

@@ -1,9 +1,9 @@
import getChildEventSubscriber from './getChildEventSubscriber';
import getChildRouter from './getChildRouter'; import getChildRouter from './getChildRouter';
import getNavigationActionCreators from './routers/getNavigationActionCreators'; import getNavigationActionCreators from './routers/getNavigationActionCreators';
import getChildrenNavigationCache from './getChildrenNavigationCache'; import getChildrenNavigationCache from './getChildrenNavigationCache';
import getEventManager from './getEventManager';
const createParamGetter = route => (paramName, defaultValue) => { const createParamGetter = (route) => (paramName, defaultValue) => {
const params = route.params; const params = route.params;
if (params && paramName in params) { if (params && paramName in params) {
@@ -15,7 +15,7 @@ const createParamGetter = route => (paramName, defaultValue) => {
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) { function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
const children = getChildrenNavigationCache(navigation); const children = getChildrenNavigationCache(navigation);
const childRoute = navigation.state.routes.find(r => r.key === childKey); const childRoute = navigation.state.routes.find((r) => r.key === childKey);
if (!childRoute) { if (!childRoute) {
return null; return null;
@@ -47,7 +47,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
}; };
const actionHelpers = {}; const actionHelpers = {};
Object.keys(actionCreators).forEach(actionName => { Object.keys(actionCreators).forEach((actionName) => {
actionHelpers[actionName] = (...args) => { actionHelpers[actionName] = (...args) => {
const actionCreator = actionCreators[actionName]; const actionCreator = actionCreators[actionName];
const action = actionCreator(...args); const action = actionCreator(...args);
@@ -78,10 +78,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
}; };
return children[childKey]; return children[childKey];
} else { } else {
const childSubscriber = getChildEventSubscriber( const { addListener, emit } = getEventManager(childKey);
navigation.addListener,
childKey
);
children[childKey] = { children[childKey] = {
...actionHelpers, ...actionHelpers,
@@ -91,7 +88,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
actions: actionCreators, actions: actionCreators,
getParam: createParamGetter(childRoute), getParam: createParamGetter(childRoute),
getChildNavigation: grandChildKey => getChildNavigation: (grandChildKey) =>
getChildNavigation(children[childKey], grandChildKey, () => { getChildNavigation(children[childKey], grandChildKey, () => {
const nav = getCurrentParentNavigation(); const nav = getCurrentParentNavigation();
return nav && nav.getChildNavigation(childKey); return nav && nav.getChildNavigation(childKey);
@@ -115,9 +112,10 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
dispatch: navigation.dispatch, dispatch: navigation.dispatch,
getScreenProps: navigation.getScreenProps, getScreenProps: navigation.getScreenProps,
dangerouslyGetParent: getCurrentParentNavigation, dangerouslyGetParent: getCurrentParentNavigation,
addListener: childSubscriber.addListener, addListener,
emit: childSubscriber.emit, emit,
}; };
return children[childKey]; return children[childKey];
} }
} }

View File

@@ -5,8 +5,8 @@ export default function getChildrenNavigationCache(navigation) {
let childrenNavigationCache = let childrenNavigationCache =
navigation._childrenNavigation || (navigation._childrenNavigation = {}); navigation._childrenNavigation || (navigation._childrenNavigation = {});
let childKeys = navigation.state.routes.map(route => route.key); let childKeys = navigation.state.routes.map((route) => route.key);
Object.keys(childrenNavigationCache).forEach(cacheKey => { Object.keys(childrenNavigationCache).forEach((cacheKey) => {
if (!childKeys.includes(cacheKey) && !navigation.state.isTransitioning) { if (!childKeys.includes(cacheKey) && !navigation.state.isTransitioning) {
delete childrenNavigationCache[cacheKey]; delete childrenNavigationCache[cacheKey];
} }

View File

@@ -0,0 +1,61 @@
// @ts-check
/**
* @param {string} target
*/
export default function getEventManager(target) {
/**
* @type {Record<string, Record<string, ((e: any) => void)[]>>}
*/
const listeners = {};
/**
* @param {string} type
* @param {() => void} callback
*/
const removeListener = (type, callback) => {
const callbacks = listeners[type] ? listeners[type][target] : undefined;
if (!callbacks) {
return;
}
const index = callbacks.indexOf(callback);
callbacks.splice(index, 1);
};
/**
* @param {string} type
* @param {() => void} callback
*/
const addListener = (type, callback) => {
listeners[type] = listeners[type] || {};
listeners[type][target] = listeners[type][target] || [];
listeners[type][target].push(callback);
return {
remove: () => removeListener(type, callback),
};
};
return {
addListener,
/**
* @param {string} type
* @param {any} [data]
*/
emit: (type, data) => {
const items = listeners[type] || {};
/**
* Copy the current list of callbacks in case they are mutated during execution
* @type {((data: any) => void)[] | undefined}
*/
const callbacks = items[target] && items[target].slice();
callbacks?.forEach((cb) => cb(data));
},
};
}

View File

@@ -18,9 +18,9 @@ export default function getNavigation(
state, state,
dispatch, dispatch,
getScreenProps, getScreenProps,
getChildNavigation: childKey => getChildNavigation: (childKey) =>
getChildNavigation(navigation, childKey, getCurrentNavigation), getChildNavigation(navigation, childKey, getCurrentNavigation),
isFocused: childKey => { isFocused: (childKey) => {
const { routes, index } = getCurrentNavigation().state; const { routes, index } = getCurrentNavigation().state;
if (childKey == null || routes[index].key === childKey) { if (childKey == null || routes[index].key === childKey) {
return true; return true;
@@ -48,7 +48,7 @@ export default function getNavigation(
...actions, ...actions,
}; };
Object.keys(actionCreators).forEach(actionName => { Object.keys(actionCreators).forEach((actionName) => {
navigation[actionName] = (...args) => navigation[actionName] = (...args) =>
navigation.dispatch(actionCreators[actionName](...args)); navigation.dispatch(actionCreators[actionName](...args));
}); });

View File

@@ -4,8 +4,8 @@ import { render, fireEvent } from 'react-native-testing-library';
import { createAppContainer } from '@react-navigation/native'; import { createAppContainer } from '@react-navigation/native';
import createSwitchNavigator from '../createSwitchNavigator'; import createSwitchNavigator from '../createSwitchNavigator';
const getActiveRouteName = state => state.routes[state.index].routeName; const getActiveRouteName = (state) => state.routes[state.index].routeName;
const createScreen = (routeName, nextRouteName) => props => ( const createScreen = (routeName, nextRouteName) => (props) => (
<View> <View>
<Text testID="title">{routeName}</Text> <Text testID="title">{routeName}</Text>
<Text testID={routeName + '-isFirstRoute'}> <Text testID={routeName + '-isFirstRoute'}>

View File

@@ -1,6 +1,7 @@
import React from 'react'; import * as React from 'react';
import invariant from '../utils/invariant'; import invariant from '../utils/invariant';
import ThemeContext from '../views/ThemeContext'; import ThemeContext from '../views/ThemeContext';
import NavigationFocusEvents from '../views/NavigationFocusEvents';
function createNavigator(NavigatorView, router, navigationConfig) { function createNavigator(NavigatorView, router, navigationConfig) {
class Navigator extends React.Component { class Navigator extends React.Component {
@@ -78,13 +79,21 @@ function createNavigator(NavigatorView, router, navigationConfig) {
render() { render() {
return ( return (
<NavigatorView <React.Fragment>
{...this.props} <NavigationFocusEvents
screenProps={this.state.screenProps} navigation={this.props.navigation}
navigation={this.props.navigation} onEvent={(target, type, data) => {
navigationConfig={navigationConfig} this.state.descriptors[target]?.navigation.emit(type, data);
descriptors={this.state.descriptors} }}
/> />
<NavigatorView
{...this.props}
screenProps={this.state.screenProps}
navigation={this.props.navigation}
navigationConfig={navigationConfig}
descriptors={this.state.descriptors}
/>
</React.Fragment>
); );
} }
} }

View File

@@ -5,33 +5,33 @@ export const RESET = 'Navigation/RESET';
export const REPLACE = 'Navigation/REPLACE'; export const REPLACE = 'Navigation/REPLACE';
export const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION'; export const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
export const pop = payload => ({ export const pop = (payload) => ({
type: POP, type: POP,
...payload, ...payload,
}); });
export const popToTop = payload => ({ export const popToTop = (payload) => ({
type: POP_TO_TOP, type: POP_TO_TOP,
...payload, ...payload,
}); });
export const push = payload => ({ export const push = (payload) => ({
type: PUSH, type: PUSH,
...payload, ...payload,
}); });
export const reset = payload => ({ export const reset = (payload) => ({
type: RESET, type: RESET,
key: null, key: null,
...payload, ...payload,
}); });
export const replace = payload => ({ export const replace = (payload) => ({
type: REPLACE, type: REPLACE,
...payload, ...payload,
}); });
export const completeTransition = payload => ({ export const completeTransition = (payload) => ({
type: COMPLETE_TRANSITION, type: COMPLETE_TRANSITION,
preserveFocus: true, preserveFocus: true,
...payload, ...payload,

View File

@@ -29,7 +29,7 @@ export default (routeConfigs, stackConfig = {}) => {
const routeNames = Object.keys(routeConfigs); const routeNames = Object.keys(routeConfigs);
// Loop through routes and find child routers // Loop through routes and find child routers
routeNames.forEach(routeName => { routeNames.forEach((routeName) => {
// We're not using `getScreenForRouteName` here to preserve the lazy loading // We're not using `getScreenForRouteName` here to preserve the lazy loading
// behaviour of routes. This means that routes with child routers must be // behaviour of routes. This means that routes with child routers must be
// defined using a component directly or with an object with a screen prop. // defined using a component directly or with an object with a screen prop.
@@ -152,7 +152,7 @@ export default (routeConfigs, stackConfig = {}) => {
n, n,
...params, ...params,
}), }),
popToTop: params => StackActions.popToTop(params), popToTop: (params) => StackActions.popToTop(params),
push: (routeName, params, action) => push: (routeName, params, action) =>
StackActions.push({ StackActions.push({
routeName, routeName,
@@ -280,7 +280,7 @@ export default (routeConfigs, stackConfig = {}) => {
// Before pushing a new route we first try to find one in the existing route stack // Before pushing a new route we first try to find one in the existing route stack
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md // More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
const lastRouteIndex = state.routes.findIndex(r => { const lastRouteIndex = state.routes.findIndex((r) => {
if (action.key) { if (action.key) {
return r.key === action.key; return r.key === action.key;
} else { } else {
@@ -425,7 +425,7 @@ export default (routeConfigs, stackConfig = {}) => {
if (action.key === undefined && state.routes.length) { if (action.key === undefined && state.routes.length) {
routeIndex = state.routes.length - 1; routeIndex = state.routes.length - 1;
} else { } else {
routeIndex = state.routes.findIndex(r => r.key === action.key); routeIndex = state.routes.findIndex((r) => r.key === action.key);
} }
// Only replace if the key matches one of our routes // Only replace if the key matches one of our routes
@@ -467,7 +467,7 @@ export default (routeConfigs, stackConfig = {}) => {
if (action.type === NavigationActions.SET_PARAMS) { if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key; const key = action.key;
const lastRoute = state.routes.find(route => route.key === key); const lastRoute = state.routes.find((route) => route.key === key);
if (lastRoute) { if (lastRoute) {
const params = { const params = {
...lastRoute.params, ...lastRoute.params,
@@ -496,7 +496,7 @@ export default (routeConfigs, stackConfig = {}) => {
return { return {
...state, ...state,
routes: newStackActions.map(newStackAction => { routes: newStackActions.map((newStackAction) => {
const router = childRouters[newStackAction.routeName]; const router = childRouters[newStackAction.routeName];
let childState = {}; let childState = {};
@@ -535,19 +535,22 @@ export default (routeConfigs, stackConfig = {}) => {
const { key, n, immediate, prune } = action; const { key, n, immediate, prune } = action;
if (action.type === StackActions.POP && prune === false && key) { if (action.type === StackActions.POP && prune === false && key) {
const index = state.routes.findIndex(r => r.key === key); const index = state.routes.findIndex((r) => r.key === key);
const count = Math.max(index - (n == null ? 1 : n) + 1, 1);
const routes = state.routes
.slice(0, count)
.concat(state.routes.slice(index + 1));
if (routes.length) { if (index > 0) {
return { const count = Math.max(index - (n ?? 1) + 1, 1);
...state, const routes = state.routes
routes, .slice(0, count)
index: routes.length - 1, .concat(state.routes.slice(index + 1));
isTransitioning: immediate !== true,
}; if (routes.length) {
return {
...state,
routes,
index: routes.length - 1,
isTransitioning: immediate !== true,
};
}
} }
} else { } else {
let backRouteIndex = state.index; let backRouteIndex = state.index;
@@ -557,7 +560,7 @@ export default (routeConfigs, stackConfig = {}) => {
// back from state.index, as if it were a normal "BACK" action // back from state.index, as if it were a normal "BACK" action
backRouteIndex = Math.max(1, state.index - n + 1); backRouteIndex = Math.max(1, state.index - n + 1);
} else if (key) { } else if (key) {
const backRoute = state.routes.find(route => route.key === key); const backRoute = state.routes.find((route) => route.key === key);
backRouteIndex = state.routes.indexOf(backRoute); backRouteIndex = state.routes.indexOf(backRoute);
} }

View File

@@ -30,12 +30,12 @@ export default (routeConfigs, config = {}) => {
if (initialRouteIndex === -1) { if (initialRouteIndex === -1) {
throw new Error( throw new Error(
`Invalid initialRouteName '${initialRouteName}'.` + `Invalid initialRouteName '${initialRouteName}'.` +
`Should be one of ${order.map(n => `"${n}"`).join(', ')}` `Should be one of ${order.map((n) => `"${n}"`).join(', ')}`
); );
} }
const childRouters = {}; const childRouters = {};
order.forEach(routeName => { order.forEach((routeName) => {
childRouters[routeName] = null; childRouters[routeName] = null;
const screen = getScreenForRouteName(routeConfigs, routeName); const screen = getScreenForRouteName(routeConfigs, routeName);
if (screen.router) { if (screen.router) {
@@ -93,7 +93,7 @@ export default (routeConfigs, config = {}) => {
if (action.type === NavigationActions.NAVIGATE) { if (action.type === NavigationActions.NAVIGATE) {
nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy
const keyToAdd = nextState.routes[nextState.index].key; const keyToAdd = nextState.routes[nextState.index].key;
nextRouteKeyHistory = nextRouteKeyHistory.filter(k => k !== keyToAdd); // dedup nextRouteKeyHistory = nextRouteKeyHistory.filter((k) => k !== keyToAdd); // dedup
nextRouteKeyHistory.push(keyToAdd); nextRouteKeyHistory.push(keyToAdd);
} else if (action.type === NavigationActions.BACK) { } else if (action.type === NavigationActions.BACK) {
nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy
@@ -156,7 +156,7 @@ export default (routeConfigs, config = {}) => {
// Merge any params from the action into all the child routes // Merge any params from the action into all the child routes
const { params } = action; const { params } = action;
if (params) { if (params) {
state.routes = state.routes.map(route => ({ state.routes = state.routes.map((route) => ({
...route, ...route,
params: { params: {
...route.params, ...route.params,
@@ -175,14 +175,14 @@ export default (routeConfigs, config = {}) => {
) { ) {
const { params } = action; const { params } = action;
const index = state.routes.findIndex( const index = state.routes.findIndex(
route => route.routeName === action.routeName (route) => route.routeName === action.routeName
); );
if (index === -1) { if (index === -1) {
throw new Error( throw new Error(
`There is no route named '${action.routeName}' in the navigator with the key '${action.key}'.\n` + `There is no route named '${action.routeName}' in the navigator with the key '${action.key}'.\n` +
`Must be one of: ${state.routes `Must be one of: ${state.routes
.map(route => `'${route.routeName}'`) .map((route) => `'${route.routeName}'`)
.join(',')}` .join(',')}`
); );
} }
@@ -305,7 +305,7 @@ export default (routeConfigs, config = {}) => {
if (action.type === NavigationActions.SET_PARAMS) { if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key; const key = action.key;
const lastRoute = state.routes.find(route => route.key === key); const lastRoute = state.routes.find((route) => route.key === key);
if (lastRoute) { if (lastRoute) {
const params = { const params = {
...lastRoute.params, ...lastRoute.params,

View File

@@ -12,7 +12,7 @@ beforeEach(() => {
_TESTING_ONLY_normalize_keys(); _TESTING_ONLY_normalize_keys();
}); });
const performRouterTest = createTestRouter => { const performRouterTest = (createTestRouter) => {
const ListScreen = () => <div />; const ListScreen = () => <div />;
const ProfileNavigator = () => <div />; const ProfileNavigator = () => <div />;
@@ -584,7 +584,7 @@ it('Handles nested switch routers', () => {
expect(action.action.routeName).toEqual('B'); expect(action.action.routeName).toEqual('B');
}); });
const performRouteNameAsPathDisabledTest = createTestRouter => { const performRouteNameAsPathDisabledTest = (createTestRouter) => {
const BScreen = () => <div />; const BScreen = () => <div />;
const NestedNavigator = () => <div />; const NestedNavigator = () => <div />;
NestedNavigator.router = createTestRouter({ NestedNavigator.router = createTestRouter({

View File

@@ -24,7 +24,7 @@ const dummyEventSubscriber = () => ({
remove: () => {}, remove: () => {},
}); });
Object.keys(ROUTERS).forEach(routerName => { Object.keys(ROUTERS).forEach((routerName) => {
const Router = ROUTERS[routerName]; const Router = ROUTERS[routerName];
describe(`General router features - ${routerName}`, () => { describe(`General router features - ${routerName}`, () => {
@@ -441,7 +441,7 @@ it('Inner actions are only unpacked if the current tab matches', () => {
routes: [screenApreState], routes: [screenApreState],
}; };
const comparable = state => { const comparable = (state) => {
let result = {}; let result = {};
if (typeof state.routeName === 'string') { if (typeof state.routeName === 'string') {
result = { ...result, routeName: state.routeName }; result = { ...result, routeName: state.routeName };

View File

@@ -775,7 +775,7 @@ describe('TabRouter', () => {
routes: [screenApreState], routes: [screenApreState],
}; };
const comparable = state => { const comparable = (state) => {
let result = {}; let result = {};
if (typeof state.routeName === 'string') { if (typeof state.routeName === 'string') {
result = { ...result, routeName: state.routeName }; result = { ...result, routeName: state.routeName };

View File

@@ -17,7 +17,7 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
? undefined ? undefined
: router.getStateForAction({ type: NavigationActions.INIT }); : router.getStateForAction({ type: NavigationActions.INIT });
const applyAction = action => { const applyAction = (action) => {
state = router.getStateForAction(action, state); state = router.getStateForAction(action, state);
}; };
@@ -35,7 +35,7 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
...otherActionAttributes, ...otherActionAttributes,
}); });
const back = key => const back = (key) =>
applyAction({ applyAction({
type: NavigationActions.BACK, type: NavigationActions.BACK,
key, key,

View File

@@ -13,7 +13,7 @@ export default function getScreenForRouteName(routeConfigs, routeName) {
throw new Error( throw new Error(
`There is no route defined for key ${routeName}.\n` + `There is no route defined for key ${routeName}.\n` +
`Must be one of: ${Object.keys(routeConfigs) `Must be one of: ${Object.keys(routeConfigs)
.map(a => `'${a}'`) .map((a) => `'${a}'`)
.join(',')}` .join(',')}`
); );
} }

View File

@@ -36,7 +36,7 @@ export const getParamsFromPath = (inputParams, pathMatch, pathMatchKeys) => {
return params; return params;
}; };
const getRestOfPath = (pathMatch, pathMatchKeys) => { const getRestOfPath = (pathMatch, pathMatchKeys) => {
const rest = pathMatch[pathMatchKeys.findIndex(k => k.asterisk) + 1]; const rest = pathMatch[pathMatchKeys.findIndex((k) => k.asterisk) + 1];
return rest; return rest;
}; };
@@ -81,7 +81,7 @@ export const createPathParser = (
let paths = []; let paths = [];
// Build pathsByRouteNames, which includes a regex to match paths for each route. Keep in mind, the regex will pass for the route and all child routes. The code that uses pathsByRouteNames will need to also verify that the child router produces an action, in the case of isPathMatchable false (a null path). // Build pathsByRouteNames, which includes a regex to match paths for each route. Keep in mind, the regex will pass for the route and all child routes. The code that uses pathsByRouteNames will need to also verify that the child router produces an action, in the case of isPathMatchable false (a null path).
Object.keys(childRouters).forEach(routeName => { Object.keys(childRouters).forEach((routeName) => {
let pathPattern; let pathPattern;
// First check for paths on the router, then check the route config // First check for paths on the router, then check the route config
@@ -189,7 +189,7 @@ export const createPathParser = (
return null; return null;
}; };
const getPathAndParamsForRoute = route => { const getPathAndParamsForRoute = (route) => {
const { routeName, params } = route; const { routeName, params } = route;
const childRouter = childRouters[routeName]; const childRouter = childRouters[routeName];
const { toPath, exactReKeys } = pathsByRouteNames[routeName]; const { toPath, exactReKeys } = pathsByRouteNames[routeName];
@@ -197,8 +197,8 @@ export const createPathParser = (
const nonPathParams = {}; const nonPathParams = {};
if (params) { if (params) {
Object.keys(params) Object.keys(params)
.filter(paramName => !exactReKeys.find(k => k.name === paramName)) .filter((paramName) => !exactReKeys.find((k) => k.name === paramName))
.forEach(paramName => { .forEach((paramName) => {
nonPathParams[paramName] = params[paramName]; nonPathParams[paramName] = params[paramName];
}); });
} }

View File

@@ -13,7 +13,7 @@ function validateRouteConfigMap(routeConfigs) {
'Please specify at least one route when configuring a navigator.' 'Please specify at least one route when configuring a navigator.'
); );
routeNames.forEach(routeName => { routeNames.forEach((routeName) => {
const routeConfig = routeConfigs[routeName]; const routeConfig = routeConfigs[routeName];
const screenComponent = getScreenComponent(routeConfig); const screenComponent = getScreenComponent(routeConfig);

View File

@@ -7,7 +7,7 @@ const deprecatedKeys = ['tabBar'];
export default (screenOptions, route) => { export default (screenOptions, route) => {
const keys = Object.keys(screenOptions); const keys = Object.keys(screenOptions);
const deprecatedKey = keys.find(key => deprecatedKeys.includes(key)); const deprecatedKey = keys.find((key) => deprecatedKeys.includes(key));
if (typeof screenOptions.title === 'function') { if (typeof screenOptions.title === 'function') {
throw new Error( throw new Error(
@@ -54,7 +54,7 @@ export default (screenOptions, route) => {
'{', '{',
` ${deprecatedKey}: {`, ` ${deprecatedKey}: {`,
...Object.keys(screenOptions[deprecatedKey]).map( ...Object.keys(screenOptions[deprecatedKey]).map(
key => ` ${key}: ...,` (key) => ` ${key}: ...,`
), ),
' },', ' },',
'}', '}',
@@ -62,7 +62,7 @@ export default (screenOptions, route) => {
'with:', 'with:',
'{', '{',
...Object.keys(screenOptions[deprecatedKey]).map( ...Object.keys(screenOptions[deprecatedKey]).map(
key => (key) =>
` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,` ` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,`
), ),
'}', '}',

View File

@@ -5,8 +5,10 @@ function getSceneIndicesForInterpolationInputRange(props) {
const isBack = !scenes[lastSceneIndexInScenes].isActive; const isBack = !scenes[lastSceneIndexInScenes].isActive;
if (isBack) { if (isBack) {
const currentSceneIndexInScenes = scenes.findIndex(item => item === scene); const currentSceneIndexInScenes = scenes.findIndex(
const targetSceneIndexInScenes = scenes.findIndex(item => item.isActive); (item) => item === scene
);
const targetSceneIndexInScenes = scenes.findIndex((item) => item.isActive);
const targetSceneIndex = scenes[targetSceneIndexInScenes].index; const targetSceneIndex = scenes[targetSceneIndexInScenes].index;
const lastSceneIndex = scenes[lastSceneIndexInScenes].index; const lastSceneIndex = scenes[lastSceneIndexInScenes].index;

View File

@@ -9,10 +9,10 @@
* will remain to ensure logic does not differ in production. * will remain to ensure logic does not differ in production.
*/ */
let validateFormat: (format?: string) => void = function() {}; let validateFormat: (format?: string) => void = function () {};
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
validateFormat = function(format?: string) { validateFormat = function (format?: string) {
if (format === undefined) { if (format === undefined) {
throw new Error('invariant requires an error message argument'); throw new Error('invariant requires an error message argument');
} }

View File

@@ -17,7 +17,7 @@ class NavigationEvents extends React.Component {
// We register all navigation listeners on mount to ensure listener stability across re-render // We register all navigation listeners on mount to ensure listener stability across re-render
// A former implementation was replacing (removing/adding) listeners on all update (if prop provided) // A former implementation was replacing (removing/adding) listeners on all update (if prop provided)
// but there were issues (see https://github.com/react-navigation/react-navigation/issues/5058) // but there were issues (see https://github.com/react-navigation/react-navigation/issues/5058)
EventNames.forEach(eventName => { EventNames.forEach((eventName) => {
this.subscriptions[eventName] = this.props.navigation.addListener( this.subscriptions[eventName] = this.props.navigation.addListener(
eventName, eventName,
(...args) => { (...args) => {
@@ -29,12 +29,12 @@ class NavigationEvents extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
EventNames.forEach(eventName => { EventNames.forEach((eventName) => {
this.subscriptions[eventName].remove(); this.subscriptions[eventName].remove();
}); });
} }
getPropListener = eventName => this.props[EventNameToPropName[eventName]]; getPropListener = (eventName) => this.props[EventNameToPropName[eventName]];
render() { render() {
return null; return null;

View File

@@ -0,0 +1,342 @@
// @ts-check
import * as React from 'react';
/**
* @typedef {object} State
* @prop {number} index
* @prop {({ key: string } & (State | {}))[]} routes
* @prop {boolean} [isTransitioning]
*
* @typedef {object} ParentPayload
* @prop {string} type
* @prop {object} action
* @prop {State} state
* @prop {State | {key: string, routes?: undefined, index?: undefined, isTransitioning?: undefined} | undefined | null} lastState
* @prop {string} [context]
*
* @typedef {object} Payload
* @prop {string} type
* @prop {object} action
* @prop {State | {key: string}} state
* @prop {State | {key: string} | undefined | null} lastState
* @prop {string} [context]
*
* @typedef {object} Props
* @prop {object} navigation
* @prop {Function} navigation.addListener
* @prop {Function} navigation.removeListener
* @prop {() => boolean} navigation.isFocused
* @prop {() => object | undefined} navigation.dangerouslyGetParent
* @prop {State} navigation.state
* @prop {(target: string, type: string, data: any) => void} onEvent
*
* @extends {React.Component<Props>}
*/
export default class NavigationEventManager extends React.Component {
componentDidMount() {
const { navigation } = this.props;
this._actionSubscription = navigation.addListener(
'action',
this._handleAction
);
this._willFocusSubscription = navigation.addListener(
'willFocus',
this._handleWillFocus
);
this._willBlurSubscription = navigation.addListener(
'willBlur',
this._handleWillBlur
);
this._didFocusSubscription = navigation.addListener(
'didFocus',
this._handleDidFocus
);
this._didBlurSubscription = navigation.addListener(
'didBlur',
this._handleDidBlur
);
}
componentWillUnmount() {
this._actionSubscription?.remove();
this._willFocusSubscription?.remove();
this._willBlurSubscription?.remove();
this._didFocusSubscription?.remove();
this._didBlurSubscription?.remove();
}
/**
* @type {{ remove(): void } | undefined}
*/
_actionSubscription;
/**
* @type {{ remove(): void } | undefined}
*/
_willFocusSubscription;
/**
* @type {{ remove(): void } | undefined}
*/
_willBlurSubscription;
/**
* @type {{ remove(): void } | undefined}
*/
_didFocusSubscription;
/**
* @type {{ remove(): void } | undefined}
*/
_didBlurSubscription;
/**
* @type {string | undefined}
*/
_lastWillBlurKey;
/**
* @type {string | undefined}
*/
_lastWillFocusKey;
/**
* @type {string | undefined}
*/
_lastDidBlurKey;
/**
* @type {string | undefined}
*/
_lastDidFocusKey;
/**
* The 'action' event will fire when navigation state changes.
* Detect if the focused route changed here and emit appropriate events.
*
* @param {ParentPayload} payload
*/
_handleAction = ({ state, lastState, action, type, context }) => {
const { navigation, onEvent } = this.props;
// We should only emit events when the navigator is focused
// When navigator is not focused, screens inside shouldn't receive focused status either
if (!navigation.isFocused()) {
return;
}
const previous = lastState
? lastState.routes?.[lastState.index]
: undefined;
const current = state.routes[state.index];
const payload = {
context: `${current.key}:${action.type}_${context || 'Root'}`,
state: current,
lastState: previous,
action,
type,
};
if (previous?.key !== current.key) {
this._emitWillFocus(current.key, payload);
if (previous?.key) {
this._emitWillBlur(previous.key, payload);
}
}
if (
lastState?.isTransitioning !== state.isTransitioning &&
state.isTransitioning === false
) {
if (this._lastWillBlurKey) {
this._emitDidBlur(this._lastWillBlurKey, payload);
}
if (this._lastWillFocusKey) {
this._emitDidFocus(this._lastWillFocusKey, payload);
}
}
onEvent(current.key, 'action', payload);
};
/**
* @param {ParentPayload} payload
*/
_handleWillFocus = ({ lastState, action, context, type }) => {
const { navigation } = this.props;
const route = navigation.state.routes[navigation.state.index];
this._emitWillFocus(route.key, {
context: `${route.key}:${action.type}_${context || 'Root'}`,
state: route,
lastState: lastState?.routes?.find((r) => r.key === route.key),
action,
type,
});
};
/**
* @param {ParentPayload} payload
*/
_handleWillBlur = ({ lastState, action, context, type }) => {
const { navigation } = this.props;
const route = navigation.state.routes[navigation.state.index];
this._emitWillBlur(route.key, {
context: `${route.key}:${action.type}_${context || 'Root'}`,
state: route,
lastState: lastState?.routes?.find((r) => r.key === route.key),
action,
type,
});
};
/**
* @param {ParentPayload} payload
*/
_handleDidFocus = ({ lastState, action, context, type }) => {
const { navigation } = this.props;
if (this._lastWillFocusKey) {
const route = navigation.state.routes.find(
(r) => r.key === this._lastWillFocusKey
);
if (route) {
this._emitDidFocus(route.key, {
context: `${route.key}:${action.type}_${context || 'Root'}`,
state: route,
lastState: lastState?.routes?.find((r) => r.key === route.key),
action,
type,
});
}
}
};
/**
* @param {ParentPayload} payload
*/
_handleDidBlur = ({ lastState, action, context, type }) => {
const { navigation } = this.props;
if (this._lastWillBlurKey) {
const route = navigation.state.routes.find(
(r) => r.key === this._lastWillBlurKey
);
if (route) {
this._emitDidBlur(route.key, {
context: `${route.key}:${action.type}_${context || 'Root'}`,
state: route,
lastState: lastState?.routes?.find((r) => r.key === route.key),
action,
type,
});
}
}
};
/**
* @param {string} target
* @param {Payload} payload
*/
_emitWillFocus = (target, payload) => {
if (this._lastWillBlurKey === target) {
this._lastWillBlurKey = undefined;
}
if (this._lastWillFocusKey === target) {
return;
}
this._lastDidFocusKey = undefined;
this._lastWillFocusKey = target;
const { navigation, onEvent } = this.props;
onEvent(target, 'willFocus', payload);
if (
typeof navigation.state.isTransitioning !== 'boolean' ||
(navigation.state.isTransitioning !== true &&
!navigation.dangerouslyGetParent())
) {
this._emitDidFocus(target, payload);
}
};
/**
* @param {string} target
* @param {Payload} payload
*/
_emitWillBlur = (target, payload) => {
if (this._lastWillFocusKey === target) {
this._lastWillFocusKey = undefined;
}
if (this._lastWillBlurKey === target) {
return;
}
this._lastDidBlurKey = undefined;
this._lastWillBlurKey = target;
const { navigation, onEvent } = this.props;
onEvent(target, 'willBlur', payload);
if (
typeof navigation.state.isTransitioning !== 'boolean' ||
(navigation.state.isTransitioning !== true &&
!navigation.dangerouslyGetParent())
) {
this._emitDidBlur(target, payload);
}
};
/**
* @param {string} target
* @param {Payload} payload
*/
_emitDidFocus = (target, payload) => {
if (this._lastWillFocusKey !== target || this._lastDidFocusKey === target) {
return;
}
this._lastDidFocusKey = target;
const { onEvent } = this.props;
onEvent(target, 'didFocus', payload);
};
/**
* @param {string} target
* @param {Payload} payload
*/
_emitDidBlur = (target, payload) => {
if (this._lastWillBlurKey !== target || this._lastDidBlurKey === target) {
return;
}
this._lastDidBlurKey = target;
const { onEvent } = this.props;
onEvent(target, 'didBlur', payload);
};
render() {
return null;
}
}

View File

@@ -26,13 +26,15 @@ const createTestNavigationAndHelpers = () => {
listeners[eventName].push(handler); listeners[eventName].push(handler);
}, },
remove: (eventName, handler) => { remove: (eventName, handler) => {
listeners[eventName] = listeners[eventName].filter(h => h !== handler); listeners[eventName] = listeners[eventName].filter(
(h) => h !== handler
);
}, },
get: eventName => { get: (eventName) => {
return listeners[eventName]; return listeners[eventName];
}, },
call: eventName => { call: (eventName) => {
listeners[eventName].forEach(listener => listener()); listeners[eventName].forEach((listener) => listener());
}, },
}; };
})(); })();

View File

@@ -8,14 +8,15 @@ export default function withNavigation(
config = { forwardRef: true } config = { forwardRef: true }
) { ) {
class ComponentWithNavigation extends React.Component { class ComponentWithNavigation extends React.Component {
static displayName = `withNavigation(${Component.displayName || static displayName = `withNavigation(${
Component.name})`; Component.displayName || Component.name
})`;
render() { render() {
const navigationProp = this.props.navigation; const navigationProp = this.props.navigation;
return ( return (
<NavigationContext.Consumer> <NavigationContext.Consumer>
{navigationContext => { {(navigationContext) => {
const navigation = navigationProp || navigationContext; const navigation = navigationProp || navigationContext;
invariant( invariant(
!!navigation, !!navigation,

View File

@@ -4,28 +4,29 @@ import withNavigation from './withNavigation';
export default function withNavigationFocus(Component) { export default function withNavigationFocus(Component) {
class ComponentWithNavigationFocus extends React.Component { class ComponentWithNavigationFocus extends React.Component {
static displayName = `withNavigationFocus(${Component.displayName || static displayName = `withNavigationFocus(${
Component.name})`; Component.displayName || Component.name
})`;
constructor(props) { state = {
super(props); isFocused: this.props.navigation.isFocused(),
};
componentDidMount() {
const { navigation } = this.props;
this.subscriptions = [ this.subscriptions = [
props.navigation.addListener('didFocus', () => navigation.addListener('willFocus', () =>
this.setState({ isFocused: true }) this.setState({ isFocused: true })
), ),
props.navigation.addListener('willBlur', () => navigation.addListener('willBlur', () =>
this.setState({ isFocused: false }) this.setState({ isFocused: false })
), ),
]; ];
this.state = {
isFocused: props.navigation ? props.navigation.isFocused() : false,
};
} }
componentWillUnmount() { componentWillUnmount() {
this.subscriptions.forEach(sub => sub.remove()); this.subscriptions?.forEach((sub) => sub.remove());
} }
render() { render() {

View File

@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"allowJs": true
}
}

View File

@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.4.9](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.7...react-navigation-drawer@2.4.9) (2020-03-28)
**Note:** Version bump only for package react-navigation-drawer
## [2.4.8](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.7...react-navigation-drawer@2.4.8) (2020-03-27)
**Note:** Version bump only for package react-navigation-drawer
## [2.4.7](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.6...react-navigation-drawer@2.4.7) (2020-03-22)
**Note:** Version bump only for package react-navigation-drawer
## [2.4.6](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.5...react-navigation-drawer@2.4.6) (2020-03-20)
**Note:** Version bump only for package react-navigation-drawer
## [2.4.5](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.4...react-navigation-drawer@2.4.5) (2020-03-19)
### Bug Fixes
* don't use react-native-screens on web ([44c3900](https://github.com/react-navigation/drawer/commit/44c390075f7b76664e09fd9a1a7926645133ebec)), closes [#7485](https://github.com/react-navigation/drawer/issues/7485)
## [2.4.4](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.3...react-navigation-drawer@2.4.4) (2020-03-16)
**Note:** Version bump only for package react-navigation-drawer
## [2.4.3](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.2...react-navigation-drawer@2.4.3) (2020-03-16) ## [2.4.3](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.2...react-navigation-drawer@2.4.3) (2020-03-16)
**Note:** Version bump only for package react-navigation-drawer **Note:** Version bump only for package react-navigation-drawer

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-drawer", "name": "react-navigation-drawer",
"version": "2.4.3", "version": "2.4.9",
"description": "Drawer navigator component for React Navigation", "description": "Drawer navigator component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -49,7 +49,7 @@
"react-native-reanimated": "^1.2.0", "react-native-reanimated": "^1.2.0",
"react-native-screens": "^2.3.0", "react-native-screens": "^2.3.0",
"react-native-testing-library": "^1.12.0", "react-native-testing-library": "^1.12.0",
"react-navigation": "^4.3.0", "react-navigation": "^4.3.5",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -1,5 +1,3 @@
/* @flow */
import * as React from 'react'; import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native'; import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens'; import { Screen, screensEnabled } from 'react-native-screens';
@@ -10,21 +8,29 @@ type Props = {
style?: any; style?: any;
}; };
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
if (screensEnabled?.()) { // react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore // @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />; return <Screen active={isVisible ? 1 : 0} {...rest} />;
} }
const { isVisible, children, style, ...rest } = this.props; const { isVisible, children, style, ...rest } = this.props;
return ( return (
<View <View
style={[styles.container, style]} style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
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,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.8](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.6...react-navigation-material-bottom-tabs@2.2.8) (2020-03-28)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.2.7](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.6...react-navigation-material-bottom-tabs@2.2.7) (2020-03-27)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.2.6](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.5...react-navigation-material-bottom-tabs@2.2.6) (2020-03-22)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.2.5](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.4...react-navigation-material-bottom-tabs@2.2.5) (2020-03-20)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.2.4](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.3...react-navigation-material-bottom-tabs@2.2.4) (2020-03-16)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.2.3](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.2...react-navigation-material-bottom-tabs@2.2.3) (2020-03-16) ## [2.2.3](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.2...react-navigation-material-bottom-tabs@2.2.3) (2020-03-16)
**Note:** Version bump only for package react-navigation-material-bottom-tabs **Note:** Version bump only for package react-navigation-material-bottom-tabs

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-material-bottom-tabs", "name": "react-navigation-material-bottom-tabs",
"version": "2.2.3", "version": "2.2.8",
"description": "Material Bottom Tab Navigation component for React Navigation", "description": "Material Bottom Tab Navigation component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -48,7 +48,7 @@
"react-native-gesture-handler": "^1.6.0", "react-native-gesture-handler": "^1.6.0",
"react-native-paper": "^3.1.1", "react-native-paper": "^3.1.1",
"react-native-reanimated": "^1.2.0", "react-native-reanimated": "^1.2.0",
"react-navigation": "^4.3.0", "react-navigation": "^4.3.5",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -3,6 +3,58 @@
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.
## [3.7.9](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.7...@react-navigation/native@3.7.9) (2020-03-28)
### Bug Fixes
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-native/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-native/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-native/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-native/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-native/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-native/issues/7749)
## [3.7.8](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.7...@react-navigation/native@3.7.8) (2020-03-27)
### Bug Fixes
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-native/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-native/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-native/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-native/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-native/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-native/issues/7749)
## [3.7.7](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.6...@react-navigation/native@3.7.7) (2020-03-22)
**Note:** Version bump only for package @react-navigation/native
## [3.7.6](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.5...@react-navigation/native@3.7.6) (2020-03-20)
### Bug Fixes
* don't mix import and module.exports. closes [#7660](https://github.com/react-navigation/react-navigation-native/issues/7660) ([80c8c9d](https://github.com/react-navigation/react-navigation-native/commit/80c8c9d1dead57eab3b977a1eebf1e9f5f35cd1a))
## [3.7.5](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.4...@react-navigation/native@3.7.5) (2020-03-16)
### Bug Fixes
* downgrade react-native-safe-area-view ([cfc1bac](https://github.com/react-navigation/react-navigation-native/commit/cfc1bac4e153db4a4ba3f2a9033f77b53367fcbc)), closes [#7813](https://github.com/react-navigation/react-navigation-native/issues/7813)
## [3.7.4](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.3...@react-navigation/native@3.7.4) (2020-03-16) ## [3.7.4](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.3...@react-navigation/native@3.7.4) (2020-03-16)
**Note:** Version bump only for package @react-navigation/native **Note:** Version bump only for package @react-navigation/native

View File

@@ -1,6 +1,6 @@
{ {
"name": "@react-navigation/native", "name": "@react-navigation/native",
"version": "3.7.4", "version": "3.7.9",
"description": "React Native support for React Navigation", "description": "React Native support for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -15,7 +15,8 @@
}, },
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
"access": "public" "access": "public",
"tag": "4.x"
}, },
"keywords": [ "keywords": [
"react-native", "react-native",
@@ -35,11 +36,11 @@
"homepage": "https://github.com/react-navigation/react-navigation-native#readme", "homepage": "https://github.com/react-navigation/react-navigation-native#readme",
"dependencies": { "dependencies": {
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"react-native-safe-area-view": "^1.0.0" "react-native-safe-area-view": "^0.14.8"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.10.0",
"@react-navigation/core": "^3.7.0", "@react-navigation/core": "^3.7.3",
"@types/react-test-renderer": "^16.9.2", "@types/react-test-renderer": "^16.9.2",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",

View File

@@ -52,7 +52,7 @@ const WrappedFlatList = React.forwardRef((props, ref) => (
<FlatList <FlatList
ref={ref} ref={ref}
{...props} {...props}
renderScrollComponent={props => ( renderScrollComponent={(props) => (
<WrappedScrollView {...propsMaybeWithRefreshControl(props)} /> <WrappedScrollView {...propsMaybeWithRefreshControl(props)} />
)} )}
/> />
@@ -62,15 +62,14 @@ const WrappedSectionList = React.forwardRef((props, ref) => (
<SectionList <SectionList
ref={ref} ref={ref}
{...props} {...props}
renderScrollComponent={props => ( renderScrollComponent={(props) => (
<WrappedScrollView {...propsMaybeWithRefreshControl(props)} /> <WrappedScrollView {...propsMaybeWithRefreshControl(props)} />
)} )}
/> />
)); ));
// eslint-disable-next-line import/no-commonjs export {
module.exports = { WrappedScrollView as ScrollView,
ScrollView: WrappedScrollView, WrappedFlatList as FlatList,
FlatList: WrappedFlatList, WrappedSectionList as SectionList,
SectionList: WrappedSectionList,
}; };

View File

@@ -274,7 +274,7 @@ describe('NavigationContainer', () => {
}); });
// https://github.com/facebook/jest/issues/2157#issuecomment-279171856 // https://github.com/facebook/jest/issues/2157#issuecomment-279171856
const flushPromises = () => new Promise(resolve => setImmediate(resolve)); const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
describe('state persistence', () => { describe('state persistence', () => {
async function createPersistenceEnabledContainer( async function createPersistenceEnabledContainer(

View File

@@ -266,7 +266,7 @@ export default function createNavigationContainer(Component) {
} }
const dispatchActions = () => const dispatchActions = () =>
this._actionEventSubscribers.forEach(subscriber => this._actionEventSubscribers.forEach((subscriber) =>
subscriber({ subscriber({
type: 'action', type: 'action',
action, action,
@@ -316,7 +316,7 @@ export default function createNavigationContainer(Component) {
} }
} }
_persistNavigationState = async nav => { _persistNavigationState = async (nav) => {
const { persistNavigationState } = this.props; const { persistNavigationState } = this.props;
if (persistNavigationState) { if (persistNavigationState) {
try { try {
@@ -341,7 +341,7 @@ export default function createNavigationContainer(Component) {
// Per-tick temporary storage for state.nav // Per-tick temporary storage for state.nav
dispatch = action => { dispatch = (action) => {
if (this.props.navigation) { if (this.props.navigation) {
return this.props.navigation.dispatch(action); return this.props.navigation.dispatch(action);
} }
@@ -357,7 +357,7 @@ export default function createNavigationContainer(Component) {
const navState = reducedState === null ? lastNavState : reducedState; const navState = reducedState === null ? lastNavState : reducedState;
const dispatchActionEvents = () => { const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber => this._actionEventSubscribers.forEach((subscriber) =>
subscriber({ subscriber({
type: 'action', type: 'action',
action, action,

View File

@@ -4,9 +4,10 @@ import { withNavigation } from '@react-navigation/core';
export default function createNavigationAwareScrollable(Component) { export default function createNavigationAwareScrollable(Component) {
const ComponentWithNavigationScrolling = withNavigation( const ComponentWithNavigationScrolling = withNavigation(
class extends React.PureComponent<any> { class extends React.PureComponent {
static displayName = `withNavigationScrolling(${Component.displayName || static displayName = `withNavigationScrolling(${
Component.name})`; Component.displayName || Component.name
})`;
componentDidMount() { componentDidMount() {
this._subscription = this.props.navigation.addListener( this._subscription = this.props.navigation.addListener(
@@ -49,7 +50,7 @@ export default function createNavigationAwareScrollable(Component) {
render() { render() {
return ( return (
<Component <Component
ref={view => { ref={(view) => {
this._scrollRef = view; this._scrollRef = view;
}} }}
{...this.props} {...this.props}
@@ -59,11 +60,12 @@ export default function createNavigationAwareScrollable(Component) {
} }
); );
class NavigationAwareScrollable extends React.PureComponent<any> { class NavigationAwareScrollable extends React.PureComponent {
static displayName = `NavigationAwareScrollable(${Component.displayName || static displayName = `NavigationAwareScrollable(${
Component.name})`; Component.displayName || Component.name
})`;
_captureRef = view => { _captureRef = (view) => {
this._innerRef = view; this._innerRef = view;
this.props.onRef && this.props.onRef(view); this.props.onRef && this.props.onRef(view);
}; };

View File

@@ -1,4 +1,4 @@
export default function() { export default function () {
throw new Error( throw new Error(
"This version of '@react-navigation/native' is not compatible with React Navigation 5. Make sure you install 5.x version of '@react-navigation/native'." "This version of '@react-navigation/native' is not compatible with React Navigation 5. Make sure you install 5.x version of '@react-navigation/native'."
); );

View File

@@ -9,17 +9,17 @@
* will remain to ensure logic does not differ in production. * will remain to ensure logic does not differ in production.
*/ */
var validateFormat = function() {}; var validateFormat = function () {};
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
validateFormat = function(format) { validateFormat = function (format) {
if (format === undefined) { if (format === undefined) {
throw new Error('invariant requires an error message argument'); throw new Error('invariant requires an error message argument');
} }
}; };
} }
function invariant(condition, format, a, b, c, d, e, f) { export default function invariant(condition, format, a, b, c, d, e, f) {
validateFormat(format); validateFormat(format);
if (!condition) { if (!condition) {
@@ -32,7 +32,7 @@ function invariant(condition, format, a, b, c, d, e, f) {
var args = [a, b, c, d, e, f]; var args = [a, b, c, d, e, f];
var argIndex = 0; var argIndex = 0;
error = new Error( error = new Error(
format.replace(/%s/g, function() { format.replace(/%s/g, function () {
return args[argIndex++]; return args[argIndex++];
}) })
); );
@@ -43,6 +43,3 @@ function invariant(condition, format, a, b, c, d, e, f) {
throw error; throw error;
} }
} }
// eslint-disable-next-line import/no-commonjs
module.exports = invariant;

View File

@@ -4,7 +4,7 @@ import hoistNonReactStatic from 'hoist-non-react-statics';
export const isOrientationLandscape = ({ width, height }) => width > height; export const isOrientationLandscape = ({ width, height }) => width > height;
export default function(WrappedComponent) { export default function (WrappedComponent) {
class withOrientation extends React.Component { class withOrientation extends React.Component {
constructor() { constructor() {
super(); super();

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.3.5](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.3...react-navigation@4.3.5) (2020-03-28)
**Note:** Version bump only for package react-navigation
## [4.3.4](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.3...react-navigation@4.3.4) (2020-03-27)
**Note:** Version bump only for package react-navigation
## [4.3.3](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.2...react-navigation@4.3.3) (2020-03-22)
**Note:** Version bump only for package react-navigation
## [4.3.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.1...react-navigation@4.3.2) (2020-03-20)
**Note:** Version bump only for package react-navigation
## [4.3.1](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.0...react-navigation@4.3.1) (2020-03-16)
**Note:** Version bump only for package react-navigation
# [4.3.0](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.2.2...react-navigation@4.3.0) (2020-03-16) # [4.3.0](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.2.2...react-navigation@4.3.0) (2020-03-16)

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation", "name": "react-navigation",
"version": "4.3.0", "version": "4.3.5",
"description": "Routing and navigation for your React Native apps", "description": "Routing and navigation for your React Native apps",
"main": "src/index.js", "main": "src/index.js",
"types": "typescript/react-navigation.d.ts", "types": "typescript/react-navigation.d.ts",
@@ -24,8 +24,8 @@
"react-native": "*" "react-native": "*"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^3.7.0", "@react-navigation/core": "^3.7.3",
"@react-navigation/native": "^3.7.4" "@react-navigation/native": "^3.7.9"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.9.23", "@types/react": "^16.9.23",

View File

@@ -3,6 +3,85 @@
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.
## [2.3.9](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.7...react-navigation-stack@2.3.9) (2020-03-28)
### Bug Fixes
* emit didFocus and didBlur events based on parent's transition ([14a6538](https://github.com/react-navigation/react-navigation-stack/commit/14a6538cc8e12c50d5d10722d75c9395a0a281ec))
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-stack/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-stack/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-stack/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-stack/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-stack/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-stack/issues/7749)
## [2.3.8](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.7...react-navigation-stack@2.3.8) (2020-03-27)
### Bug Fixes
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-stack/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-stack/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-stack/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-stack/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-stack/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-stack/issues/7749)
## [2.3.7](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.6...react-navigation-stack@2.3.7) (2020-03-27)
**Note:** Version bump only for package react-navigation-stack
## [2.3.6](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.5...react-navigation-stack@2.3.6) (2020-03-23)
**Note:** Version bump only for package react-navigation-stack
## [2.3.5](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.4...react-navigation-stack@2.3.5) (2020-03-22)
**Note:** Version bump only for package react-navigation-stack
## [2.3.4](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.3...react-navigation-stack@2.3.4) (2020-03-20)
**Note:** Version bump only for package react-navigation-stack
## [2.3.3](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.2...react-navigation-stack@2.3.3) (2020-03-20)
**Note:** Version bump only for package react-navigation-stack
## [2.3.2](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.1...react-navigation-stack@2.3.2) (2020-03-19)
**Note:** Version bump only for package react-navigation-stack
## [2.3.1](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.3.0...react-navigation-stack@2.3.1) (2020-03-16)
**Note:** Version bump only for package react-navigation-stack
# [2.3.0](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.2.3...react-navigation-stack@2.3.0) (2020-03-16) # [2.3.0](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.2.3...react-navigation-stack@2.3.0) (2020-03-16)

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-stack", "name": "react-navigation-stack",
"version": "2.3.0", "version": "2.3.9",
"description": "Stack navigator component for React Navigation", "description": "Stack navigator component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -45,7 +45,7 @@
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.10.0",
"@react-native-community/masked-view": "^0.1.7", "@react-native-community/masked-view": "^0.1.7",
"@react-navigation/stack": "^5.2.0", "@react-navigation/stack": "^5.2.8",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/react": "^16.9.23", "@types/react": "^16.9.23",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.61.22",
@@ -56,7 +56,7 @@
"react-native-gesture-handler": "^1.6.0", "react-native-gesture-handler": "^1.6.0",
"react-native-safe-area-context": "^0.7.3", "react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0", "react-native-screens": "^2.3.0",
"react-navigation": "^4.3.0", "react-navigation": "^4.3.5",
"react-test-renderer": "~16.9.0", "react-test-renderer": "~16.9.0",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },

View File

@@ -1,6 +1,6 @@
diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx
--- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/index.tsx 2020-03-16 14:41:12.000000000 +0100 +++ src/vendor/index.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -3,11 +3,6 @@ @@ -3,11 +3,6 @@
import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs'; import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs';
import * as TransitionPresets from './TransitionConfigs/TransitionPresets'; import * as TransitionPresets from './TransitionConfigs/TransitionPresets';
@@ -28,7 +28,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/i
StackHeaderTitleProps, StackHeaderTitleProps,
StackCardInterpolatedStyle, StackCardInterpolatedStyle,
diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx
--- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100 +++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100
@@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
-import * as React from 'react'; -import * as React from 'react';
@@ -73,7 +73,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
- React.useEffect( - React.useEffect(
- () => - () =>
- navigation.addListener && - navigation.addListener &&
- navigation.addListener('tabPress', e => { - navigation.addListener('tabPress', (e) => {
- const isFocused = navigation.isFocused(); - const isFocused = navigation.isFocused();
- -
- // Run the operation in the next frame so we're sure all listeners have been run - // Run the operation in the next frame so we're sure all listeners have been run
@@ -113,8 +113,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
- typeof StackNavigator - typeof StackNavigator
->(StackNavigator); ->(StackNavigator);
diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx
--- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/types.tsx 2020-03-16 14:47:01.000000000 +0100 +++ src/vendor/types.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -8,14 +8,28 @@ @@ -8,14 +8,28 @@
} from 'react-native'; } from 'react-native';
import { EdgeInsets } from 'react-native-safe-area-context'; import { EdgeInsets } from 'react-native-safe-area-context';
@@ -240,8 +240,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
export type StackNavigationConfig = { export type StackNavigationConfig = {
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/Header.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Header/Header.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -1,12 +1,14 @@ @@ -1,12 +1,14 @@
import * as React from 'react'; import * as React from 'react';
-import { StackActions } from '@react-navigation/native'; -import { StackActions } from '@react-navigation/native';
@@ -321,8 +321,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
+ +
+export default Header; +export default Header;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/HeaderBackButton.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Header/HeaderBackButton.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -8,9 +8,9 @@ @@ -8,9 +8,9 @@
StyleSheet, StyleSheet,
LayoutChangeEvent, LayoutChangeEvent,
@@ -335,8 +335,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBac
type Props = StackHeaderLeftButtonProps; type Props = StackHeaderLeftButtonProps;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/HeaderBackground.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Header/HeaderBackground.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Animated, StyleSheet, Platform, ViewProps } from 'react-native'; import { Animated, StyleSheet, Platform, ViewProps } from 'react-native';
@@ -346,8 +346,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBac
type Props = ViewProps & { type Props = ViewProps & {
children?: React.ReactNode; children?: React.ReactNode;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/HeaderContainer.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Header/HeaderContainer.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -1,11 +1,6 @@ @@ -1,11 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
@@ -399,8 +399,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
); );
})} })}
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/HeaderSegment.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Header/HeaderSegment.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
@@ -420,8 +420,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSeg
}; };
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Header/HeaderTitle.tsx 2020-03-16 14:43:23.000000000 +0100 +++ src/vendor/views/Header/HeaderTitle.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Animated, StyleSheet, Platform } from 'react-native'; import { Animated, StyleSheet, Platform } from 'react-native';
@@ -431,8 +431,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTit
type Props = React.ComponentProps<typeof Animated.Text> & { type Props = React.ComponentProps<typeof Animated.Text> & {
tintColor?: string; tintColor?: string;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Stack/Card.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Stack/Card.tsx 2020-03-23 11:43:59.000000000 +0100
@@ -138,7 +138,7 @@ @@ -138,7 +138,7 @@
private interactionHandle: number | undefined; private interactionHandle: number | undefined;
@@ -443,8 +443,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx s
private animate = ({ private animate = ({
closing, closing,
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Stack/CardContainer.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Stack/CardContainer.tsx 2020-03-27 14:41:20.000000000 +0100
@@ -1,10 +1,16 @@ @@ -1,10 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
@@ -464,33 +464,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContai
type Props = TransitionPreset & { type Props = TransitionPreset & {
index: number; index: number;
@@ -36,6 +42,7 @@ @@ -183,7 +189,7 @@
closing: boolean
) => void;
onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void;
+ onTransitionComplete: (props: { route: Route<string> }) => void;
onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void;
onPageChangeCancel?: () => void;
@@ -83,6 +90,7 @@
layout,
onCloseRoute,
onOpenRoute,
+ onTransitionComplete,
onPageChangeCancel,
onPageChangeConfirm,
onPageChangeStart,
@@ -152,6 +160,9 @@
};
}, [pointerEvents, scene.progress.next]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ React.useEffect(() => onTransitionComplete({ route: scene.route }), []);
+
return (
<Card
index={index}
@@ -183,7 +194,7 @@
? { marginTop: headerHeight } ? { marginTop: headerHeight }
: null : null
} }
@@ -500,8 +474,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContai
> >
<View style={styles.container}> <View style={styles.container}>
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Stack/CardStack.tsx 2020-03-16 14:41:16.000000000 +0100 +++ src/vendor/views/Stack/CardStack.tsx 2020-03-27 14:41:20.000000000 +0100
@@ -9,9 +9,8 @@ @@ -9,9 +9,8 @@
ViewProps, ViewProps,
} from 'react-native'; } from 'react-native';
@@ -521,33 +495,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.
Layout, Layout,
StackHeaderMode, StackHeaderMode,
StackCardMode, StackCardMode,
@@ -54,6 +54,7 @@
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
renderScene: (props: { route: Route<string> }) => React.ReactNode;
headerMode: StackHeaderMode;
+ onTransitionComplete: (props: { route: Route<string> }) => void;
onTransitionStart: (
props: { route: Route<string> },
closing: boolean
@@ -371,6 +372,7 @@
renderHeader,
renderScene,
headerMode,
+ onTransitionComplete,
onTransitionStart,
onTransitionEnd,
onPageChangeStart,
@@ -548,6 +550,7 @@
renderScene={renderScene}
onOpenRoute={onOpenRoute}
onCloseRoute={onCloseRoute}
+ onTransitionComplete={onTransitionComplete}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-03-16 14:39:49.000000000 +0100 --- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-03-23 11:43:17.000000000 +0100
+++ src/vendor/views/Stack/StackView.tsx 2020-03-16 15:41:41.000000000 +0100 +++ src/vendor/views/Stack/StackView.tsx 2020-03-27 14:41:20.000000000 +0100
@@ -4,9 +4,9 @@ @@ -4,9 +4,9 @@
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { import {
@@ -613,10 +563,10 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
+ +
private handleOpenRoute = ({ route }: { route: Route<string> }) => { private handleOpenRoute = ({ route }: { route: Route<string> }) => {
+ this.handleTransitionComplete({ route }); + this.handleTransitionComplete({ route });
this.setState(state => ({ this.setState((state) => ({
routes: state.replacingRouteKeys.length routes: state.replacingRouteKeys.length
? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key)) ? state.routes.filter((r) => !state.replacingRouteKeys.includes(r.key))
@@ -315,12 +340,17 @@ @@ -319,12 +344,17 @@
// If a route exists in state, trigger a pop // If a route exists in state, trigger a pop
// This will happen in when the route was closed from the card component // This will happen in when the route was closed from the card component
// e.g. When the close animation triggered from a gesture ends // e.g. When the close animation triggered from a gesture ends
@@ -630,16 +580,16 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
} else { } else {
+ // While closing route we need to point to the previous one assuming that + // While closing route we need to point to the previous one assuming that
+ // this previous one in routes array + // this previous one in routes array
+ const index = this.state.routes.findIndex(r => r.key === route.key); + const index = this.state.routes.findIndex((r) => r.key === route.key);
+ +
+ this.handleTransitionComplete({ + this.handleTransitionComplete({
+ route: this.state.routes[Math.max(index - 1, 0)], + route: this.state.routes[Math.max(index - 1, 0)],
+ }); + });
+ +
// We need to clean up any state tracking the route and pop it immediately // We need to clean up any state tracking the route and pop it immediately
this.setState(state => ({ this.setState((state) => ({
routes: state.routes.filter(r => r.key !== route.key), routes: state.routes.filter((r) => r.key !== route.key),
@@ -337,22 +367,24 @@ @@ -341,22 +371,24 @@
private handleTransitionStart = ( private handleTransitionStart = (
{ route }: { route: Route<string> }, { route }: { route: Route<string> },
closing: boolean closing: boolean
@@ -676,11 +626,3 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
render() { render() {
const { const {
@@ -391,6 +423,7 @@
closingRouteKeys={closingRouteKeys}
onOpenRoute={this.handleOpenRoute}
onCloseRoute={this.handleCloseRoute}
+ onTransitionComplete={this.handleTransitionComplete}
onTransitionStart={this.handleTransitionStart}
onTransitionEnd={this.handleTransitionEnd}
renderHeader={this.renderHeader}

View File

@@ -30,7 +30,7 @@ function createStackNavigator(
return createNavigator( return createNavigator(
// TODO: don't have time to fix it right now // TODO: don't have time to fix it right now
// @ts-ignore // @ts-ignore
navigatorProps => <StackView {...navigatorProps} />, (navigatorProps) => <StackView {...navigatorProps} />,
router, router,
stackConfig stackConfig
); );

View File

@@ -58,7 +58,7 @@ export default function validateDeprecatedConfig(
) { ) {
let result = options; let result = options;
Object.keys(validations).forEach(name => { Object.keys(validations).forEach((name) => {
if (name in config) { if (name in config) {
const { compat, message } = validations[name]; const { compat, message } = validations[name];

View File

@@ -12,10 +12,10 @@ const shownWarnings: string[] = [];
const validations: Validation[] = [ const validations: Validation[] = [
{ {
check: o => typeof o.headerForceInset === 'object', check: (o) => typeof o.headerForceInset === 'object',
deprecated: 'headerForceInset', deprecated: 'headerForceInset',
updated: 'safeAreaInsets', updated: 'safeAreaInsets',
compat: o => { compat: (o) => {
const { headerForceInset, ...rest } = o; const { headerForceInset, ...rest } = o;
let safeAreaInsets: Partial<EdgeInsets> | undefined = { let safeAreaInsets: Partial<EdgeInsets> | undefined = {
@@ -55,20 +55,20 @@ const validations: Validation[] = [
}, },
}, },
{ {
check: o => o.gesturesEnabled !== undefined, check: (o) => o.gesturesEnabled !== undefined,
deprecated: 'gesturesEnabled', deprecated: 'gesturesEnabled',
updated: 'gestureEnabled', updated: 'gestureEnabled',
compat: o => { compat: (o) => {
const { gesturesEnabled, ...rest } = o; const { gesturesEnabled, ...rest } = o;
return { ...rest, gestureEnabled: gesturesEnabled }; return { ...rest, gestureEnabled: gesturesEnabled };
}, },
}, },
{ {
check: o => o.header === null, check: (o) => o.header === null,
deprecated: 'header: null', deprecated: 'header: null',
updated: 'headerShown: false', updated: 'headerShown: false',
compat: o => { compat: (o) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { header, ...rest } = o; const { header, ...rest } = o;
@@ -76,26 +76,26 @@ const validations: Validation[] = [
}, },
}, },
{ {
check: o => o.header != null && typeof o.header !== 'function', check: (o) => o.header != null && typeof o.header !== 'function',
deprecated: 'header: <SomeElement />', deprecated: 'header: <SomeElement />',
updated: 'header: () => <SomeElement />', updated: 'header: () => <SomeElement />',
compat: o => ({ ...o, header: () => o.header }), compat: (o) => ({ ...o, header: () => o.header }),
}, },
{ {
check: o => check: (o) =>
o.headerTitle !== undefined && o.headerTitle !== undefined &&
typeof o.headerTitle !== 'string' && typeof o.headerTitle !== 'string' &&
typeof o.headerTitle !== 'function', typeof o.headerTitle !== 'function',
deprecated: 'headerTitle: <SomeElement />', deprecated: 'headerTitle: <SomeElement />',
updated: 'headerTitle: () => <SomeElement />', updated: 'headerTitle: () => <SomeElement />',
compat: o => ({ ...o, headerTitle: () => o.headerTitle }), compat: (o) => ({ ...o, headerTitle: () => o.headerTitle }),
}, },
...['headerLeft', 'headerRight', 'headerBackground', 'headerBackImage'].map( ...['headerLeft', 'headerRight', 'headerBackground', 'headerBackImage'].map(
(p): Validation => ({ (p): Validation => ({
check: (o: any) => o[p] !== undefined && typeof o[p] !== 'function', check: (o: any) => o[p] !== undefined && typeof o[p] !== 'function',
deprecated: `${p}: <SomeElement />`, deprecated: `${p}: <SomeElement />`,
updated: `${p}: () => <SomeElement />`, updated: `${p}: () => <SomeElement />`,
compat: o => ({ ...o, [p]: () => o[p] }), compat: (o) => ({ ...o, [p]: () => o[p] }),
}) })
), ),
]; ];
@@ -108,7 +108,7 @@ export default function validateDeprecatedOptions(
const warnings: Validation[] = []; const warnings: Validation[] = [];
// Validate options to show warnings for deprecations // Validate options to show warnings for deprecations
validations.forEach(v => { validations.forEach((v) => {
if (v.check(options)) { if (v.check(options)) {
result = v.compat(result); result = v.compat(result);
@@ -127,7 +127,7 @@ export default function validateDeprecatedOptions(
console.warn( console.warn(
`Deprecation in 'navigationOptions':\n${warnings `Deprecation in 'navigationOptions':\n${warnings
.map( .map(
v => (v) =>
`- '${v.deprecated}' will be removed in a future version. Use '${v.updated}' instead` `- '${v.deprecated}' will be removed in a future version. Use '${v.updated}' instead`
) )
.join('\n')}` .join('\n')}`

View File

@@ -4,7 +4,7 @@ export default function debounce<T extends (...args: any[]) => void>(
): T { ): T {
let timeout: NodeJS.Timeout | number | undefined; let timeout: NodeJS.Timeout | number | undefined;
return function(this: any, ...args) { return function (this: any, ...args) {
if (!timeout) { if (!timeout) {
// eslint-disable-next-line babel/no-invalid-this // eslint-disable-next-line babel/no-invalid-this
func.apply(this, args); func.apply(this, args);

View File

@@ -118,7 +118,7 @@ export default function HeaderContainer({
<View <View
onLayout={ onLayout={
onContentHeightChange onContentHeightChange
? e => ? (e) =>
onContentHeightChange({ onContentHeightChange({
route: scene.route, route: scene.route,
height: e.nativeEvent.layout.height, height: e.nativeEvent.layout.height,

View File

@@ -38,7 +38,7 @@ type State = {
}; };
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => { const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
Object.keys(styles).forEach(styleProp => { Object.keys(styles).forEach((styleProp) => {
const value = styles[styleProp]; const value = styles[styleProp];
if (styleProp === 'position' && value === 'absolute') { if (styleProp === 'position' && value === 'absolute') {

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { TextInput, Keyboard } from 'react-native'; import { TextInput } from 'react-native';
type Props = { type Props = {
enabled: boolean; enabled: boolean;
@@ -54,7 +54,11 @@ export default class KeyboardManager extends React.Component<Props> {
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
Keyboard.dismiss(); const input = this.previouslyFocusedTextInput;
if (input) {
TextInput.State.blurTextInput(input);
}
// Cleanup the ID on successful page change // Cleanup the ID on successful page change
this.previouslyFocusedTextInput = null; this.previouslyFocusedTextInput = null;

View File

@@ -30,7 +30,7 @@ type Props = {
export default function SafeAreaProviderCompat({ children }: Props) { export default function SafeAreaProviderCompat({ children }: Props) {
return ( return (
<SafeAreaConsumer> <SafeAreaConsumer>
{insets => { {(insets) => {
if (insets) { if (insets) {
// If we already have insets, don't wrap the stack in another safe area provider // If we already have insets, don't wrap the stack in another safe area provider
// This avoids an issue with updates at the cost of potentially incorrect values // This avoids an issue with updates at the cost of potentially incorrect values

View File

@@ -272,7 +272,9 @@ export default class Card extends React.Component<Props> {
} }
const closing = const closing =
translation + velocity * gestureVelocityImpact > distance / 2 (translation + velocity * gestureVelocityImpact) *
getInvertedMultiplier(gestureDirection) >
distance / 2
? velocity !== 0 || translation !== 0 ? velocity !== 0 || translation !== 0
: false; : false;
@@ -478,7 +480,7 @@ export default class Card extends React.Component<Props> {
<CardAnimationContext.Provider value={animationContext}> <CardAnimationContext.Provider value={animationContext}>
<View pointerEvents="box-none" {...rest}> <View pointerEvents="box-none" {...rest}>
{overlayEnabled ? ( {overlayEnabled ? (
<View style={StyleSheet.absoluteFill}> <View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
{overlay({ style: overlayStyle })} {overlay({ style: overlayStyle })}
</View> </View>
) : null} ) : null}

View File

@@ -42,7 +42,6 @@ type Props = TransitionPreset & {
closing: boolean closing: boolean
) => void; ) => void;
onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void; onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void;
onTransitionComplete: (props: { route: Route<string> }) => void;
onPageChangeStart?: () => void; onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void; onPageChangeConfirm?: () => void;
onPageChangeCancel?: () => void; onPageChangeCancel?: () => void;
@@ -90,7 +89,6 @@ function CardContainer({
layout, layout,
onCloseRoute, onCloseRoute,
onOpenRoute, onOpenRoute,
onTransitionComplete,
onPageChangeCancel, onPageChangeCancel,
onPageChangeConfirm, onPageChangeConfirm,
onPageChangeStart, onPageChangeStart,
@@ -160,9 +158,6 @@ function CardContainer({
}; };
}, [pointerEvents, scene.progress.next]); }, [pointerEvents, scene.progress.next]);
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => onTransitionComplete({ route: scene.route }), []);
return ( return (
<Card <Card
index={index} index={index}

View File

@@ -54,7 +54,6 @@ type Props = {
renderHeader: (props: HeaderContainerProps) => React.ReactNode; renderHeader: (props: HeaderContainerProps) => React.ReactNode;
renderScene: (props: { route: Route<string> }) => React.ReactNode; renderScene: (props: { route: Route<string> }) => React.ReactNode;
headerMode: StackHeaderMode; headerMode: StackHeaderMode;
onTransitionComplete: (props: { route: Route<string> }) => void;
onTransitionStart: ( onTransitionStart: (
props: { route: Route<string> }, props: { route: Route<string> },
closing: boolean closing: boolean
@@ -76,9 +75,6 @@ type State = {
const EPSILON = 0.01; const EPSILON = 0.01;
const dimensions = Dimensions.get('window');
const layout = { width: dimensions.width, height: dimensions.height };
const MaybeScreenContainer = ({ const MaybeScreenContainer = ({
enabled, enabled,
...rest ...rest
@@ -161,7 +157,16 @@ const getProgressFromGesture = (
layout: Layout, layout: Layout,
descriptor?: StackDescriptor descriptor?: StackDescriptor
) => { ) => {
const distance = getDistanceFromOptions(mode, layout, descriptor); const distance = getDistanceFromOptions(
mode,
{
// Make sure that we have a non-zero distance, otherwise there will be incorrect progress
// This causes blank screen on web if it was previously inside container with display: none
width: Math.max(1, layout.width),
height: Math.max(1, layout.height),
},
descriptor
);
if (distance > 0) { if (distance > 0) {
return gesture.interpolate({ return gesture.interpolate({
@@ -291,19 +296,25 @@ export default class CardStack extends React.Component<Props, State> {
}; };
} }
state: State = { constructor(props: Props) {
routes: [], super(props);
scenes: [],
gestures: {}, const { height = 0, width = 0 } = Dimensions.get('window');
layout,
descriptors: this.props.descriptors, this.state = {
// Used when card's header is null and mode is float to make transition routes: [],
// between screens with headers and those without headers smooth. scenes: [],
// This is not a great heuristic here. We don't know synchronously gestures: {},
// on mount what the header height is so we have just used the most layout: { height, width },
// common cases here. descriptors: this.props.descriptors,
headerHeights: {}, // Used when card's header is null and mode is float to make transition
}; // between screens with headers and those without headers smooth.
// This is not a great heuristic here. We don't know synchronously
// on mount what the header height is so we have just used the most
// common cases here.
headerHeights: {},
};
}
private handleLayout = (e: LayoutChangeEvent) => { private handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout; const { height, width } = e.nativeEvent.layout;
@@ -372,7 +383,6 @@ export default class CardStack extends React.Component<Props, State> {
renderHeader, renderHeader,
renderScene, renderScene,
headerMode, headerMode,
onTransitionComplete,
onTransitionStart, onTransitionStart,
onTransitionEnd, onTransitionEnd,
onPageChangeStart, onPageChangeStart,
@@ -403,9 +413,9 @@ export default class CardStack extends React.Component<Props, State> {
left = insets.left, left = insets.left,
} = focusedOptions.safeAreaInsets || {}; } = focusedOptions.safeAreaInsets || {};
// Screens is buggy on iOS, so we don't enable it there // Screens is buggy on iOS and web, so we only enable it on Android
// For modals, usually we want the screen underneath to be visible, so also disable it there // For modals, usually we want the screen underneath to be visible, so also disable it there
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal'; const isScreensEnabled = Platform.OS === 'android' && mode !== 'modal';
return ( return (
<React.Fragment> <React.Fragment>
@@ -550,7 +560,6 @@ export default class CardStack extends React.Component<Props, State> {
renderScene={renderScene} renderScene={renderScene}
onOpenRoute={onOpenRoute} onOpenRoute={onOpenRoute}
onCloseRoute={onCloseRoute} onCloseRoute={onCloseRoute}
onTransitionComplete={onTransitionComplete}
onTransitionStart={onTransitionStart} onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd} onTransitionEnd={onTransitionEnd}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })} gestureEnabled={index !== 0 && getGesturesEnabled({ route })}

View File

@@ -117,7 +117,7 @@ export default class StackView extends React.Component<Props, State> {
// 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
if (!previousRoutes.find(r => r.key === nextFocusedRoute.key)) { if (!previousRoutes.find((r) => r.key === nextFocusedRoute.key)) {
// A new route has come to the focus, we treat this as a push // A new route has come to the focus, we treat this as a push
// A replace can also trigger this, the animation should look like push // A replace can also trigger this, the animation should look like push
@@ -130,17 +130,17 @@ export default class StackView extends React.Component<Props, State> {
openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key]; openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
closingRouteKeys = closingRouteKeys.filter( closingRouteKeys = closingRouteKeys.filter(
key => key !== nextFocusedRoute.key (key) => key !== nextFocusedRoute.key
); );
replacingRouteKeys = replacingRouteKeys.filter( replacingRouteKeys = replacingRouteKeys.filter(
key => key !== nextFocusedRoute.key (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
openingRouteKeys = openingRouteKeys.filter( openingRouteKeys = openingRouteKeys.filter(
key => key !== previousFocusedRoute.key (key) => key !== previousFocusedRoute.key
); );
if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') { if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') {
@@ -153,7 +153,7 @@ export default class StackView extends React.Component<Props, State> {
// But since user configured it to animate the old screen like a pop, we need to add this without animation // But since user configured it to animate the old screen like a pop, we need to add this without animation
// So remove it from `openingRouteKeys` which will remove the animation // So remove it from `openingRouteKeys` which will remove the animation
openingRouteKeys = openingRouteKeys.filter( openingRouteKeys = openingRouteKeys.filter(
key => key !== nextFocusedRoute.key (key) => key !== nextFocusedRoute.key
); );
// Keep the route being removed at the end to animate it out // Keep the route being removed at the end to animate it out
@@ -165,7 +165,7 @@ export default class StackView extends React.Component<Props, State> {
]; ];
closingRouteKeys = closingRouteKeys.filter( closingRouteKeys = closingRouteKeys.filter(
key => key !== previousFocusedRoute.key (key) => key !== previousFocusedRoute.key
); );
// Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt // Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt
@@ -176,7 +176,7 @@ export default class StackView extends React.Component<Props, State> {
} }
} }
} }
} else if (!routes.find(r => r.key === previousFocusedRoute.key)) { } else if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
// The previously focused route was removed, we treat this as a pop // The previously focused route was removed, we treat this as a pop
if ( if (
@@ -188,10 +188,10 @@ export default class StackView extends React.Component<Props, State> {
// 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
openingRouteKeys = openingRouteKeys.filter( openingRouteKeys = openingRouteKeys.filter(
key => key !== previousFocusedRoute.key (key) => key !== previousFocusedRoute.key
); );
replacingRouteKeys = replacingRouteKeys.filter( replacingRouteKeys = replacingRouteKeys.filter(
key => key !== previousFocusedRoute.key (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
@@ -273,13 +273,13 @@ export default class StackView extends React.Component<Props, State> {
private getPreviousRoute = ({ route }: { route: Route<string> }) => { private getPreviousRoute = ({ route }: { route: Route<string> }) => {
const { closingRouteKeys, replacingRouteKeys } = 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 ||
(!closingRouteKeys.includes(r.key) && (!closingRouteKeys.includes(r.key) &&
!replacingRouteKeys.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);
return routes[index - 1]; return routes[index - 1];
}; };
@@ -308,27 +308,31 @@ export default class StackView extends React.Component<Props, State> {
return <HeaderContainer {...props} />; return <HeaderContainer {...props} />;
}; };
private handleTransitionComplete = ({ route }: { route: Route<string> }) => { private handleTransitionComplete = () => {
const { state, navigation } = this.props; const { state, navigation } = this.props;
if (state.isTransitioning) { if (state.isTransitioning) {
navigation.dispatch( navigation.dispatch(
StackActions.completeTransition({ StackActions.completeTransition({
key: navigation.state.key, key: navigation.state.key,
toChildKey: route.key, toChildKey: state.routes[state.index].key,
}) })
); );
} }
}; };
private handleOpenRoute = ({ route }: { route: Route<string> }) => { private handleOpenRoute = ({ route }: { route: Route<string> }) => {
this.handleTransitionComplete({ route }); this.handleTransitionComplete();
this.setState(state => ({ this.setState((state) => ({
routes: state.replacingRouteKeys.length routes: state.replacingRouteKeys.length
? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key)) ? state.routes.filter((r) => !state.replacingRouteKeys.includes(r.key))
: state.routes, : state.routes,
openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key), openingRouteKeys: state.openingRouteKeys.filter(
closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key), (key) => key !== route.key
),
closingRouteKeys: state.closingRouteKeys.filter(
(key) => key !== route.key
),
replacingRouteKeys: [], replacingRouteKeys: [],
})); }));
}; };
@@ -336,29 +340,23 @@ export default class StackView extends React.Component<Props, State> {
private handleCloseRoute = ({ route }: { route: Route<string> }) => { private handleCloseRoute = ({ route }: { route: Route<string> }) => {
const { state, navigation } = this.props; const { state, navigation } = this.props;
if (state.routes.find(r => r.key === route.key)) { if (state.routes.find((r) => r.key === route.key)) {
// If a route exists in state, trigger a pop // If a route exists in state, trigger a pop
// This will happen in when the route was closed from the card component // This will happen in when the route was closed from the card component
// e.g. When the close animation triggered from a gesture ends // e.g. When the close animation triggered from a gesture ends
// @ts-ignore // @ts-ignore
navigation.dispatch(StackActions.pop({ key: route.key, prune: false })); navigation.dispatch(StackActions.pop({ key: route.key, prune: false }));
} else { } else {
// While closing route we need to point to the previous one assuming that this.handleTransitionComplete();
// this previous one in routes array
const index = this.state.routes.findIndex(r => r.key === route.key);
this.handleTransitionComplete({
route: this.state.routes[Math.max(index - 1, 0)],
});
// We need to clean up any state tracking the route and pop it immediately // We need to clean up any state tracking the route and pop it immediately
this.setState(state => ({ this.setState((state) => ({
routes: state.routes.filter(r => r.key !== route.key), routes: state.routes.filter((r) => r.key !== route.key),
openingRouteKeys: state.openingRouteKeys.filter( openingRouteKeys: state.openingRouteKeys.filter(
key => key !== route.key (key) => key !== route.key
), ),
closingRouteKeys: state.closingRouteKeys.filter( closingRouteKeys: state.closingRouteKeys.filter(
key => key !== route.key (key) => key !== route.key
), ),
})); }));
} }
@@ -410,9 +408,9 @@ export default class StackView extends React.Component<Props, State> {
<GestureHandlerWrapper style={styles.container}> <GestureHandlerWrapper style={styles.container}>
<SafeAreaProviderCompat> <SafeAreaProviderCompat>
<SafeAreaConsumer> <SafeAreaConsumer>
{insets => ( {(insets) => (
<KeyboardManager enabled={keyboardHandlingEnabled !== false}> <KeyboardManager enabled={keyboardHandlingEnabled !== false}>
{props => ( {(props) => (
<CardStack <CardStack
mode={mode} mode={mode}
insets={insets as EdgeInsets} insets={insets as EdgeInsets}
@@ -423,7 +421,6 @@ export default class StackView extends React.Component<Props, State> {
closingRouteKeys={closingRouteKeys} closingRouteKeys={closingRouteKeys}
onOpenRoute={this.handleOpenRoute} onOpenRoute={this.handleOpenRoute}
onCloseRoute={this.handleCloseRoute} onCloseRoute={this.handleCloseRoute}
onTransitionComplete={this.handleTransitionComplete}
onTransitionStart={this.handleTransitionStart} onTransitionStart={this.handleTransitionStart}
onTransitionEnd={this.handleTransitionEnd} onTransitionEnd={this.handleTransitionEnd}
renderHeader={this.renderHeader} renderHeader={this.renderHeader}

View File

@@ -3,6 +3,60 @@
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.
## [2.8.9](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.7...react-navigation-tabs@2.8.9) (2020-03-28)
**Note:** Version bump only for package react-navigation-tabs
## [2.8.8](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.7...react-navigation-tabs@2.8.8) (2020-03-27)
**Note:** Version bump only for package react-navigation-tabs
## [2.8.7](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.6...react-navigation-tabs@2.8.7) (2020-03-22)
**Note:** Version bump only for package react-navigation-tabs
## [2.8.6](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.5...react-navigation-tabs@2.8.6) (2020-03-20)
**Note:** Version bump only for package react-navigation-tabs
## [2.8.5](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.4...react-navigation-tabs@2.8.5) (2020-03-19)
### Bug Fixes
* don't use react-native-screens on web ([44c3900](https://github.com/react-navigation/tabs/commit/44c390075f7b76664e09fd9a1a7926645133ebec)), closes [#7485](https://github.com/react-navigation/tabs/issues/7485)
## [2.8.4](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.3...react-navigation-tabs@2.8.4) (2020-03-16)
### Bug Fixes
* downgrade react-native-safe-area-view ([cfc1bac](https://github.com/react-navigation/tabs/commit/cfc1bac4e153db4a4ba3f2a9033f77b53367fcbc)), closes [#7813](https://github.com/react-navigation/tabs/issues/7813)
## [2.8.3](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.2...react-navigation-tabs@2.8.3) (2020-03-16) ## [2.8.3](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.2...react-navigation-tabs@2.8.3) (2020-03-16)
**Note:** Version bump only for package react-navigation-tabs **Note:** Version bump only for package react-navigation-tabs

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-tabs", "name": "react-navigation-tabs",
"version": "2.8.3", "version": "2.8.9",
"description": "Tab Navigation components for React Navigation", "description": "Tab Navigation components for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -42,7 +42,7 @@
"dependencies": { "dependencies": {
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
"react-native-safe-area-view": "^1.0.0", "react-native-safe-area-view": "^0.14.8",
"react-native-tab-view": "^2.11.0" "react-native-tab-view": "^2.11.0"
}, },
"devDependencies": { "devDependencies": {
@@ -56,7 +56,7 @@
"react-native-gesture-handler": "^1.6.0", "react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.2.0", "react-native-reanimated": "^1.2.0",
"react-native-tab-view": "^2.13.0", "react-native-tab-view": "^2.13.0",
"react-navigation": "^4.3.0", "react-navigation": "^4.3.5",
"typescript": "~3.7.5" "typescript": "~3.7.5"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -131,7 +131,9 @@ class TabNavigationView extends React.PureComponent<Props, State> {
_jumpTo = (key: string) => { _jumpTo = (key: string) => {
const { navigation, onIndexChange } = this.props; const { navigation, onIndexChange } = this.props;
const index = navigation.state.routes.findIndex(route => route.key === key); const index = navigation.state.routes.findIndex(
(route) => route.key === key
);
onIndexChange(index); onIndexChange(index);
}; };

View File

@@ -134,12 +134,12 @@ class MaterialTabView extends React.PureComponent<Props> {
renderTabBar={this.renderTabBar} renderTabBar={this.renderTabBar}
renderLazyPlaceholder={ renderLazyPlaceholder={
lazyPlaceholderComponent !== undefined lazyPlaceholderComponent !== undefined
? props => React.createElement(lazyPlaceholderComponent, props) ? (props) => React.createElement(lazyPlaceholderComponent, props)
: undefined : undefined
} }
renderPager={ renderPager={
pagerComponent !== undefined pagerComponent !== undefined
? props => React.createElement(pagerComponent, props) ? (props) => React.createElement(pagerComponent, props)
: undefined : undefined
} }
/> />

View File

@@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native'; import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens'; import { Screen, screensEnabled } from 'react-native-screens';
type Props = { type Props = {
@@ -9,12 +8,14 @@ type Props = {
style?: any; style?: any;
}; };
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
if (screensEnabled?.()) { // react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore // @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />; return <Screen active={isVisible ? 1 : 0} {...rest} />;
} }
@@ -23,7 +24,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
return ( return (
<View <View
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]} style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
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

@@ -9,7 +9,7 @@ const packages = path.join(__dirname, '..', 'packages');
const invalid = []; const invalid = [];
fs.readdirSync(packages).forEach(name => { fs.readdirSync(packages).forEach((name) => {
const dir = path.join(packages, name); const dir = path.join(packages, name);
if (fs.statSync(path.join(packages, name)).isDirectory()) { if (fs.statSync(path.join(packages, name)).isDirectory()) {
@@ -26,6 +26,6 @@ fs.readdirSync(packages).forEach(name => {
if (invalid.length) { if (invalid.length) {
console.log( console.log(
'Found invalid path to type definitions in the following packages:\n', 'Found invalid path to type definitions in the following packages:\n',
invalid.map(p => `- ${p.name} (${p.types})`).join('\n') invalid.map((p) => `- ${p.name} (${p.types})`).join('\n')
); );
} }

3222
yarn.lock

File diff suppressed because it is too large Load Diff