diff --git a/examples/NavigationPlayground/js/App.js b/examples/NavigationPlayground/js/App.js
index 9db6a2cbb3..6435338e82 100644
--- a/examples/NavigationPlayground/js/App.js
+++ b/examples/NavigationPlayground/js/App.js
@@ -322,7 +322,7 @@ const AppNavigator = createStackNavigator(
}
);
-export default () => ;
+export default AppNavigator;
const styles = StyleSheet.create({
item: {
diff --git a/examples/NavigationPlayground/js/Drawer.js b/examples/NavigationPlayground/js/Drawer.js
index e80b070735..5c4c5a0386 100644
--- a/examples/NavigationPlayground/js/Drawer.js
+++ b/examples/NavigationPlayground/js/Drawer.js
@@ -32,14 +32,7 @@ const InboxScreen = ({ navigation }) => (
);
InboxScreen.navigationOptions = {
- drawerLabel: 'Inbox',
- drawerIcon: ({ tintColor }) => (
-
- ),
+ headerTitle: 'Inbox',
};
const EmailScreen = ({ navigation }) => (
@@ -50,10 +43,7 @@ const DraftsScreen = ({ navigation }) => (
);
DraftsScreen.navigationOptions = {
- drawerLabel: 'Drafts',
- drawerIcon: ({ tintColor }) => (
-
- ),
+ headerTitle: 'Drafts',
};
const InboxStack = createStackNavigator({
@@ -61,11 +51,29 @@ const InboxStack = createStackNavigator({
Email: { screen: EmailScreen },
});
+InboxStack.navigationOptions = {
+ drawerLabel: 'Inbox',
+ drawerIcon: ({ tintColor }) => (
+
+ ),
+};
+
const DraftsStack = createStackNavigator({
Drafts: { screen: DraftsScreen },
Email: { screen: EmailScreen },
});
+DraftsStack.navigationOptions = {
+ drawerLabel: 'Drafts',
+ drawerIcon: ({ tintColor }) => (
+
+ ),
+};
+
const DrawerExample = createDrawerNavigator(
{
Inbox: {
diff --git a/examples/NavigationPlayground/js/KeyboardHandlingExample.js b/examples/NavigationPlayground/js/KeyboardHandlingExample.js
index 7a0674a514..01e2ce765d 100644
--- a/examples/NavigationPlayground/js/KeyboardHandlingExample.js
+++ b/examples/NavigationPlayground/js/KeyboardHandlingExample.js
@@ -24,9 +24,9 @@ class ScreenOne extends React.Component {
}
class ScreenTwo extends React.Component {
- static navigationOptions = {
- title: 'Screen w/ Input',
- };
+ static navigationOptions = ({ navigation }) => ({
+ title: navigation.getParam('inputValue', 'Screen w/ Input'),
+ });
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
@@ -41,6 +41,7 @@ class ScreenTwo extends React.Component {
(this._textInput = c)}
+ onChangeText={inputValue => navigation.setParams({ inputValue })}
style={{
backgroundColor: 'white',
height: 24,
diff --git a/examples/NavigationPlayground/js/TabsWithNavigationFocus.js b/examples/NavigationPlayground/js/TabsWithNavigationFocus.js
index 79effb2c97..e40b3125bd 100644
--- a/examples/NavigationPlayground/js/TabsWithNavigationFocus.js
+++ b/examples/NavigationPlayground/js/TabsWithNavigationFocus.js
@@ -4,10 +4,8 @@
import React from 'react';
import { SafeAreaView, StatusBar, Text, View } from 'react-native';
-import {
- createBottomTabNavigator,
- withNavigationFocus,
-} from 'react-navigation';
+import { withNavigationFocus } from 'react-navigation';
+import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Button } from './commonComponents/ButtonWithMargin';
@@ -80,16 +78,26 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
return withNavigationFocus(TabScreen);
};
-const TabsWithNavigationFocus = createBottomTabNavigator({
- One: {
- screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
- },
- Two: {
- screen: createTabScreen('Two', 'numeric-2-box-outline', 'numeric-2-box'),
- },
- Three: {
- screen: createTabScreen('Three', 'numeric-3-box-outline', 'numeric-3-box'),
+const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
+ {
+ One: {
+ screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
+ },
+ Two: {
+ screen: createTabScreen('Two', 'numeric-2-box-outline', 'numeric-2-box'),
+ },
+ Three: {
+ screen: createTabScreen(
+ 'Three',
+ 'numeric-3-box-outline',
+ 'numeric-3-box'
+ ),
+ },
},
-});
+ {
+ shifting: false,
+ activeTintColor: '#F44336',
+ }
+);
export default TabsWithNavigationFocus;
diff --git a/examples/NavigationPlayground/package.json b/examples/NavigationPlayground/package.json
index e60490a6a7..7a8da1126c 100644
--- a/examples/NavigationPlayground/package.json
+++ b/examples/NavigationPlayground/package.json
@@ -16,7 +16,8 @@
"react-native": "^0.54.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-navigation": "link:../..",
- "react-navigation-header-buttons": "^0.0.4"
+ "react-navigation-header-buttons": "^0.0.4",
+ "react-navigation-material-bottom-tabs": "0.1.3"
},
"devDependencies": {
"babel-jest": "^22.4.1",
diff --git a/examples/NavigationPlayground/yarn.lock b/examples/NavigationPlayground/yarn.lock
index 08ee41e75f..590a1b0011 100644
--- a/examples/NavigationPlayground/yarn.lock
+++ b/examples/NavigationPlayground/yarn.lock
@@ -2110,6 +2110,13 @@ create-react-context@^0.2.1:
fbjs "^0.8.0"
gud "^1.0.0"
+create-react-context@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
+ dependencies:
+ fbjs "^0.8.0"
+ gud "^1.0.0"
+
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -2220,7 +2227,7 @@ deepmerge@^1.3.0, deepmerge@^1.5.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
-deepmerge@^2.0.1:
+deepmerge@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102"
@@ -5550,6 +5557,10 @@ react-lifecycles-compat@^1.0.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.1.4.tgz#fc005c72849b7ed364de20a0f64ff58ebdc2009a"
+react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz#7279047275bd727a912e25f734c0559527e84eff"
+
react-native-branch@2.0.0-beta.3:
version "2.0.0-beta.3"
resolved "https://registry.yarnpkg.com/react-native-branch/-/react-native-branch-2.0.0-beta.3.tgz#2167af86bbc9f964bd45bd5f37684e5b54965e32"
@@ -5589,14 +5600,15 @@ react-native-iphone-x-helper@^1.0.2:
babel-plugin-module-resolver "^2.3.0"
babel-preset-react-native "1.9.0"
-react-native-paper@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/react-native-paper/-/react-native-paper-1.2.5.tgz#3a4480ca967aae4b3e65a987116973dd012d1fa8"
+react-native-paper@=1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/react-native-paper/-/react-native-paper-1.4.0.tgz#33bd477cde1468b63e60bdb0c9a500822d03cdca"
dependencies:
color "^2.0.1"
- create-react-context "^0.2.1"
- deepmerge "^2.0.1"
+ create-react-context "^0.2.2"
+ deepmerge "^2.1.0"
hoist-non-react-statics "^2.5.0"
+ react-lifecycles-compat "^3.0.2"
react-native-platform-touchable@^1.1.1:
version "1.1.1"
@@ -5642,9 +5654,9 @@ react-native-svg@6.2.2:
lodash "^4.16.6"
pegjs "^0.10.0"
-"react-native-tab-view@github:react-navigation/react-native-tab-view":
+react-native-tab-view@^0.0.74:
version "0.0.74"
- resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb"
+ resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.74.tgz#62c0c882d9232b461ce181d440d683b4f99d1bd8"
dependencies:
prop-types "^15.6.0"
@@ -5725,11 +5737,11 @@ react-native@^0.54.0:
xmldoc "^0.4.0"
yargs "^9.0.0"
-react-navigation-deprecated-tab-navigator@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.0.1.tgz#a869d749d16116630411d6c7761c51484ba90175"
+react-navigation-deprecated-tab-navigator@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.2.0.tgz#e0d969c196dcd3a4a440770a7bd97fa058eb4aaf"
dependencies:
- react-native-tab-view react-navigation/react-native-tab-view
+ react-native-tab-view "^0.0.74"
react-navigation-header-buttons@^0.0.4:
version "0.0.4"
@@ -5737,14 +5749,32 @@ react-navigation-header-buttons@^0.0.4:
dependencies:
react-native-platform-touchable "^1.1.1"
-react-navigation-tabs@0.1.0-alpha.5:
- version "0.1.0-alpha.5"
- resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.1.0-alpha.5.tgz#51cae3ea5f0f77bdf0deb50a855a5dcaa2f9bf1e"
+react-navigation-material-bottom-tabs@0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/react-navigation-material-bottom-tabs/-/react-navigation-material-bottom-tabs-0.1.3.tgz#8e11c3d57ebb72d19adf5e4764390729ed964072"
+ dependencies:
+ hoist-non-react-statics "^2.5.0"
+ prop-types "^15.6.0"
+ react-native-paper "=1.4.0"
+ react-navigation-tabs "0.2.0-rc.0"
+
+react-navigation-tabs@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.2.0.tgz#d25be64e01a728c893a93cfeee6b176ab74d67de"
+ dependencies:
+ hoist-non-react-statics "^2.5.0"
+ prop-types "^15.6.0"
+ react-lifecycles-compat "^1.0.2"
+ react-native-safe-area-view "^0.7.0"
+ react-native-tab-view "~0.0.77"
+
+react-navigation-tabs@0.2.0-rc.0:
+ version "0.2.0-rc.0"
+ resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.2.0-rc.0.tgz#671e8c9914b915796879329752f7240147494038"
dependencies:
hoist-non-react-statics "^2.5.0"
prop-types "^15.6.0"
react-lifecycles-compat "^1.0.2"
- react-native-paper "^1.2.5"
react-native-safe-area-view "^0.7.0"
react-native-tab-view "~0.0.77"
diff --git a/flow/react-navigation.js b/flow/react-navigation.js
index 57df21db1c..30ea9dd477 100644
--- a/flow/react-navigation.js
+++ b/flow/react-navigation.js
@@ -809,10 +809,6 @@ declare module 'react-navigation' {
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationContainer<*, *, *>;
- declare export function createMaterialBottomTabNavigator(
- routeConfigs: NavigationRouteConfigMap,
- config?: _TabNavigatorConfig
- ): NavigationContainer<*, *, *>;
declare export function createMaterialTopTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
diff --git a/package.json b/package.json
index fd35f536af..1766744d68 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-navigation",
- "version": "2.0.0-rc.7",
+ "version": "2.0.2",
"description": "Routing and navigation for your React Native apps",
"main": "src/react-navigation.js",
"repository": {
@@ -34,11 +34,11 @@
"hoist-non-react-statics": "^2.2.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.5.10",
- "react-lifecycles-compat": "^1.0.2",
+ "react-lifecycles-compat": "^3",
"react-native-drawer-layout-polyfill": "^1.3.2",
"react-native-safe-area-view": "^0.7.0",
- "react-navigation-deprecated-tab-navigator": "1.1.0",
- "react-navigation-tabs": "0.1.0-alpha.8"
+ "react-navigation-deprecated-tab-navigator": "1.2.0",
+ "react-navigation-tabs": "0.2.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
diff --git a/src/StateUtils.js b/src/StateUtils.js
index c743eac3a5..2fa2ab8e3b 100644
--- a/src/StateUtils.js
+++ b/src/StateUtils.js
@@ -21,7 +21,7 @@ const StateUtils = {
* routes of the navigation state, or -1 if it is not present.
*/
indexOf(state, key) {
- return state.routes.map(route => route.key).indexOf(key);
+ return state.routes.findIndex(route => route.key === key);
},
/**
diff --git a/src/__tests__/addNavigationHelpers-test.js b/src/__tests__/addNavigationHelpers-test.js
deleted file mode 100644
index 642d7677da..0000000000
--- a/src/__tests__/addNavigationHelpers-test.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import NavigationActions from '../NavigationActions';
-import addNavigationHelpers from '../addNavigationHelpers';
-
-const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
- remove: () => {},
-});
-
-describe('addNavigationHelpers', () => {
- it('handles dismiss action', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- const child = { key: 'A', routeName: 'Home' };
- expect(
- addNavigationHelpers({
- state: child,
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- dangerouslyGetParent: () => ({
- state: {
- key: 'P',
- routeName: 'Parent',
- routes: [child],
- },
- }),
- }).dismiss()
- ).toEqual(true);
- expect(mockedDispatch).toBeCalledWith({
- type: NavigationActions.BACK,
- key: 'P',
- });
- expect(mockedDispatch.mock.calls.length).toBe(1);
- });
-
- it('handles Back action', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { key: 'A', routeName: 'Home' },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).goBack('A')
- ).toEqual(true);
- expect(mockedDispatch).toBeCalledWith({
- type: NavigationActions.BACK,
- key: 'A',
- });
- expect(mockedDispatch.mock.calls.length).toBe(1);
- });
-
- it('handles Back action when the key is not defined', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { routeName: 'Home' },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).goBack()
- ).toEqual(true);
- expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK });
- expect(mockedDispatch.mock.calls.length).toBe(1);
- });
-
- it('handles Navigate action', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { routeName: 'Home' },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).navigate('Profile', { name: 'Matt' })
- ).toEqual(true);
- expect(mockedDispatch).toBeCalledWith({
- type: NavigationActions.NAVIGATE,
- params: { name: 'Matt' },
- routeName: 'Profile',
- });
- expect(mockedDispatch.mock.calls.length).toBe(1);
- });
-
- it('handles SetParams action', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { key: 'B', routeName: 'Settings' },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).setParams({ notificationsEnabled: 'yes' })
- ).toEqual(true);
- expect(mockedDispatch).toBeCalledWith({
- type: NavigationActions.SET_PARAMS,
- key: 'B',
- params: { notificationsEnabled: 'yes' },
- });
- expect(mockedDispatch.mock.calls.length).toBe(1);
- });
-
- it('handles GetParams action', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { key: 'B', routeName: 'Settings', params: { name: 'Peter' } },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).getParam('name', 'Brent')
- ).toEqual('Peter');
- });
-
- it('handles GetParams action with default param', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { key: 'B', routeName: 'Settings' },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).getParam('name', 'Brent')
- ).toEqual('Brent');
- });
-
- it('handles GetParams action with param value as null', () => {
- const mockedDispatch = jest
- .fn(() => false)
- .mockImplementationOnce(() => true);
- expect(
- addNavigationHelpers({
- state: { key: 'B', routeName: 'Settings', params: { name: null } },
- dispatch: mockedDispatch,
- addListener: dummyEventSubscriber,
- }).getParam('name')
- ).toEqual(null);
- });
-});
diff --git a/src/addNavigationHelpers.js b/src/addNavigationHelpers.js
deleted file mode 100644
index 57bbd0f74e..0000000000
--- a/src/addNavigationHelpers.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/* Helpers for navigation */
-
-import NavigationActions from './NavigationActions';
-import invariant from './utils/invariant';
-export default function(navigation) {
- return {
- ...navigation,
- // Go back from the given key, default to active key
- goBack: key => {
- let actualizedKey = key;
- if (key === undefined && navigation.state.key) {
- invariant(
- typeof navigation.state.key === 'string',
- 'key should be a string'
- );
- actualizedKey = navigation.state.key;
- }
- return navigation.dispatch(
- NavigationActions.back({ key: actualizedKey })
- );
- },
- // Go back from the parent key. If this is a nested stack, the entire
- // stack will be dismissed.
- dismiss: () => {
- let parent = navigation.dangerouslyGetParent();
- if (parent && parent.state) {
- return navigation.dispatch(
- NavigationActions.back({ key: parent.state.key })
- );
- } else {
- return false;
- }
- },
- navigate: (navigateTo, params, action) => {
- if (typeof navigateTo === 'string') {
- return navigation.dispatch(
- NavigationActions.navigate({ routeName: navigateTo, params, action })
- );
- }
- invariant(
- typeof navigateTo === 'object',
- 'Must navigateTo an object or a string'
- );
- invariant(
- params == null,
- 'Params must not be provided to .navigate() when specifying an object'
- );
- invariant(
- action == null,
- 'Child action must not be provided to .navigate() when specifying an object'
- );
- return navigation.dispatch(NavigationActions.navigate(navigateTo));
- },
- pop: (n, params) =>
- navigation.dispatch(
- NavigationActions.pop({ n, immediate: params && params.immediate })
- ),
- popToTop: params =>
- navigation.dispatch(
- NavigationActions.popToTop({ immediate: params && params.immediate })
- ),
- /**
- * For updating current route params. For example the nav bar title and
- * buttons are based on the route params.
- * This means `setParams` can be used to update nav bar for example.
- */
- setParams: params => {
- invariant(
- navigation.state.key && typeof navigation.state.key === 'string',
- 'setParams cannot be called by root navigator'
- );
- const key = navigation.state.key;
- return navigation.dispatch(NavigationActions.setParams({ params, key }));
- },
-
- getParam: (paramName, defaultValue) => {
- const params = navigation.state.params;
-
- if (params && paramName in params) {
- return params[paramName];
- }
-
- return defaultValue;
- },
-
- push: (routeName, params, action) =>
- navigation.dispatch(
- NavigationActions.push({ routeName, params, action })
- ),
-
- replace: (routeName, params, action) =>
- navigation.dispatch(
- NavigationActions.replace({
- routeName,
- params,
- action,
- key: navigation.state.key,
- })
- ),
-
- openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
- closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
- toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
- };
-}
diff --git a/src/createNavigationContainer.js b/src/createNavigationContainer.js
index a13fc36b35..91aebb72e9 100644
--- a/src/createNavigationContainer.js
+++ b/src/createNavigationContainer.js
@@ -1,10 +1,11 @@
import React from 'react';
-import { Linking, AsyncStorage } from 'react-native';
-import withLifecyclePolyfill from 'react-lifecycles-compat';
+import { AsyncStorage, Linking, Platform } from 'react-native';
+import { polyfill } from 'react-lifecycles-compat';
import { BackHandler } from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import invariant from './utils/invariant';
+import getNavigationActionCreators from './routers/getNavigationActionCreators';
import docsUrl from './utils/docsUrl';
function isStateful(props) {
@@ -184,9 +185,9 @@ export default function createNavigationContainer(Component) {
}
componentDidUpdate() {
- // Clear cached _nav every tick
- if (this._nav === this.state.nav) {
- this._nav = null;
+ // Clear cached _navState every tick
+ if (this._navState === this.state.nav) {
+ this._navState = null;
}
}
@@ -198,11 +199,15 @@ export default function createNavigationContainer(Component) {
if (__DEV__ && !this.props.detached) {
if (_statefulContainerCount > 0) {
- console.error(
- `You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
- 'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
- )}`
- );
+ // Temporarily only show this on iOS due to this issue:
+ // https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
+ if (Platform.OS === 'ios') {
+ console.warn(
+ `You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
+ 'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
+ )}`
+ );
+ }
}
}
_statefulContainerCount++;
@@ -252,12 +257,7 @@ export default function createNavigationContainer(Component) {
}
}
- if (startupState === this.state.nav) {
- return;
- }
-
- this.setState({ nav: startupState }, () => {
- _reactNavigationIsHydratingState = false;
+ const dispatchActions = () =>
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
@@ -266,6 +266,15 @@ export default function createNavigationContainer(Component) {
lastState: null,
})
);
+
+ if (startupState === this.state.nav) {
+ dispatchActions();
+ return;
+ }
+
+ this.setState({ nav: startupState }, () => {
+ _reactNavigationIsHydratingState = false;
+ dispatchActions();
});
}
@@ -303,32 +312,47 @@ export default function createNavigationContainer(Component) {
if (this.props.navigation) {
return this.props.navigation.dispatch(action);
}
- this._nav = this._nav || this.state.nav;
- const oldNav = this._nav;
- invariant(oldNav, 'should be set in constructor if stateful');
- const nav = Component.router.getStateForAction(action, oldNav);
+
+ // 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;
+ invariant(lastNavState, 'should be set in constructor if stateful');
+ const reducedState = Component.router.getStateForAction(
+ action,
+ lastNavState
+ );
+ const navState = reducedState === null ? lastNavState : reducedState;
+
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
- state: nav,
- lastState: oldNav,
+ state: navState,
+ lastState: lastNavState,
})
);
};
- if (nav && nav !== oldNav) {
+
+ 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._nav = nav;
- this.setState({ nav }, () => {
- this._onNavigationStateChange(oldNav, nav, action);
+ this._navState = navState;
+ this.setState({ nav: navState }, () => {
+ this._onNavigationStateChange(lastNavState, navState, action);
dispatchActionEvents();
- this._persistNavigationState(nav);
+ this._persistNavigationState(navState);
});
return true;
- } else {
- dispatchActionEvents();
}
+
+ dispatchActionEvents();
return false;
};
@@ -355,6 +379,11 @@ export default function createNavigationContainer(Component) {
};
},
};
+ const actionCreators = getNavigationActionCreators(nav);
+ Object.keys(actionCreators).forEach(actionName => {
+ this._navigation[actionName] = (...args) =>
+ this.dispatch(actionCreators[actionName](...args));
+ });
}
navigation = this._navigation;
}
@@ -363,5 +392,5 @@ export default function createNavigationContainer(Component) {
}
}
- return withLifecyclePolyfill(NavigationContainer);
+ return polyfill(NavigationContainer);
}
diff --git a/src/getChildEventSubscriber.js b/src/getChildEventSubscriber.js
index 0900bfe9f9..8839248a01 100644
--- a/src/getChildEventSubscriber.js
+++ b/src/getChildEventSubscriber.js
@@ -138,11 +138,14 @@ export default function getChildEventSubscriber(addListener, key) {
emit((lastEmittedEvent = 'didBlur'), childPayload);
}
}
+
+ if (lastEmittedEvent === 'didBlur' && !newRoute) {
+ removeAll();
+ }
})
);
return {
- removeAll,
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
diff --git a/src/navigators/createKeyboardAwareNavigator.js b/src/navigators/createKeyboardAwareNavigator.js
index 8da2d5bfe6..5b76a5acd2 100644
--- a/src/navigators/createKeyboardAwareNavigator.js
+++ b/src/navigators/createKeyboardAwareNavigator.js
@@ -39,10 +39,16 @@ export default Navigator =>
};
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
- const currentField = TextInput.State.currentlyFocusedField();
- if (currentField) {
- TextInput.State.blurTextInput(currentField);
+ // TODO: We should not even have received the transition start event
+ // in the case where the index did not change, I believe. We
+ // should revisit this after 2.0 release.
+ if (transitionProps.index !== prevTransitionProps.index) {
+ const currentField = TextInput.State.currentlyFocusedField();
+ if (currentField) {
+ TextInput.State.blurTextInput(currentField);
+ }
}
+
this.props.onTransitionStart &&
this.props.onTransitionStart(transitionProps, prevTransitionProps);
};
diff --git a/src/navigators/createNavigator.js b/src/navigators/createNavigator.js
index 6401aeb6cf..35a1d4f5af 100644
--- a/src/navigators/createNavigator.js
+++ b/src/navigators/createNavigator.js
@@ -14,15 +14,14 @@ function createNavigator(NavigatorView, router, navigationConfig) {
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
Object.keys(this.childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
- this.childEventSubscribers[key].removeAll();
delete this.childEventSubscribers[key];
}
});
}
- // Remove all subscriptions
+ // Remove all subscription references
componentWillUnmount() {
- Object.values(this.childEventSubscribers).map(s => s.removeAll());
+ this.childEventSubscribers = {};
}
_isRouteFocused = route => {
diff --git a/src/react-navigation.js b/src/react-navigation.js
index e0d59c2eee..d93c0dcc79 100644
--- a/src/react-navigation.js
+++ b/src/react-navigation.js
@@ -57,9 +57,6 @@ module.exports = {
get createBottomTabNavigator() {
return require('react-navigation-tabs').createBottomTabNavigator;
},
- get createMaterialBottomTabNavigator() {
- return require('react-navigation-tabs').createMaterialBottomTabNavigator;
- },
get createMaterialTopTabNavigator() {
return require('react-navigation-tabs').createMaterialTopTabNavigator;
},
diff --git a/src/routers/DrawerRouter.js b/src/routers/DrawerRouter.js
index 03716a8dc4..a38a806096 100644
--- a/src/routers/DrawerRouter.js
+++ b/src/routers/DrawerRouter.js
@@ -33,44 +33,52 @@ export default (routeConfigs, config = {}) => {
const isRouterTargeted = action.key == null || action.key === state.key;
- if (
- isRouterTargeted &&
- action.type === DrawerActions.CLOSE_DRAWER &&
- state.isDrawerOpen
- ) {
- return {
- ...state,
- isDrawerOpen: false,
- };
- }
+ if (isRouterTargeted) {
+ // Only handle actions that are meant for this drawer, as specified by action.key.
+
+ if (action.type === DrawerActions.CLOSE_DRAWER && state.isDrawerOpen) {
+ return {
+ ...state,
+ isDrawerOpen: false,
+ };
+ }
+
+ if (action.type === DrawerActions.OPEN_DRAWER && !state.isDrawerOpen) {
+ return {
+ ...state,
+ isDrawerOpen: true,
+ };
+ }
- if (
- isRouterTargeted &&
- action.type === DrawerActions.OPEN_DRAWER &&
- !state.isDrawerOpen
- ) {
- return {
- ...state,
- isDrawerOpen: true,
- };
+ if (action.type === DrawerActions.TOGGLE_DRAWER) {
+ return {
+ ...state,
+ isDrawerOpen: !state.isDrawerOpen,
+ };
+ }
}
- if (isRouterTargeted && action.type === DrawerActions.TOGGLE_DRAWER) {
- return {
- ...state,
- isDrawerOpen: !state.isDrawerOpen,
- };
+ // Fall back on switch router for screen switching logic, and handling of child routers
+ const switchedState = switchRouter.getStateForAction(action, state);
+
+ if (switchedState === null) {
+ // The switch router or a child router is attempting to swallow this action. We return null to allow this.
+ return null;
}
- // Fall back on tab router for screen switching logic
- const childState = switchRouter.getStateForAction(action, state);
- if (childState !== null && childState !== state) {
- // If the tabs have changed, make sure to close the drawer
- return {
- ...childState,
- isDrawerOpen: false,
- };
+ if (switchedState !== state) {
+ if (switchedState.index !== state.index) {
+ // If the tabs have changed, make sure to close the drawer
+ return {
+ ...switchedState,
+ isDrawerOpen: false,
+ };
+ }
+ // Return the state new state, as returned by the switch router.
+ // The index hasn't changed, so this most likely means that a child router has returned a new state
+ return switchedState;
}
+
return state;
},
};
diff --git a/src/routers/StackRouter.js b/src/routers/StackRouter.js
index 00ba9b2d11..2f94620c8a 100644
--- a/src/routers/StackRouter.js
+++ b/src/routers/StackRouter.js
@@ -164,7 +164,7 @@ export default (routeConfigs, stackConfig = {}) => {
getActionCreators(route, navStateKey) {
return {
- ...getNavigationActionCreators(route, navStateKey),
+ ...getNavigationActionCreators(route),
...getCustomActionCreators(route, navStateKey),
pop: (n, params) =>
StackActions.pop({
@@ -255,11 +255,14 @@ export default (routeConfigs, stackConfig = {}) => {
// active route has the first opportunity, then the one before it, etc.
for (let childRoute of state.routes.slice().reverse()) {
let childRouter = childRouters[childRoute.routeName];
- let debug = action.params && action.params.debug;
+ let childAction =
+ action.routeName === childRoute.routeName && action.action
+ ? action.action
+ : action;
if (childRouter) {
const nextRouteState = childRouter.getStateForAction(
- action,
+ childAction,
childRoute
);
@@ -324,7 +327,7 @@ export default (routeConfigs, stackConfig = {}) => {
isTransitioning:
state.index !== lastRouteIndex
? action.immediate !== true
- : undefined,
+ : state.isTransitioning,
index: lastRouteIndex,
routes,
};
diff --git a/src/routers/SwitchRouter.js b/src/routers/SwitchRouter.js
index 01fb6195b3..cd375c1b3a 100644
--- a/src/routers/SwitchRouter.js
+++ b/src/routers/SwitchRouter.js
@@ -108,7 +108,7 @@ export default (routeConfigs, config = {}) => {
getActionCreators(route, stateKey) {
return {
- ...getNavigationActionCreators(route, stateKey),
+ ...getNavigationActionCreators(route),
...getCustomActionCreators(route, stateKey),
};
},
diff --git a/src/routers/__tests__/DrawerRouter-test.js b/src/routers/__tests__/DrawerRouter-test.js
index f1a588c2cf..42f9bff5dc 100644
--- a/src/routers/__tests__/DrawerRouter-test.js
+++ b/src/routers/__tests__/DrawerRouter-test.js
@@ -91,3 +91,49 @@ describe('DrawerRouter', () => {
expect(state3.isDrawerOpen).toEqual(true);
});
});
+
+test('Nested routers bubble up blocked actions', () => {
+ const ScreenA = () => ;
+ ScreenA.router = {
+ getStateForAction(action, lastState) {
+ if (action.type === 'CHILD_ACTION') return null;
+ return lastState;
+ },
+ };
+ const ScreenB = () => ;
+ const router = DrawerRouter({
+ Foo: { screen: ScreenA },
+ Bar: { screen: ScreenB },
+ });
+ const state = router.getStateForAction(INIT_ACTION);
+
+ const state2 = router.getStateForAction({ type: 'CHILD_ACTION' }, state);
+ expect(state2).toEqual(null);
+});
+
+test('Drawer stays open when child routers return new state', () => {
+ const ScreenA = () => ;
+ ScreenA.router = {
+ getStateForAction(action, lastState = { changed: false }) {
+ if (action.type === 'CHILD_ACTION')
+ return { ...lastState, changed: true };
+ return lastState;
+ },
+ };
+ const router = DrawerRouter({
+ Foo: { screen: ScreenA },
+ });
+
+ const state = router.getStateForAction(INIT_ACTION);
+ expect(state.isDrawerOpen).toEqual(false);
+
+ const state2 = router.getStateForAction(
+ { type: DrawerActions.OPEN_DRAWER, key: state.key },
+ state
+ );
+ expect(state2.isDrawerOpen).toEqual(true);
+
+ const state3 = router.getStateForAction({ type: 'CHILD_ACTION' }, state2);
+ expect(state3.isDrawerOpen).toEqual(true);
+ expect(state3.routes[0].changed).toEqual(true);
+});
diff --git a/src/routers/__tests__/Routers-test.js b/src/routers/__tests__/Routers-test.js
index 38e344577f..33e3a5af50 100644
--- a/src/routers/__tests__/Routers-test.js
+++ b/src/routers/__tests__/Routers-test.js
@@ -4,6 +4,7 @@ import React from 'react';
import StackRouter from '../StackRouter';
import TabRouter from '../TabRouter';
+import SwitchRouter from '../SwitchRouter';
import NavigationActions from '../../NavigationActions';
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
@@ -89,6 +90,75 @@ Object.keys(ROUTERS).forEach(routerName => {
});
});
+test('Nested navigate behavior test', () => {
+ const Leaf = () => ;
+
+ const First = () => ;
+ First.router = StackRouter({
+ First1: Leaf,
+ First2: Leaf,
+ });
+
+ const Second = () => ;
+ Second.router = StackRouter({
+ Second1: Leaf,
+ Second2: Leaf,
+ });
+
+ const Main = () => ;
+ Main.router = StackRouter({
+ First,
+ Second,
+ });
+ const TestRouter = SwitchRouter({
+ Login: Leaf,
+ Main,
+ });
+
+ const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
+
+ const state2 = TestRouter.getStateForAction(
+ { type: NavigationActions.NAVIGATE, routeName: 'First' },
+ state1
+ );
+ expect(state2.index).toEqual(1);
+ expect(state2.routes[1].index).toEqual(0);
+ expect(state2.routes[1].routes[0].index).toEqual(0);
+
+ const state3 = TestRouter.getStateForAction(
+ { type: NavigationActions.NAVIGATE, routeName: 'Second2' },
+ state2
+ );
+ expect(state3.index).toEqual(1);
+ expect(state3.routes[1].index).toEqual(1); // second
+ expect(state3.routes[1].routes[1].index).toEqual(1); //second.second2
+
+ const state4 = TestRouter.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'First',
+ action: { type: NavigationActions.NAVIGATE, routeName: 'First2' },
+ },
+ state3,
+ true
+ );
+ expect(state4.index).toEqual(1); // main
+ expect(state4.routes[1].index).toEqual(0); // first
+ expect(state4.routes[1].routes[0].index).toEqual(1); // first2
+
+ const state5 = TestRouter.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'First',
+ action: { type: NavigationActions.NAVIGATE, routeName: 'First1' },
+ },
+ state3 // second.second2 is active on state3
+ );
+ expect(state5.index).toEqual(1); // main
+ expect(state5.routes[1].index).toEqual(0); // first
+ expect(state5.routes[1].routes[0].index).toEqual(0); // first.first1
+});
+
test('Handles no-op actions with tabs within stack router', () => {
const BarView = () => ;
const FooTabNavigator = () => ;
diff --git a/src/routers/__tests__/StackRouter-test.js b/src/routers/__tests__/StackRouter-test.js
index 1bb80792ba..b71dea8670 100644
--- a/src/routers/__tests__/StackRouter-test.js
+++ b/src/routers/__tests__/StackRouter-test.js
@@ -1850,3 +1850,120 @@ test('Handles deep navigate completion action', () => {
expect(state3 && state3.routes[0].index).toEqual(1);
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
});
+
+test('order of handling navigate action is correct for nested stackrouters', () => {
+ const Screen = () => ;
+ const NestedStack = () => ;
+ let nestedRouter = StackRouter({
+ Foo: Screen,
+ Bar: Screen,
+ });
+
+ NestedStack.router = nestedRouter;
+
+ let router = StackRouter(
+ {
+ NestedStack,
+ Bar: Screen,
+ Baz: Screen,
+ },
+ {
+ initialRouteName: 'Baz',
+ }
+ );
+
+ const state = router.getStateForAction({ type: NavigationActions.INIT });
+ expect(state.routes[state.index].routeName).toEqual('Baz');
+
+ const state2 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state
+ );
+ expect(state2.routes[state2.index].routeName).toEqual('Bar');
+
+ const state3 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Baz',
+ },
+ state2
+ );
+ expect(state3.routes[state3.index].routeName).toEqual('Baz');
+
+ const state4 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Foo',
+ },
+ state3
+ );
+ let activeState4 = state4.routes[state4.index];
+ expect(activeState4.routeName).toEqual('NestedStack');
+ expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
+
+ const state5 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state4
+ );
+ let activeState5 = state5.routes[state5.index];
+ expect(activeState5.routeName).toEqual('NestedStack');
+ expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
+});
+
+test('order of handling navigate action is correct for nested stackrouters', () => {
+ const Screen = () => ;
+ const NestedStack = () => ;
+ const OtherNestedStack = () => ;
+
+ let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
+ let otherNestedRouter = StackRouter({ Foo: Screen });
+ NestedStack.router = nestedRouter;
+ OtherNestedStack.router = otherNestedRouter;
+
+ let router = StackRouter(
+ {
+ NestedStack,
+ OtherNestedStack,
+ Bar: Screen,
+ },
+ {
+ initialRouteName: 'OtherNestedStack',
+ }
+ );
+
+ const state = router.getStateForAction({ type: NavigationActions.INIT });
+ expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
+
+ const state2 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state
+ );
+ expect(state2.routes[state2.index].routeName).toEqual('Bar');
+
+ const state3 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'NestedStack',
+ },
+ state2
+ );
+ const state4 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state3
+ );
+ let activeState4 = state4.routes[state4.index];
+ expect(activeState4.routeName).toEqual('NestedStack');
+ expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
+});
diff --git a/src/routers/__tests__/SwitchRouter-test.js b/src/routers/__tests__/SwitchRouter-test.js
index 04929465d1..d22edde5f9 100644
--- a/src/routers/__tests__/SwitchRouter-test.js
+++ b/src/routers/__tests__/SwitchRouter-test.js
@@ -127,6 +127,93 @@ describe('SwitchRouter', () => {
},
});
});
+
+ test('order of handling navigate action is correct for nested switchrouters', () => {
+ // router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
+ // if we are focused on Other and navigate to Bar, what should happen?
+
+ const Screen = () => ;
+ const NestedSwitch = () => ;
+ const OtherNestedSwitch = () => ;
+
+ let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
+ let otherNestedRouter = SwitchRouter({ Foo: Screen });
+ NestedSwitch.router = nestedRouter;
+ OtherNestedSwitch.router = otherNestedRouter;
+
+ let router = SwitchRouter(
+ {
+ NestedSwitch,
+ OtherNestedSwitch,
+ Bar: Screen,
+ },
+ {
+ initialRouteName: 'OtherNestedSwitch',
+ }
+ );
+
+ const state = router.getStateForAction({ type: NavigationActions.INIT });
+ expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
+
+ const state2 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state
+ );
+ expect(state2.routes[state2.index].routeName).toEqual('Bar');
+
+ const state3 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'NestedSwitch',
+ },
+ state2
+ );
+ const state4 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Bar',
+ },
+ state3
+ );
+ let activeState4 = state4.routes[state4.index];
+ expect(activeState4.routeName).toEqual('NestedSwitch');
+ expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
+ });
+
+ // https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
+ test('order of handling navigate action is correct for nested stackrouters', () => {
+ const Screen = () => ;
+ const MainStack = () => ;
+ const LoginStack = () => ;
+ MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
+ LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
+
+ let router = SwitchRouter(
+ {
+ Home: Screen,
+ Login: LoginStack,
+ Main: MainStack,
+ },
+ {
+ initialRouteName: 'Login',
+ }
+ );
+
+ const state = router.getStateForAction({ type: NavigationActions.INIT });
+ expect(state.routes[state.index].routeName).toEqual('Login');
+
+ const state2 = router.getStateForAction(
+ {
+ type: NavigationActions.NAVIGATE,
+ routeName: 'Home',
+ },
+ state
+ );
+ expect(state2.routes[state2.index].routeName).toEqual('Home');
+ });
});
const getExampleRouter = (config = {}) => {
diff --git a/src/routers/__tests__/createConfigGetter-test.js b/src/routers/__tests__/createConfigGetter-test.js
index 3852598f7a..0a79d88642 100644
--- a/src/routers/__tests__/createConfigGetter-test.js
+++ b/src/routers/__tests__/createConfigGetter-test.js
@@ -1,6 +1,5 @@
import { Component } from 'react';
import createConfigGetter from '../createConfigGetter';
-import addNavigationHelpers from '../../addNavigationHelpers';
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
remove: () => {},
@@ -67,81 +66,81 @@ test('should get config for screen', () => {
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).title
).toEqual('Welcome anonymous');
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[1],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).title
).toEqual('Welcome jane');
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).gesturesEnabled
).toEqual(true);
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[2],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).title
).toEqual('Settings!!!');
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[2],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).gesturesEnabled
).toEqual(false);
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[3],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).title
).toEqual('10 new notifications');
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[3],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).gesturesEnabled
).toEqual(true);
expect(
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[4],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
).gesturesEnabled
).toEqual(false);
@@ -164,11 +163,11 @@ test('should throw if the route does not exist', () => {
expect(() =>
getScreenOptions(
- addNavigationHelpers({
+ {
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
- }),
+ },
{}
)
).toThrowError(
diff --git a/src/views/Drawer/DrawerSidebar.js b/src/views/Drawer/DrawerSidebar.js
index eaf4ecf413..faf3adf0e0 100644
--- a/src/views/Drawer/DrawerSidebar.js
+++ b/src/views/Drawer/DrawerSidebar.js
@@ -2,6 +2,7 @@ import React from 'react';
import { StyleSheet, View } from 'react-native';
import NavigationActions from '../../NavigationActions';
+import StackActions from '../../routers/StackActions';
import invariant from '../../utils/invariant';
/**
@@ -46,9 +47,10 @@ class DrawerSidebar extends React.PureComponent {
_onItemPress = ({ route, focused }) => {
if (!focused) {
let subAction;
+ // TODO (v3): Revisit and repeal this behavior:
// if the child screen is a StackRouter then always navigate to its first screen (see #1914)
- if (route.index !== undefined && route.index !== 0) {
- subAction = NavigationActions.reset({
+ if (route.index != null && route.index !== 0) {
+ subAction = StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
@@ -57,7 +59,12 @@ class DrawerSidebar extends React.PureComponent {
],
});
}
- this.props.navigation.navigate(route.routeName, undefined, subAction);
+ this.props.navigation.dispatch(
+ NavigationActions.navigate({
+ routeName: route.routeName,
+ action: subAction,
+ })
+ );
}
};
diff --git a/src/views/Header/Header.js b/src/views/Header/Header.js
index 9f31bd4f66..2319ec6c4a 100644
--- a/src/views/Header/Header.js
+++ b/src/views/Header/Header.js
@@ -7,6 +7,7 @@ import {
Platform,
StyleSheet,
View,
+ I18nManager,
ViewPropTypes,
} from 'react-native';
import { MaskedViewIOS } from '../../PlatformHelpers';
@@ -565,6 +566,7 @@ const styles = StyleSheet.create({
marginTop: -0.5, // resizes down to 20.5
alignSelf: 'center',
resizeMode: 'contain',
+ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
title: {
bottom: 0,
diff --git a/src/views/ResourceSavingSceneView.js b/src/views/ResourceSavingSceneView.js
index 63b4f4ac03..fc7fa47530 100644
--- a/src/views/ResourceSavingSceneView.js
+++ b/src/views/ResourceSavingSceneView.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
-import withLifecyclePolyfill from 'react-lifecycles-compat';
+import { polyfill } from 'react-lifecycles-compat';
import SceneView from './SceneView';
@@ -77,4 +77,4 @@ const styles = StyleSheet.create({
},
});
-export default withLifecyclePolyfill(ResourceSavingSceneView);
+export default polyfill(ResourceSavingSceneView);
diff --git a/src/views/StackView/StackView.js b/src/views/StackView/StackView.js
index 2d1a61a7f7..4f4030e39f 100644
--- a/src/views/StackView/StackView.js
+++ b/src/views/StackView/StackView.js
@@ -3,7 +3,6 @@ import { NativeModules } from 'react-native';
import StackViewLayout from './StackViewLayout';
import Transitioner from '../Transitioner';
-import NavigationActions from '../../NavigationActions';
import StackActions from '../../routers/StackActions';
import TransitionConfigs from './StackViewTransitionConfigs';
@@ -24,20 +23,18 @@ class StackView extends React.Component {
configureTransition={this._configureTransition}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
- onTransitionStart={this.props.onTransitionStart}
+ onTransitionStart={this.props.navigationConfig.onTransitionStart}
onTransitionEnd={(transition, lastTransition) => {
- const { onTransitionEnd, navigation } = this.props;
- if (
- transition.navigation.state.isTransitioning &&
- !lastTransition.navigation.state.isTransitioning
- ) {
+ const { navigationConfig, navigation } = this.props;
+ if (transition.navigation.state.isTransitioning) {
navigation.dispatch(
StackActions.completeTransition({
key: navigation.state.key,
})
);
}
- onTransitionEnd && onTransitionEnd(transition, lastTransition);
+ navigationConfig.onTransitionEnd &&
+ navigationConfig.onTransitionEnd(transition, lastTransition);
}}
/>
);
diff --git a/src/views/StackView/StackViewLayout.js b/src/views/StackView/StackViewLayout.js
index 60de24302c..eb660c19f5 100644
--- a/src/views/StackView/StackViewLayout.js
+++ b/src/views/StackView/StackViewLayout.js
@@ -209,6 +209,186 @@ class StackViewLayout extends React.Component {
}
}
+ _panResponder = PanResponder.create({
+ onPanResponderTerminate: () => {
+ this._isResponding = false;
+ this._reset(index, 0);
+ this.props.onGestureCanceled && this.props.onGestureCanceled();
+ },
+ onPanResponderGrant: () => {
+ const {
+ transitionProps: { navigation, position, scene },
+ } = this.props;
+ const { index } = navigation.state;
+
+ if (index !== scene.index) {
+ return false;
+ }
+
+ position.stopAnimation((value: number) => {
+ this._isResponding = true;
+ this._gestureStartValue = value;
+ });
+ this.props.onGestureBegin && this.props.onGestureBegin();
+ },
+ onMoveShouldSetPanResponder: (event, gesture) => {
+ const {
+ transitionProps: { navigation, position, layout, scene, scenes },
+ mode,
+ } = this.props;
+ const { index } = navigation.state;
+ const isVertical = mode === 'modal';
+ const { options } = scene.descriptor;
+ const gestureDirection = options.gestureDirection;
+
+ const gestureDirectionInverted =
+ typeof gestureDirection === 'string'
+ ? gestureDirection === 'inverted'
+ : I18nManager.isRTL;
+
+ if (index !== scene.index) {
+ return false;
+ }
+
+ const immediateIndex =
+ this._immediateIndex == null ? index : this._immediateIndex;
+ const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
+ const currentDragPosition =
+ event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
+ const axisLength = isVertical
+ ? layout.height.__getValue()
+ : layout.width.__getValue();
+ const axisHasBeenMeasured = !!axisLength;
+
+ // Measure the distance from the touch to the edge of the screen
+ const screenEdgeDistance = gestureDirectionInverted
+ ? axisLength - (currentDragPosition - currentDragDistance)
+ : currentDragPosition - currentDragDistance;
+ // Compare to the gesture distance relavant to card or modal
+
+ const {
+ gestureResponseDistance: userGestureResponseDistance = {},
+ } = options;
+ const gestureResponseDistance = isVertical
+ ? userGestureResponseDistance.vertical ||
+ GESTURE_RESPONSE_DISTANCE_VERTICAL
+ : userGestureResponseDistance.horizontal ||
+ GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
+ // GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
+ if (screenEdgeDistance > gestureResponseDistance) {
+ // Reject touches that started in the middle of the screen
+ return false;
+ }
+
+ const hasDraggedEnough =
+ Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
+
+ const isOnFirstCard = immediateIndex === 0;
+ const shouldSetResponder =
+ hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
+ return shouldSetResponder;
+ },
+ onPanResponderMove: (event, gesture) => {
+ const {
+ transitionProps: { navigation, position, layout, scene },
+ mode,
+ } = this.props;
+ const { index } = navigation.state;
+ const isVertical = mode === 'modal';
+ const { options } = scene.descriptor;
+ const gestureDirection = options.gestureDirection;
+
+ const gestureDirectionInverted =
+ typeof gestureDirection === 'string'
+ ? gestureDirection === 'inverted'
+ : I18nManager.isRTL;
+
+ // Handle the moving touches for our granted responder
+ const startValue = this._gestureStartValue;
+ const axis = isVertical ? 'dy' : 'dx';
+ const axisDistance = isVertical
+ ? layout.height.__getValue()
+ : layout.width.__getValue();
+ const currentValue =
+ axis === 'dx' && gestureDirectionInverted
+ ? startValue + gesture[axis] / axisDistance
+ : startValue - gesture[axis] / axisDistance;
+ const value = clamp(index - 1, currentValue, index);
+ position.setValue(value);
+ },
+ onPanResponderTerminationRequest: () =>
+ // Returning false will prevent other views from becoming responder while
+ // the navigation view is the responder (mid-gesture)
+ false,
+ onPanResponderRelease: (event, gesture) => {
+ const {
+ transitionProps: { navigation, position, layout, scene },
+ mode,
+ } = this.props;
+ const { index } = navigation.state;
+ const isVertical = mode === 'modal';
+ const { options } = scene.descriptor;
+ const gestureDirection = options.gestureDirection;
+
+ const gestureDirectionInverted =
+ typeof gestureDirection === 'string'
+ ? gestureDirection === 'inverted'
+ : I18nManager.isRTL;
+
+ if (!this._isResponding) {
+ return;
+ }
+ this._isResponding = false;
+
+ const immediateIndex =
+ this._immediateIndex == null ? index : this._immediateIndex;
+
+ // Calculate animate duration according to gesture speed and moved distance
+ const axisDistance = isVertical
+ ? layout.height.__getValue()
+ : layout.width.__getValue();
+ const movementDirection = gestureDirectionInverted ? -1 : 1;
+ const movedDistance =
+ movementDirection * gesture[isVertical ? 'dy' : 'dx'];
+ const gestureVelocity =
+ movementDirection * gesture[isVertical ? 'vy' : 'vx'];
+ const defaultVelocity = axisDistance / ANIMATION_DURATION;
+ const velocity = Math.max(Math.abs(gestureVelocity), defaultVelocity);
+ const resetDuration = gestureDirectionInverted
+ ? (axisDistance - movedDistance) / velocity
+ : movedDistance / velocity;
+ const goBackDuration = gestureDirectionInverted
+ ? movedDistance / velocity
+ : (axisDistance - movedDistance) / velocity;
+
+ // To asyncronously get the current animated value, we need to run stopAnimation:
+ position.stopAnimation(value => {
+ // If the speed of the gesture release is significant, use that as the indication
+ // of intent
+ if (gestureVelocity < -0.5) {
+ this.props.onGestureCanceled && this.props.onGestureCanceled();
+ this._reset(immediateIndex, resetDuration);
+ return;
+ }
+ if (gestureVelocity > 0.5) {
+ this.props.onGestureFinish && this.props.onGestureFinish();
+ this._goBack(immediateIndex, goBackDuration);
+ return;
+ }
+
+ // Then filter based on the distance the screen was moved. Over a third of the way swiped,
+ // and the back will happen.
+ if (value <= index - POSITION_THRESHOLD) {
+ this.props.onGestureFinish && this.props.onGestureFinish();
+ this._goBack(immediateIndex, goBackDuration);
+ } else {
+ this.props.onGestureCanceled && this.props.onGestureCanceled();
+ this._reset(immediateIndex, resetDuration);
+ }
+ });
+ },
+ });
+
render() {
let floatingHeader = null;
const headerMode = this._getHeaderMode();
@@ -227,148 +407,19 @@ class StackViewLayout extends React.Component {
const { index } = navigation.state;
const isVertical = mode === 'modal';
const { options } = scene.descriptor;
+ const gestureDirection = options.gestureDirection;
- const gestureDirectionInverted = options.gestureDirection === 'inverted';
+ const gestureDirectionInverted =
+ typeof gestureDirection === 'string'
+ ? gestureDirection === 'inverted'
+ : I18nManager.isRTL;
const gesturesEnabled =
typeof options.gesturesEnabled === 'boolean'
? options.gesturesEnabled
: Platform.OS === 'ios';
- const responder = !gesturesEnabled
- ? null
- : PanResponder.create({
- onPanResponderTerminate: () => {
- this._isResponding = false;
- this._reset(index, 0);
- this.props.onGestureCanceled && this.props.onGestureCanceled();
- },
- onPanResponderGrant: () => {
- position.stopAnimation((value: number) => {
- this._isResponding = true;
- this._gestureStartValue = value;
- });
- this.props.onGestureBegin && this.props.onGestureBegin();
- },
- onMoveShouldSetPanResponder: (event, gesture) => {
- if (index !== scene.index) {
- return false;
- }
- const immediateIndex =
- this._immediateIndex == null ? index : this._immediateIndex;
- const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
- const currentDragPosition =
- event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
- const axisLength = isVertical
- ? layout.height.__getValue()
- : layout.width.__getValue();
- const axisHasBeenMeasured = !!axisLength;
-
- // Measure the distance from the touch to the edge of the screen
- const screenEdgeDistance = gestureDirectionInverted
- ? axisLength - (currentDragPosition - currentDragDistance)
- : currentDragPosition - currentDragDistance;
- // Compare to the gesture distance relavant to card or modal
-
- const { options } = scene.descriptor;
-
- const {
- gestureResponseDistance: userGestureResponseDistance = {},
- } = options;
- const gestureResponseDistance = isVertical
- ? userGestureResponseDistance.vertical ||
- GESTURE_RESPONSE_DISTANCE_VERTICAL
- : userGestureResponseDistance.horizontal ||
- GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
- // GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
- if (screenEdgeDistance > gestureResponseDistance) {
- // Reject touches that started in the middle of the screen
- return false;
- }
-
- const hasDraggedEnough =
- Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
-
- const isOnFirstCard = immediateIndex === 0;
- const shouldSetResponder =
- hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
- return shouldSetResponder;
- },
- onPanResponderMove: (event, gesture) => {
- // Handle the moving touches for our granted responder
- const startValue = this._gestureStartValue;
- const axis = isVertical ? 'dy' : 'dx';
- const axisDistance = isVertical
- ? layout.height.__getValue()
- : layout.width.__getValue();
- const currentValue =
- (I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
- ? startValue + gesture[axis] / axisDistance
- : startValue - gesture[axis] / axisDistance;
- const value = clamp(index - 1, currentValue, index);
- position.setValue(value);
- },
- onPanResponderTerminationRequest: () =>
- // Returning false will prevent other views from becoming responder while
- // the navigation view is the responder (mid-gesture)
- false,
- onPanResponderRelease: (event, gesture) => {
- if (!this._isResponding) {
- return;
- }
- this._isResponding = false;
-
- const immediateIndex =
- this._immediateIndex == null ? index : this._immediateIndex;
-
- // Calculate animate duration according to gesture speed and moved distance
- const axisDistance = isVertical
- ? layout.height.__getValue()
- : layout.width.__getValue();
- const movementDirection = gestureDirectionInverted ? -1 : 1;
- const movedDistance =
- movementDirection * gesture[isVertical ? 'dy' : 'dx'];
- const gestureVelocity =
- movementDirection * gesture[isVertical ? 'vy' : 'vx'];
- const defaultVelocity = axisDistance / ANIMATION_DURATION;
- const velocity = Math.max(
- Math.abs(gestureVelocity),
- defaultVelocity
- );
- const resetDuration = gestureDirectionInverted
- ? (axisDistance - movedDistance) / velocity
- : movedDistance / velocity;
- const goBackDuration = gestureDirectionInverted
- ? movedDistance / velocity
- : (axisDistance - movedDistance) / velocity;
-
- // To asyncronously get the current animated value, we need to run stopAnimation:
- position.stopAnimation(value => {
- // If the speed of the gesture release is significant, use that as the indication
- // of intent
- if (gestureVelocity < -0.5) {
- this.props.onGestureCanceled && this.props.onGestureCanceled();
- this._reset(immediateIndex, resetDuration);
- return;
- }
- if (gestureVelocity > 0.5) {
- this.props.onGestureFinish && this.props.onGestureFinish();
- this._goBack(immediateIndex, goBackDuration);
- return;
- }
-
- // Then filter based on the distance the screen was moved. Over a third of the way swiped,
- // and the back will happen.
- if (value <= index - POSITION_THRESHOLD) {
- this.props.onGestureFinish && this.props.onGestureFinish();
- this._goBack(immediateIndex, goBackDuration);
- } else {
- this.props.onGestureCanceled && this.props.onGestureCanceled();
- this._reset(immediateIndex, resetDuration);
- }
- });
- },
- });
+ const responder = !gesturesEnabled ? null : this._panResponder;
const handlers = gesturesEnabled ? responder.panHandlers : {};
const containerStyle = [
diff --git a/src/views/__tests__/Transitioner-test.js b/src/views/__tests__/Transitioner-test.js
new file mode 100644
index 0000000000..bd011b1f9a
--- /dev/null
+++ b/src/views/__tests__/Transitioner-test.js
@@ -0,0 +1,49 @@
+/* eslint react/display-name:0 */
+import * as React from 'react';
+import renderer from 'react-test-renderer';
+import Transitioner from '../Transitioner';
+
+describe('Transitioner', () => {
+ it('should not trigger onTransitionStart and onTransitionEnd when route params are changed', () => {
+ const onTransitionStartCallback = jest.fn();
+ const onTransitionEndCallback = jest.fn();
+
+ const transitionerProps = {
+ configureTransition: (transitionProps, prevTransitionProps) => ({}),
+ navigation: {
+ state: {
+ index: 0,
+ routes: [
+ { key: '1', routeName: 'Foo' },
+ { key: '2', routeName: 'Bar' },
+ ],
+ },
+ goBack: () => false,
+ dispatch: () => false,
+ setParams: () => false,
+ navigate: () => false,
+ },
+ render: () => ,
+ onTransitionStart: onTransitionStartCallback,
+ onTransitionEnd: onTransitionEndCallback,
+ };
+
+ const nextTransitionerProps = {
+ ...transitionerProps,
+ navigation: {
+ ...transitionerProps.navigation,
+ state: {
+ index: 0,
+ routes: [
+ { key: '1', routeName: 'Foo', params: { name: 'Zoom' } },
+ { key: '2', routeName: 'Bar' },
+ ],
+ },
+ },
+ };
+ const component = renderer.create();
+ component.update();
+ expect(onTransitionStartCallback).not.toBeCalled();
+ expect(onTransitionEndCallback).not.toBeCalled();
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 31331a9b2a..0d613db603 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -108,7 +108,7 @@ ansi-gray@^0.1.1:
dependencies:
ansi-wrap "0.1.0"
-ansi-regex@^2.0.0, ansi-regex@^2.1.1:
+ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
@@ -120,7 +120,7 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-ansi-styles@^3.0.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
dependencies:
@@ -1372,34 +1372,20 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
-color-convert@^1.9.0, color-convert@^1.9.1:
+color-convert@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
dependencies:
color-name "^1.1.1"
-color-name@^1.0.0, color-name@^1.1.1:
+color-name@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-color-string@^1.5.2:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
- dependencies:
- color-name "^1.0.0"
- simple-swizzle "^0.2.2"
-
color-support@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
-color@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839"
- dependencies:
- color-convert "^1.9.1"
- color-string "^1.5.2"
-
combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
@@ -1594,13 +1580,6 @@ create-react-context@^0.2.1:
fbjs "^0.8.0"
gud "^1.0.0"
-create-react-context@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
- dependencies:
- fbjs "^0.8.0"
- gud "^1.0.0"
-
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -1700,10 +1679,6 @@ deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
-deepmerge@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102"
-
default-require-extensions@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
@@ -1915,7 +1890,7 @@ escodegen@^1.9.0:
optionalDependencies:
source-map "~0.6.1"
-eslint-config-prettier@^2.3.0:
+eslint-config-prettier@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz#5ecd65174d486c22dff389fe036febf502d468a3"
dependencies:
@@ -1962,7 +1937,7 @@ eslint-plugin-jsx-a11y@^6.0.2:
emoji-regex "^6.1.0"
jsx-ast-utils "^2.0.0"
-eslint-plugin-prettier@^2.1.2:
+eslint-plugin-prettier@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7"
dependencies:
@@ -1989,7 +1964,50 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
-eslint@^4.2.0, eslint@^4.5.0:
+eslint@^4.0.0:
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
+ dependencies:
+ ajv "^5.3.0"
+ babel-code-frame "^6.22.0"
+ chalk "^2.1.0"
+ concat-stream "^1.6.0"
+ cross-spawn "^5.1.0"
+ debug "^3.1.0"
+ doctrine "^2.1.0"
+ eslint-scope "^3.7.1"
+ eslint-visitor-keys "^1.0.0"
+ espree "^3.5.4"
+ esquery "^1.0.0"
+ esutils "^2.0.2"
+ file-entry-cache "^2.0.0"
+ functional-red-black-tree "^1.0.1"
+ glob "^7.1.2"
+ globals "^11.0.1"
+ ignore "^3.3.3"
+ imurmurhash "^0.1.4"
+ inquirer "^3.0.6"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.9.1"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.3.0"
+ lodash "^4.17.4"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.1"
+ natural-compare "^1.4.0"
+ optionator "^0.8.2"
+ path-is-inside "^1.0.2"
+ pluralize "^7.0.0"
+ progress "^2.0.0"
+ regexpp "^1.0.1"
+ require-uncached "^1.0.3"
+ semver "^5.3.0"
+ strip-ansi "^4.0.0"
+ strip-json-comments "~2.0.1"
+ table "4.0.2"
+ text-table "~0.2.0"
+
+eslint@^4.2.0:
version "4.18.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.2.tgz#0f81267ad1012e7d2051e186a9004cc2267b8d45"
dependencies:
@@ -2031,7 +2049,7 @@ eslint@^4.2.0, eslint@^4.5.0:
table "4.0.2"
text-table "~0.2.0"
-espree@^3.5.2:
+espree@^3.5.2, espree@^3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
dependencies:
@@ -2890,10 +2908,6 @@ is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-is-arrayish@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd"
-
is-binary-path@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -3903,6 +3917,10 @@ lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
+lodash.unescape@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
+
lodash@^3.5.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@@ -4688,30 +4706,26 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-prettier-eslint@^6.4.2:
- version "6.4.3"
- resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-6.4.3.tgz#8335b7a8bd50d01879a9d505bc0b9208caa9ecfc"
+prettier-eslint@^8.8.1:
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-8.8.1.tgz#38505163274742f2a0b31653c39e40f37ebd07da"
dependencies:
+ babel-runtime "^6.26.0"
common-tags "^1.4.0"
dlv "^1.1.0"
- eslint "^4.5.0"
+ eslint "^4.0.0"
indent-string "^3.2.0"
lodash.merge "^4.6.0"
loglevel-colored-level-prefix "^1.0.0"
- prettier "^1.6.0"
- pretty-format "^20.0.3"
+ prettier "^1.7.0"
+ pretty-format "^22.0.3"
require-relative "^0.8.7"
+ typescript "^2.5.1"
+ typescript-eslint-parser "^11.0.0"
-prettier@^1.5.3, prettier@^1.6.0:
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
-
-pretty-format@^20.0.3:
- version "20.0.3"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14"
- dependencies:
- ansi-regex "^2.1.1"
- ansi-styles "^3.0.0"
+prettier@^1.12.1, prettier@^1.7.0:
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325"
pretty-format@^21.2.1:
version "21.2.1"
@@ -4720,6 +4734,13 @@ pretty-format@^21.2.1:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
+pretty-format@^22.0.3:
+ version "22.4.3"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
+ dependencies:
+ ansi-regex "^3.0.0"
+ ansi-styles "^3.2.0"
+
pretty-format@^22.4.0:
version "22.4.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.0.tgz#237b1f7e1c50ed03bc65c03ccc29d7c8bb7beb94"
@@ -4840,7 +4861,7 @@ react-lifecycles-compat@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.0.2.tgz#551d8b1d156346e5fcf30ffac9b32ce3f78b8850"
-react-lifecycles-compat@^3.0.2:
+react-lifecycles-compat@^3:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz#7279047275bd727a912e25f734c0559527e84eff"
@@ -4860,16 +4881,6 @@ react-native-drawer-layout@1.3.2:
dependencies:
react-native-dismiss-keyboard "1.0.0"
-react-native-paper@^1.4.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/react-native-paper/-/react-native-paper-1.4.1.tgz#81df909c14d9ae78274662df97c3b35224aa39a9"
- dependencies:
- color "^2.0.1"
- create-react-context "^0.2.2"
- deepmerge "^2.1.0"
- hoist-non-react-statics "^2.5.0"
- react-lifecycles-compat "^3.0.2"
-
react-native-safe-area-view@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.7.0.tgz#38f5ab9368d6ef9e5d18ab64212938af3ec39421"
@@ -4955,20 +4966,19 @@ react-native@^0.52.0:
xmldoc "^0.4.0"
yargs "^9.0.0"
-react-navigation-deprecated-tab-navigator@1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.1.0.tgz#58945c1c4c7c21b54954e814e8721e98423df75d"
+react-navigation-deprecated-tab-navigator@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.2.0.tgz#e0d969c196dcd3a4a440770a7bd97fa058eb4aaf"
dependencies:
react-native-tab-view "^0.0.74"
-react-navigation-tabs@0.1.0-alpha.7:
- version "0.1.0-alpha.7"
- resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.1.0-alpha.7.tgz#bcea5b031a3db3b4230be63f17fed721bf78e198"
+react-navigation-tabs@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.2.0.tgz#d25be64e01a728c893a93cfeee6b176ab74d67de"
dependencies:
hoist-non-react-statics "^2.5.0"
prop-types "^15.6.0"
react-lifecycles-compat "^1.0.2"
- react-native-paper "^1.4.0"
react-native-safe-area-view "^0.7.0"
react-native-tab-view "~0.0.77"
@@ -5106,6 +5116,10 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
+regexpp@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab"
+
regexpu-core@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
@@ -5397,6 +5411,10 @@ sax@~1.1.1:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
+semver@5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
+
send@0.13.2:
version "0.13.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.13.2.tgz#765e7607c8055452bba6f0b052595350986036de"
@@ -5512,12 +5530,6 @@ simple-plist@^0.2.1:
bplist-parser "0.1.1"
plist "2.0.1"
-simple-swizzle@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
- dependencies:
- is-arrayish "^0.3.1"
-
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -5975,6 +5987,17 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+typescript-eslint-parser@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-11.0.0.tgz#37dba6a0130dd307504aa4b4b21b0d3dc7d4e9f2"
+ dependencies:
+ lodash.unescape "4.0.1"
+ semver "5.4.1"
+
+typescript@^2.5.1:
+ version "2.8.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170"
+
ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"