Fix eslint issues and turn on prettier by default (#1195)

* Automatically generate prop-types from Flow

* Remove propTypes usage

* Fix flow

* Modify some eslint settings

* Fix flowtype

* Lint tweaks

* use prop-types pkg

* Run prettier

* Fix flow

* Fix few lint issues

* Make eslint pass

* Run lint on tests

* Fix flow

* Fixes

* Alphabetical

* Trailing comma: ES5 for website compat, also fix config/paths

* Apply eslint --fix only to src now

* Fix missing transitionconfig

* Update TypeDefinition.js

* New stuff

* Unstage website and examples

* reformat code

* Update circle.yml
This commit is contained in:
Mike Grabowski 2017-04-24 14:01:22 +02:00 committed by Satyajit Sahoo
parent 23e310742c
commit bbe9caff06
57 changed files with 1983 additions and 1264 deletions

View File

@ -1,13 +1,27 @@
{
"extends": "airbnb",
"extends": [
"airbnb",
"prettier",
"prettier/flowtype",
"prettier/react"
],
"parser": "babel-eslint",
"plugins": [
"flowtype"
"flowtype",
"prettier"
],
"env": {
"jasmine": true
},
"globals": {
"ReactClass": true
},
"rules": {
"prettier/prettier": ["error", {
"trailingComma": "all",
"singleQuote": true
}],
"no-underscore-dangle": 0,
"no-use-before-define": 0,
"no-unused-expressions": 0,
@ -36,11 +50,6 @@
2,
"boolean"
],
"flowtype/define-flow-type": 1,
"flowtype/generic-spacing": [
2,
"never"
],
"flowtype/no-weak-types": 1,
"flowtype/require-parameter-type": 2,
"flowtype/require-return-type": [
@ -51,26 +60,6 @@
}
],
"flowtype/require-valid-file-annotation": 2,
"flowtype/semi": [
2,
"always"
],
"flowtype/space-after-type-colon": [
2,
"always"
],
"flowtype/space-before-generic-bracket": [
2,
"never"
],
"flowtype/space-before-type-colon": [
2,
"never"
],
"flowtype/union-intersection-spacing": [
2,
"always"
],
"flowtype/use-flow-type": 1,
"flowtype/valid-syntax": 1
},

View File

@ -17,6 +17,3 @@ deployment:
commands:
- yarn run build-docs
- ./scripts/deploy-website.sh
test:
pre:
- yarn run flow

View File

@ -27,8 +27,10 @@
"start": "node ./node_modules/react-native/local-cli/cli.js start --config ./rn-cli.config.js",
"run-playground-ios": "cd examples/NavigationPlayground && react-native run-ios",
"run-playground-android": "cd examples/NavigationPlayground && react-native run-android",
"test": "jest",
"test": "npm run lint && npm run flow && npm run jest",
"jest": "jest",
"lint": "eslint src",
"format": "eslint --fix src",
"flow": "flow",
"prepublish": "npm run clean && npm run build"
},
@ -50,12 +52,15 @@
"babel-preset-stage-1": "^6.16.0",
"eslint": "^3.17.1",
"eslint-config-airbnb": "^14.1.0",
"eslint-config-prettier": "^1.5.0",
"eslint-plugin-flowtype": "^2.30.3",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-prettier": "^2.0.1",
"eslint-plugin-react": "^6.10.0",
"flow-bin": "^0.40.0",
"jest": "^19.0.2",
"prettier": "^0.22.0",
"react": "16.0.0-alpha.6",
"react-native": "^0.43.2",
"react-native-vector-icons": "^3.0.0",

View File

@ -7,10 +7,11 @@ const RESET = namespacedAction('RESET');
const SET_PARAMS = namespacedAction('SET_PARAMS');
const URI = namespacedAction('URI');
const createAction = (type: string) => (payload: object = {}) => ({
type,
...payload,
});
const createAction = (type: string) =>
(payload: Object = {}) => ({
type,
...payload,
});
const back = createAction(BACK);
const init = createAction(INIT);
@ -28,19 +29,23 @@ const deprecatedActionMap = {
Uri: URI,
};
const mapDeprecatedActionAndWarn = (action: object) => {
const mapDeprecatedActionAndWarn = (action: Object) => {
const mappedType = deprecatedActionMap[action.type];
if (!mappedType) { return action; }
if (!mappedType) {
return action;
}
console.warn([
`The action type '${action.type}' has been renamed to '${mappedType}'.`,
`'${action.type}' will continue to work while in beta but will be removed`,
'in the first major release. Moving forward, you should use the',
'action constants and action creators exported by this library in',
"the 'actions' object.",
'See https://github.com/react-community/react-navigation/pull/120 for',
'more details.',
].join(' '));
console.warn(
[
`The action type '${action.type}' has been renamed to '${mappedType}'.`,
`'${action.type}' will continue to work while in beta but will be removed`,
'in the first major release. Moving forward, you should use the',
'action constants and action creators exported by this library in',
"the 'actions' object.",
'See https://github.com/react-community/react-navigation/pull/120 for',
'more details.',
].join(' '),
);
return {
...action,

View File

@ -1,12 +1,5 @@
/* @flow */
import {
BackAndroid,
Linking,
} from 'react-native';
export {
BackAndroid,
Linking,
};
import { BackAndroid, Linking } from 'react-native';
export { BackAndroid, Linking };

View File

@ -2,10 +2,7 @@
import invariant from 'fbjs/lib/invariant';
import type {
NavigationRoute,
NavigationState,
} from './TypeDefinition';
import type { NavigationRoute, NavigationState } from './TypeDefinition';
/**
* Utilities to perform atomic operation with navigate state and routes.
@ -16,12 +13,11 @@ import type {
* ```
*/
const StateUtils = {
/**
* Gets a route by key. If the route isn't found, returns `null`.
*/
get(state: NavigationState, key: string): ?NavigationRoute {
return state.routes.find(route => route.key === key) || null;
return state.routes.find((route: *) => route.key === key) || null;
},
/**
@ -29,7 +25,7 @@ const StateUtils = {
* routes of the navigation state, or -1 if it is not present.
*/
indexOf(state: NavigationState, key: string): number {
return state.routes.map(route => route.key).indexOf(key);
return state.routes.map((route: *) => route.key).indexOf(key);
},
/**
@ -37,7 +33,7 @@ const StateUtils = {
* routes of the navigation state.
*/
has(state: NavigationState, key: string): boolean {
return !!state.routes.some(route => route.key === key);
return !!state.routes.some((route: *) => route.key === key);
},
/**
@ -185,7 +181,7 @@ const StateUtils = {
const nextIndex: number = index === undefined ? routes.length - 1 : index;
if (state.routes.length === routes.length && state.index === nextIndex) {
const compare = (route, ii) => routes[ii] === route;
const compare = (route: *, ii: *) => routes[ii] === route;
if (state.routes.every(compare)) {
return state;
}

View File

@ -11,7 +11,9 @@ export type HeaderMode = 'float' | 'screen' | 'none';
export type HeaderProps = NavigationSceneRendererProps & {
mode: HeaderMode,
router: NavigationRouter<NavigationState, NavigationAction, NavigationStackScreenOptions>,
getScreenDetails: NavigationScene => NavigationScreenDetails<NavigationStackScreenOptions>,
getScreenDetails: (
NavigationScene,
) => NavigationScreenDetails<NavigationStackScreenOptions>,
};
/**
@ -75,18 +77,21 @@ export type NavigationRouter<State, Action, Options> = {
* an optional previous state. When the action is considered handled but the
* state is unchanged, the output state is null.
*/
getStateForAction: (
action: Action,
lastState: ?State,
) => ?State,
getStateForAction: (action: Action, lastState: ?State) => ?State,
/**
* Maps a URI-like string to an action. This can be mapped to a state
* using `getStateForAction`.
*/
getActionForPathAndParams: (path: string, params?: NavigationParams) => ?Action,
getActionForPathAndParams: (
path: string,
params?: NavigationParams,
) => ?Action,
getPathAndParamsForState: (state: State) => {path: string, params?: NavigationParams},
getPathAndParamsForState: (state: State) => {
path: string,
params?: NavigationParams,
},
getComponentForRouteName: (routeName: string) => NavigationComponent,
@ -100,15 +105,23 @@ export type NavigationRouter<State, Action, Options> = {
*
* {routeName: 'Foo', key: '123'}
*/
getScreenOptions: NavigationScreenOptionsGetter<Options, Action>
getScreenOptions: NavigationScreenOptionsGetter<Options, Action>,
};
export type NavigationScreenOption<T> =
| T
| (navigation: NavigationScreenProp<NavigationRoute, NavigationAction>,
config: T) => T;
| ((
navigation: NavigationScreenProp<NavigationRoute, NavigationAction>,
config: T,
) => T);
export type Style = { [key: string]: any } | number | false | null | void | Array<Style>;
export type Style =
| { [key: string]: any }
| number
| false
| null
| void
| Array<Style>;
export type NavigationScreenDetails<T> = {
options: T,
@ -125,12 +138,17 @@ export type NavigationScreenConfigProps = {
screenProps: Object,
};
export type NavigationScreenConfig<Options> = Options
| NavigationScreenConfigProps & {
navigationOptions: NavigationScreenProp<NavigationRoute, NavigationAction>
} => Options;
export type NavigationScreenConfig<Options> =
| Options
| (NavigationScreenConfigProps & ((
{
navigationOptions: NavigationScreenProp<NavigationRoute, NavigationAction>,
},
) => Options));
export type NavigationComponent = NavigationScreenComponent<*, *> | NavigationNavigator<*, *, *, *>;
export type NavigationComponent =
| NavigationScreenComponent<*, *>
| NavigationNavigator<*, *, *, *>;
export type NavigationScreenComponent<T, Options> = ReactClass<T> & {
navigationOptions?: NavigationScreenConfig<Options>,
@ -192,7 +210,7 @@ export type NavigationStackViewConfig = {
headerComponent?: ReactClass<HeaderProps<*>>,
cardStyle?: Style,
onTransitionStart?: () => void,
onTransitionEnd?: () => void
onTransitionEnd?: () => void,
};
export type NavigationStackScreenOptions = NavigationScreenOptions & {
@ -238,13 +256,13 @@ export type NavigationRouteConfig<T> = T & {
path?: string,
};
export type NavigationScreenRouteConfig = NavigationScreenRouteConfig<{
// React component or navigator for this route */
screen: NavigationComponent,
} | {
// React component to lazily require and render for this route */
getScreen: () => NavigationComponent,
}>;
export type NavigationScreenRouteConfig =
| {
screen: NavigationComponent,
}
| {
getScreen: () => NavigationComponent,
};
export type NavigationPathsConfig = {
[routeName: string]: string,
@ -261,18 +279,31 @@ export type NavigationTabRouterConfig = {
};
export type NavigationTabScreenOptions = NavigationScreenOptions & {
tabBarIcon?: React.Element<*>
| (options: { tintColor: ?string, focused: boolean }) => ?React.Element<*>,
tabBarLabel?: string | React.Element<*>
| (options: { tintColor: ?string, focused: boolean }) => ?React.Element<*>,
tabBarIcon?:
| React.Element<*>
| ((
options: { tintColor: ?string, focused: boolean },
) => ?React.Element<*>),
tabBarLabel?:
| string
| React.Element<*>
| ((
options: { tintColor: ?string, focused: boolean },
) => ?React.Element<*>),
tabBarVisible?: boolean,
};
export type NavigationDrawerScreenOptions = NavigationScreenOptions & {
drawerIcon?: React.Element<*>
| (options: { tintColor: ?string, focused: boolean }) => ?React.Element<*>,
drawerLabel?: React.Element<*>
| (options: { tintColor: ?string, focused: boolean }) => ?React.Element<*>,
drawerIcon?:
| React.Element<*>
| ((
options: { tintColor: ?string, focused: boolean },
) => ?React.Element<*>),
drawerLabel?:
| React.Element<*>
| ((
options: { tintColor: ?string, focused: boolean },
) => ?React.Element<*>),
};
export type NavigationRouteConfigMap = {
@ -290,7 +321,11 @@ export type NavigationScreenProp<S, A> = {
state: S,
dispatch: NavigationDispatch<A>,
goBack: (routeKey?: ?string) => boolean,
navigate: (routeName: string, params?: NavigationParams, action?: NavigationAction) => boolean,
navigate: (
routeName: string,
params?: NavigationParams,
action?: NavigationAction,
) => boolean,
setParams: (newParams: NavigationParams) => boolean,
};
@ -381,10 +416,10 @@ export type NavigationStyleInterpolator = (
export type LayoutEvent = {
nativeEvent: {
layout: {
x: number;
y: number;
width: number;
height: number;
x: number,
y: number,
width: number,
height: number,
},
};
},
};

View File

@ -7,31 +7,55 @@ describe('actions', () => {
it('exports back action and type', () => {
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
expect(NavigationActions.back(data)).toEqual({ type: NavigationActions.BACK, ...data });
expect(NavigationActions.back(data)).toEqual({
type: NavigationActions.BACK,
...data,
});
});
it('exports init action and type', () => {
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
expect(NavigationActions.init(data)).toEqual({ type: NavigationActions.INIT, ...data });
expect(NavigationActions.init(data)).toEqual({
type: NavigationActions.INIT,
...data,
});
});
it('exports navigate action and type', () => {
expect(NavigationActions.navigate()).toEqual({ type: NavigationActions.NAVIGATE });
expect(NavigationActions.navigate(data)).toEqual({ type: NavigationActions.NAVIGATE, ...data });
expect(NavigationActions.navigate()).toEqual({
type: NavigationActions.NAVIGATE,
});
expect(NavigationActions.navigate(data)).toEqual({
type: NavigationActions.NAVIGATE,
...data,
});
});
it('exports reset action and type', () => {
expect(NavigationActions.reset()).toEqual({ type: NavigationActions.RESET });
expect(NavigationActions.reset(data)).toEqual({ type: NavigationActions.RESET, ...data });
expect(NavigationActions.reset()).toEqual({
type: NavigationActions.RESET,
});
expect(NavigationActions.reset(data)).toEqual({
type: NavigationActions.RESET,
...data,
});
});
it('exports setParams action and type', () => {
expect(NavigationActions.setParams()).toEqual({ type: NavigationActions.SET_PARAMS });
expect(NavigationActions.setParams(data)).toEqual({ type: NavigationActions.SET_PARAMS, ...data });
expect(NavigationActions.setParams()).toEqual({
type: NavigationActions.SET_PARAMS,
});
expect(NavigationActions.setParams(data)).toEqual({
type: NavigationActions.SET_PARAMS,
...data,
});
});
it('exports uri action and type', () => {
expect(NavigationActions.uri()).toEqual({ type: NavigationActions.URI });
expect(NavigationActions.uri(data)).toEqual({ type: NavigationActions.URI, ...data });
expect(NavigationActions.uri(data)).toEqual({
type: NavigationActions.URI,
...data,
});
});
});

View File

@ -8,19 +8,28 @@ describe('StateUtils', () => {
// Getters
it('gets route', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }] };
expect(NavigationStateUtils.get(state, 'a')).toEqual({ key: 'a', routeName });
expect(NavigationStateUtils.get(state, 'a')).toEqual({
key: 'a',
routeName,
});
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
});
it('gets route index', () => {
const state = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
});
it('has a route', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
});
@ -28,18 +37,27 @@ describe('StateUtils', () => {
// Push
it('pushes a route', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(newState);
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
newState,
);
});
it('does not push duplicated route', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }] };
expect(() => NavigationStateUtils.push(state, { key: 'a', routeName })).toThrow();
expect(() =>
NavigationStateUtils.push(state, { key: 'a', routeName })).toThrow();
});
// Pop
it('pops route', () => {
const state = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = { index: 0, routes: [{ key: 'a', routeName }] };
expect(NavigationStateUtils.pop(state)).toEqual(newState);
});
@ -51,88 +69,127 @@ describe('StateUtils', () => {
// Jump
it('jumps to new index', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
});
it('throws if jumps to invalid index', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
});
it('jumps to new key', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
});
it('throws if jumps to invalid key', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
});
it('move backwards', () => {
const state = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.back(state)).toEqual(newState);
expect(NavigationStateUtils.back(newState)).toBe(newState);
});
it('move forwards', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.forward(state)).toEqual(newState);
expect(NavigationStateUtils.forward(newState)).toBe(newState);
});
// Replace
it('Replaces by key', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'c', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
};
expect(
NavigationStateUtils.replaceAt(
state,
'b',
{ key: 'c', routeName },
)
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName }),
).toEqual(newState);
});
it('Replaces by index', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'a', routeName }, { key: 'c', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
};
expect(
NavigationStateUtils.replaceAtIndex(
state,
1,
{ key: 'c', routeName },
)
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName }),
).toEqual(newState);
});
it('Returns the state if index matches the route', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(
NavigationStateUtils.replaceAtIndex(
state,
1,
state.routes[1],
)
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1]),
).toEqual(state);
});
// Reset
it('Resets routes', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 1, routes: [{ key: 'x', routeName }, { key: 'y', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
};
expect(
NavigationStateUtils.reset(
state,
[{ key: 'x', routeName }, { key: 'y', routeName }],
)
NavigationStateUtils.reset(state, [
{ key: 'x', routeName },
{ key: 'y', routeName },
]),
).toEqual(newState);
expect(() => {
@ -141,18 +198,28 @@ describe('StateUtils', () => {
});
it('Resets routes with index', () => {
const state = { index: 0, routes: [{ key: 'a', routeName }, { key: 'b', routeName }] };
const newState = { index: 0, routes: [{ key: 'x', routeName }, { key: 'y', routeName }] };
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 0,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
};
expect(
NavigationStateUtils.reset(
state,
[{ key: 'x', routeName }, { key: 'y', routeName }],
0,
)
),
).toEqual(newState);
expect(() => {
NavigationStateUtils.reset(state, [{ key: 'x', routeName }, { key: 'y', routeName }], 100);
NavigationStateUtils.reset(
state,
[{ key: 'x', routeName }, { key: 'y', routeName }],
100,
);
}).toThrow();
});
});

View File

@ -5,31 +5,46 @@ import addNavigationHelpers from '../addNavigationHelpers';
describe('addNavigationHelpers', () => {
it('handles Back action', () => {
const mockedDispatch = jest.fn(() => false).mockImplementationOnce(() => true);
expect(addNavigationHelpers({
state: { key: 'A', routeName: 'Home' },
dispatch: mockedDispatch,
}).goBack('A')).toEqual(true);
expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK, key: 'A' });
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'A', routeName: 'Home' },
dispatch: mockedDispatch,
}).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,
}).goBack()).toEqual(true);
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
}).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,
}).navigate('Profile', { name: 'Matt' })).toEqual(true);
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
}).navigate('Profile', { name: 'Matt' }),
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.NAVIGATE,
params: { name: 'Matt' },
@ -39,11 +54,15 @@ describe('addNavigationHelpers', () => {
});
it('handles SetParams action', () => {
const mockedDispatch = jest.fn(() => false).mockImplementationOnce(() => true);
expect(addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
}).setParams({ notificationsEnabled: 'yes' })).toEqual(true);
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
}).setParams({ notificationsEnabled: 'yes' }),
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.SET_PARAMS,
key: 'B',

View File

@ -12,30 +12,38 @@ import type {
import NavigationActions from './NavigationActions';
export default function<S: *> (navigation: NavigationProp<S, NavigationAction>) {
export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {
return {
...navigation,
goBack: (key?: ?string): boolean => navigation.dispatch(NavigationActions.back({
key: key === undefined ? navigation.state.key : key,
})),
goBack: (key?: ?string): boolean =>
navigation.dispatch(
NavigationActions.back({
key: key === undefined ? navigation.state.key : key,
}),
),
navigate: (
routeName: string,
params?: NavigationParams,
action?: NavigationAction): boolean =>
navigation.dispatch(NavigationActions.navigate({
action?: NavigationAction,
): boolean =>
navigation.dispatch(
NavigationActions.navigate({
routeName,
params,
action,
})),
}),
),
/**
* 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: NavigationParams): boolean =>
navigation.dispatch(NavigationActions.setParams({
params,
key: navigation.state.key,
})),
navigation.dispatch(
NavigationActions.setParams({
params,
key: navigation.state.key,
}),
),
};
}

View File

@ -2,10 +2,7 @@
import React from 'react';
import invariant from 'fbjs/lib/invariant';
import {
BackAndroid,
Linking,
} from './PlatformHelpers';
import { BackAndroid, Linking } from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
@ -19,7 +16,11 @@ import type {
type NavigationContainerProps = {
uriPrefix?: string,
onNavigationStateChange?: (NavigationState, NavigationState, NavigationAction) => void,
onNavigationStateChange?: (
NavigationState,
NavigationState,
NavigationAction,
) => void,
};
type Props<T> = NavigationContainerProps & NavigationNavigatorProps<T>;
@ -75,7 +76,9 @@ export default function createNavigationContainer<T: *>(
}
const {
navigation, screenProps, navigationOptions,
navigation,
screenProps,
navigationOptions,
...containerProps
} = props;
@ -84,9 +87,9 @@ export default function createNavigationContainer<T: *>(
invariant(
keys.length === 0,
'This navigator has both navigation and container props, so it is ' +
`unclear if it should own its own state. Remove props: "${keys.join(', ')}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.',
`unclear if it should own its own state. Remove props: "${keys.join(', ')}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.',
);
}
@ -120,8 +123,8 @@ export default function createNavigationContainer<T: *>(
action: NavigationAction,
) {
if (
typeof this.props.onNavigationStateChange === 'undefined'
&& this._isStateful()
typeof this.props.onNavigationStateChange === 'undefined' &&
this._isStateful()
) {
/* eslint-disable no-console */
if (console.group) {
@ -131,7 +134,11 @@ export default function createNavigationContainer<T: *>(
console.log('Last State: ', prevNav);
console.groupEnd();
} else {
console.log('Navigation Dispatch: ', { action, newState: nav, lastState: prevNav });
console.log('Navigation Dispatch: ', {
action,
newState: nav,
lastState: prevNav,
});
}
/* eslint-enable no-console */
return;
@ -151,16 +158,16 @@ export default function createNavigationContainer<T: *>(
return;
}
this.subs = BackAndroid.addEventListener(
'backPress',
() => this.dispatch(NavigationActions.back()),
);
this.subs = BackAndroid.addEventListener('backPress', () =>
this.dispatch(NavigationActions.back()));
Linking.addEventListener('url', ({ url }: { url: string }) => {
this._handleOpenURL(url);
});
Linking.getInitialURL().then((url: string) => url && this._handleOpenURL(url));
Linking.getInitialURL().then(
(url: string) => url && this._handleOpenURL(url),
);
}
componentWillUnmount() {
@ -175,7 +182,8 @@ export default function createNavigationContainer<T: *>(
}
const nav = Component.router.getStateForAction(action, state.nav);
if (nav && nav !== state.nav) {
this.setState({ nav }, () => this._onNavigationStateChange(state.nav, nav, action));
this.setState({ nav }, () =>
this._onNavigationStateChange(state.nav, nav, action));
return true;
}
return false;
@ -194,15 +202,9 @@ export default function createNavigationContainer<T: *>(
}
navigation = this._navigation;
}
return (
<Component
{...this.props}
navigation={navigation}
/>
);
return <Component {...this.props} navigation={navigation} />;
}
}
return NavigationContainer;
}

View File

@ -1,10 +1,7 @@
/* @flow */
import React from 'react';
import {
Dimensions,
Platform,
} from 'react-native';
import { Dimensions, Platform } from 'react-native';
import createNavigator from './createNavigator';
import createNavigationContainer from '../createNavigationContainer';
@ -29,7 +26,8 @@ const DefaultDrawerConfig = {
* Default drawer width is screen width - header width
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
drawerWidth: Dimensions.get('window').width - (Platform.OS === 'android' ? 56 : 64),
drawerWidth: Dimensions.get('window').width -
(Platform.OS === 'android' ? 56 : 64),
contentComponent: DrawerView.Items,
drawerPosition: 'left',
};
@ -49,31 +47,41 @@ const DrawerNavigator = (
} = mergedConfig;
const contentRouter = TabRouter(routeConfigs, tabsConfig);
const drawerRouter = TabRouter({
DrawerClose: {
screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
(props: *) => <DrawerScreen {...props} />,
),
},
DrawerOpen: {
screen: () => null,
},
}, {
initialRouteName: 'DrawerClose',
});
const navigator = createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
(props: *) => (
<DrawerView
{...props}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
/>
),
const drawerRouter = TabRouter(
{
DrawerClose: {
screen: createNavigator(
contentRouter,
routeConfigs,
config,
NavigatorTypes.DRAWER,
)((props: *) => <DrawerScreen {...props} />),
},
DrawerOpen: {
screen: () => null,
},
},
{
initialRouteName: 'DrawerClose',
},
);
const navigator = createNavigator(
drawerRouter,
routeConfigs,
config,
NavigatorTypes.DRAWER,
)((props: *) => (
<DrawerView
{...props}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
/>
));
return createNavigationContainer(navigator, containerConfig);
};

View File

@ -1,10 +1,9 @@
/* @flow */
export type NavigatorType = (
'react-navigation/STACK' |
'react-navigation/TABS' |
'react-navigation/DRAWER'
);
export type NavigatorType =
| 'react-navigation/STACK'
| 'react-navigation/TABS'
| 'react-navigation/DRAWER';
const STACK = 'react-navigation/STACK';
const TABS = 'react-navigation/TABS';

View File

@ -42,19 +42,23 @@ export default (
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
const navigator = createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)(
(props: *) => (
<CardStackTransitioner
{...props}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
/>
),
);
const navigator = createNavigator(
router,
routeConfigMap,
stackConfig,
NavigatorTypes.STACK,
)((props: *) => (
<CardStackTransitioner
{...props}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
/>
));
return createNavigationContainer(navigator, stackConfig.containerOptions);
};

View File

@ -38,19 +38,23 @@ const TabNavigator = (
} = mergedConfig;
const router = TabRouter(routeConfigs, tabsConfig);
const navigator = createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)(
(props: *) => (
<TabView
{...props}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
lazyLoad={lazyLoad}
/>
),
);
const navigator = createNavigator(
router,
routeConfigs,
config,
NavigatorTypes.STACK,
)((props: *) => (
<TabView
{...props}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
lazyLoad={lazyLoad}
/>
));
return createNavigationContainer(navigator, tabsConfig.containerOptions);
};
@ -93,7 +97,9 @@ const Presets = {
TabNavigator.Presets = {
iOSBottomTabs: Presets.iOSBottomTabs,
AndroidTopTabs: Presets.AndroidTopTabs,
Default: Platform.OS === 'ios' ? Presets.iOSBottomTabs : Presets.AndroidTopTabs,
Default: Platform.OS === 'ios'
? Presets.iOSBottomTabs
: Presets.AndroidTopTabs,
};
export default TabNavigator;

View File

@ -4,20 +4,22 @@ import React from 'react';
import type {
NavigationRouter,
NavigationRoute,
NavigationNavigator,
NavigationNavigatorProps,
NavigationRouteConfigMap,
} from '../TypeDefinition';
import type {
NavigatorType,
} from './NavigatorTypes';
import type { NavigatorType } from './NavigatorTypes';
/**
* Creates a navigator based on a router and a view that renders the screens.
*/
const createNavigator = (router: NavigationRouter<*, *, *>, routeConfigs: NavigationRouteConfigMap, navigatorConfig: any, navigatorType: NavigatorType) =>
const createNavigator = (
router: NavigationRouter<*, *, *>,
routeConfigs: NavigationRouteConfigMap,
navigatorConfig: any,
navigatorType: NavigatorType,
) =>
(View: NavigationNavigator<*, *, *, *>) => {
class Navigator extends React.Component {
props: NavigationNavigatorProps<*>;
@ -29,12 +31,7 @@ const createNavigator = (router: NavigationRouter<*, *, *>, routeConfigs: Naviga
static navigatorType = navigatorType;
render() {
return (
<View
{...this.props}
router={router}
/>
);
return <View {...this.props} router={router} />;
}
}

View File

@ -2,33 +2,70 @@
* @noflow - get/set properties not yet supported by flow. also `...require(x)` is broken #6560135
*/
module.exports = {
/* eslint global-require: 0 */
module.exports = {
// Core
get createNavigationContainer() { return require('./createNavigationContainer').default; },
get StateUtils() { return require('./StateUtils').default; },
get addNavigationHelpers() { return require('./addNavigationHelpers').default; },
get NavigationActions() { return require('./NavigationActions').default; },
get createNavigationContainer() {
return require('./createNavigationContainer').default;
},
get StateUtils() {
return require('./StateUtils').default;
},
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() { return require('./navigators/createNavigator').default; },
get StackNavigator() { return require('./navigators/StackNavigator').default; },
get TabNavigator() { return require('./navigators/TabNavigator').default; },
get DrawerNavigator() { return require('./navigators/DrawerNavigator').default; },
get createNavigator() {
return require('./navigators/createNavigator').default;
},
get StackNavigator() {
return require('./navigators/StackNavigator').default;
},
get TabNavigator() {
return require('./navigators/TabNavigator').default;
},
get DrawerNavigator() {
return require('./navigators/DrawerNavigator').default;
},
// Routers
get StackRouter() { return require('./routers/StackRouter').default; },
get TabRouter() { return require('./routers/TabRouter').default; },
get StackRouter() {
return require('./routers/StackRouter').default;
},
get TabRouter() {
return require('./routers/TabRouter').default;
},
// Views
get Transitioner() { return require('./views/Transitioner').default; },
get CardStack() { return require('./views/CardStack').default; },
get Card() { return require('./views/Card').default; },
get Header() { return require('./views/Header').default; },
get HeaderBackButton() { return require('./views/HeaderBackButton').default; },
get DrawerView() { return require('./views/Drawer/DrawerView').default; },
get TabView() { return require('./views/TabView/TabView').default; },
get Transitioner() {
return require('./views/Transitioner').default;
},
get CardStack() {
return require('./views/CardStack').default;
},
get Card() {
return require('./views/Card').default;
},
get Header() {
return require('./views/Header').default;
},
get HeaderBackButton() {
return require('./views/HeaderBackButton').default;
},
get DrawerView() {
return require('./views/Drawer/DrawerView').default;
},
get TabView() {
return require('./views/TabView/TabView').default;
},
// HOCs
get withNavigation() { return require('./views/withNavigation').default; },
get withNavigation() {
return require('./views/withNavigation').default;
},
};

View File

@ -2,21 +2,38 @@
* @noflow - get/set properties not yet supported by flow. also `...require(x)` is broken #6560135
*/
module.exports = {
/* eslint global-require: 0 */
module.exports = {
// Core
get createNavigationContainer() { return require('./createNavigationContainer').default; },
get StateUtils() { return require('./StateUtils').default; },
get addNavigationHelpers() { return require('./addNavigationHelpers').default; },
get NavigationActions() { return require('./NavigationActions').default; },
get createNavigationContainer() {
return require('./createNavigationContainer').default;
},
get StateUtils() {
return require('./StateUtils').default;
},
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() { return require('./navigators/createNavigator').default; },
get createNavigator() {
return require('./navigators/createNavigator').default;
},
// Routers
get StackRouter() { return require('./routers/StackRouter').default; },
get TabRouter() { return require('./routers/TabRouter').default; },
get StackRouter() {
return require('./routers/StackRouter').default;
},
get TabRouter() {
return require('./routers/TabRouter').default;
},
// HOCs
get withNavigation() { return require('./views/withNavigation').default; },
get withNavigation() {
return require('./views/withNavigation').default;
},
};

View File

@ -70,12 +70,11 @@ export default (
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
}
/* $FlowFixMe */
/* $FlowFixMe */
paths[routeName] = { re, keys };
});
return {
getComponentForState(state: NavigationState): NavigationComponent {
const activeChildRoute = state.routes[state.index];
const { routeName } = activeChildRoute;
@ -89,13 +88,19 @@ export default (
return getScreenForRouteName(routeConfigs, routeName);
},
getStateForAction(action: NavigationStackAction, state: ?NavigationState) {
action = NavigationActions.mapDeprecatedActionAndWarn(action);
getStateForAction(
passedAction: NavigationStackAction,
state: ?NavigationState,
) {
const action = NavigationActions.mapDeprecatedActionAndWarn(passedAction);
// Set up the initial state if needed
if (!state) {
let route = {};
if (action.type === NavigationActions.NAVIGATE && (childRouters[action.routeName] !== undefined)) {
if (
action.type === NavigationActions.NAVIGATE &&
childRouters[action.routeName] !== undefined
) {
return {
index: 0,
routes: [
@ -108,12 +113,16 @@ export default (
};
}
if (initialChildRouter) {
route = initialChildRouter.getStateForAction(NavigationActions.navigate({
routeName: initialRouteName,
params: initialRouteParams,
}));
route = initialChildRouter.getStateForAction(
NavigationActions.navigate({
routeName: initialRouteName,
params: initialRouteParams,
}),
);
}
const params = (route.params || action.params || initialRouteParams) && {
const params = (route.params ||
action.params ||
initialRouteParams) && {
...(route.params || {}),
...(action.params || {}),
...(initialRouteParams || {}),
@ -124,6 +133,7 @@ export default (
key: 'Init',
...(params ? { params } : {}),
};
// eslint-disable-next-line no-param-reassign
state = {
index: 0,
routes: [route],
@ -131,8 +141,10 @@ export default (
}
// Check if a child scene wants to handle the action as long as it is not a reset to the root stack
if(action.type !== NavigationActions.RESET || action.key !== null) {
const keyIndex = action.key ? StateUtils.indexOf(state, action.key) : -1
if (action.type !== NavigationActions.RESET || action.key !== null) {
const keyIndex = action.key
? StateUtils.indexOf(state, action.key)
: -1;
const childIndex = keyIndex >= 0 ? keyIndex : state.index;
const childRoute = state.routes[childIndex];
const childRouter = childRouters[childRoute.routeName];
@ -148,11 +160,15 @@ export default (
}
// Handle explicit push navigation action
if (action.type === NavigationActions.NAVIGATE && childRouters[action.routeName] !== undefined) {
if (
action.type === NavigationActions.NAVIGATE &&
childRouters[action.routeName] !== undefined
) {
const childRouter = childRouters[action.routeName];
let route;
if (childRouter) {
const childAction = action.action || NavigationActions.init({ params: action.params });
const childAction = action.action ||
NavigationActions.init({ params: action.params });
route = {
params: action.params,
...childRouter.getStateForAction(childAction),
@ -177,9 +193,14 @@ export default (
const childRouter = childRouters[childRouterName];
if (childRouter) {
// For each child router, start with a blank state
const initChildRoute = childRouter.getStateForAction(NavigationActions.init());
const initChildRoute = childRouter.getStateForAction(
NavigationActions.init(),
);
// Then check to see if the router handles our navigate action
const navigatedChildRoute = childRouter.getStateForAction(action, initChildRoute);
const navigatedChildRoute = childRouter.getStateForAction(
action,
initChildRoute,
);
let routeToPush = null;
if (navigatedChildRoute === null) {
// Push the route if the router has 'handled' the action and returned null
@ -200,8 +221,10 @@ export default (
}
if (action.type === NavigationActions.SET_PARAMS) {
/* $FlowFixMe */
const lastRoute = state.routes.find((route: *) => route.key === action.key);
const lastRoute = state.routes.find(
/* $FlowFixMe */
(route: *) => route.key === action.key,
);
if (lastRoute) {
const params = {
...lastRoute.params,
@ -220,27 +243,29 @@ export default (
}
if (action.type === NavigationActions.RESET) {
const resetAction = ((action: any): NavigationResetAction);
const resetAction: NavigationResetAction = action;
return {
...state,
routes: resetAction.actions.map((action: NavigationNavigateAction, index: number) => {
const router = childRouters[action.routeName];
if (router) {
return {
...action,
...router.getStateForAction(action),
routeName: action.routeName,
routes: resetAction.actions.map(
(childAction: NavigationNavigateAction, index: number) => {
const router = childRouters[childAction.routeName];
if (router) {
return {
...childAction,
...router.getStateForAction(childAction),
routeName: childAction.routeName,
key: `Init${index}`,
};
}
const route = {
...childAction,
key: `Init${index}`,
};
}
const route = {
...action,
key: `Init${index}`,
};
delete route.type;
return route;
}),
delete route.type;
return route;
},
),
index: action.index,
};
}
@ -248,8 +273,10 @@ export default (
if (action.type === NavigationActions.BACK) {
let backRouteIndex = null;
if (action.key) {
/* $FlowFixMe */
const backRoute = state.routes.find((route: *) => route.key === action.key);
const backRoute = state.routes.find(
/* $FlowFixMe */
(route: *) => route.key === action.key,
);
/* $FlowFixMe */
backRouteIndex = state.routes.indexOf(backRoute);
}
@ -267,7 +294,7 @@ export default (
return state;
},
getPathAndParamsForState(state: NavigationState): {path: string, params?: NavigationParams} {
getPathAndParamsForState(): { path: string, params?: NavigationParams } {
// TODO: implement this!
return {
path: '',
@ -291,9 +318,10 @@ export default (
let pathMatch;
let pathMatchKeys;
for (const routeName in paths) {
// eslint-disable-next-line no-restricted-syntax
for (const [routeName, path] of Object.entries(paths)) {
/* $FlowFixMe */
const { re, keys } = paths[routeName];
const { re, keys } = path;
pathMatch = re.exec(pathNameToResolve);
if (pathMatch && pathMatch.length) {
pathMatchKeys = keys;
@ -315,36 +343,41 @@ export default (
if (childRouters[matchedRouteName]) {
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
/* $FlowFixMe */
pathMatch.slice(pathMatchKeys.length).join('/')
pathMatch.slice(pathMatchKeys.length).join('/'),
);
}
// reduce the items of the query string. any query params may
// be overridden by path params
const queryParams = (queryString || '').split('&').reduce((result: *, item: string) => {
if (item !== '') {
const nextResult = result || {};
const [key, value] = item.split('=');
nextResult[key] = value;
return nextResult;
}
return result;
}, null);
const queryParams = (queryString || '').split('&').reduce(
(result: *, item: string) => {
if (item !== '') {
const nextResult = result || {};
const [key, value] = item.split('=');
nextResult[key] = value;
return nextResult;
}
return result;
},
null,
);
// reduce the matched pieces of the path into the params
// of the route. `params` is null if there are no params.
/* $FlowFixMe */
const params = pathMatch.slice(1).reduce((result: *, matchResult: *, i: number) => {
const key = pathMatchKeys[i];
if (key.asterisk || !key) {
return result;
}
const nextResult = result || {};
const paramName = key.name;
nextResult[paramName] = matchResult;
return nextResult;
}, queryParams);
const params = pathMatch.slice(1).reduce(
(result: *, matchResult: *, i: number) => {
const key = pathMatchKeys[i];
if (key.asterisk || !key) {
return result;
}
const nextResult = result || {};
const paramName = key.name;
nextResult[paramName] = matchResult;
return nextResult;
},
queryParams,
);
return NavigationActions.navigate({
routeName: matchedRouteName,
@ -353,7 +386,10 @@ export default (
});
},
getScreenOptions: createConfigGetter(routeConfigs, stackConfig.navigationOptions),
getScreenOptions: createConfigGetter(
routeConfigs,
stackConfig.navigationOptions,
),
getScreenConfig: getScreenConfigDeprecated,
};

View File

@ -24,7 +24,7 @@ import type {
export default (
routeConfigs: NavigationRouteConfigMap,
config: NavigationTabRouterConfig = {}
config: NavigationTabRouterConfig = {},
): NavigationRouter<*, *, *> => {
// Fail fast on invalid route definitions
validateRouteConfigMap(routeConfigs);
@ -38,7 +38,9 @@ export default (
const tabRouters = {};
order.forEach((routeName: string) => {
const routeConfig = routeConfigs[routeName];
paths[routeName] = typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
paths[routeName] = typeof routeConfig.path === 'string'
? routeConfig.path
: routeName;
tabRouters[routeName] = null;
if (routeConfig.screen && routeConfig.screen.router) {
tabRouters[routeName] = routeConfig.screen.router;
@ -47,14 +49,14 @@ export default (
invariant(
initialRouteIndex !== -1,
`Invalid initialRouteName '${initialRouteName}' for TabRouter. ` +
`Should be one of ${order.map((n: *) => `"${n}"`).join(', ')}`
`Should be one of ${order.map((n: *) => `"${n}"`).join(', ')}`,
);
return {
getStateForAction(
action: NavigationAction | { action: NavigationAction },
inputState?: ?NavigationState
inputState?: ?NavigationState,
): ?NavigationState {
// eslint-disable-next-line no-param-reassign
// eslint-disable-next-line no-param-reassign
action = NavigationActions.mapDeprecatedActionAndWarn(action);
// Establish a default state
@ -63,9 +65,10 @@ export default (
const routes = order.map((routeName: string) => {
const tabRouter = tabRouters[routeName];
if (tabRouter) {
const childAction = action.action || NavigationActions.init({
const childAction = action.action ||
NavigationActions.init({
...(action.params ? { params: action.params } : {}),
});
});
return {
...tabRouter.getStateForAction(childAction),
key: routeName,
@ -88,13 +91,16 @@ export default (
// Merge any params from the action into all the child routes
const { params } = action;
if (params) {
state.routes = state.routes.map(route => ({
...route,
params: {
...route.params,
...params,
},
}: NavigationRoute));
state.routes = state.routes.map(
(route: *) =>
({
...route,
params: {
...route.params,
...params,
},
}: NavigationRoute),
);
}
}
@ -122,10 +128,12 @@ export default (
// Handle tab changing. Do this after letting the current tab try to
// handle the action, to allow inner tabs to change first
let activeTabIndex = state.index;
const isBackEligible = action.key == null || action.key === activeTabLastState.key;
const isBackEligible = action.key == null ||
action.key === activeTabLastState.key;
if (
action.type === NavigationActions.BACK &&
isBackEligible && shouldBackNavigateToInitialRoute
isBackEligible &&
shouldBackNavigateToInitialRoute
) {
activeTabIndex = initialRouteIndex;
}
@ -247,7 +255,7 @@ export default (
invariant(
routeName,
`There is no route defined for index ${state.index}. Check that
that you passed in a navigation state with a valid tab/screen index.`
that you passed in a navigation state with a valid tab/screen index.`,
);
const childRouter = tabRouters[routeName];
if (childRouter) {
@ -286,30 +294,45 @@ export default (
* This will return null if there is no action matched
*/
getActionForPathAndParams(path: string, params: ?NavigationParams) {
return order.map((tabId: string) => {
const parts = path.split('/');
const pathToTest = paths[tabId];
if (parts[0] === pathToTest) {
const tabRouter = tabRouters[tabId];
const action: NavigationNavigateAction = NavigationActions.navigate({
routeName: tabId,
});
if (tabRouter && tabRouter.getActionForPathAndParams) {
action.action = tabRouter.getActionForPathAndParams(parts.slice(1).join('/'), params);
} else if (params) {
action.params = params;
return order
.map((tabId: string) => {
const parts = path.split('/');
const pathToTest = paths[tabId];
if (parts[0] === pathToTest) {
const tabRouter = tabRouters[tabId];
const action: NavigationNavigateAction = NavigationActions.navigate(
{
routeName: tabId,
},
);
if (tabRouter && tabRouter.getActionForPathAndParams) {
action.action = tabRouter.getActionForPathAndParams(
parts.slice(1).join('/'),
params,
);
} else if (params) {
action.params = params;
}
return action;
}
return action;
}
return null;
}).find((action: *) => !!action) || order.map((tabId: string) => {
const tabRouter = tabRouters[tabId];
return tabRouter && tabRouter.getActionForPathAndParams(path, params);
}).find((action: *) => !!action) || null;
return null;
})
.find((action: *) => !!action) ||
order
.map((tabId: string) => {
const tabRouter = tabRouters[tabId];
return tabRouter &&
tabRouter.getActionForPathAndParams(path, params);
})
.find((action: *) => !!action) ||
null;
},
getScreenOptions: createConfigGetter(routeConfigs, config.navigationOptions),
getScreenOptions: createConfigGetter(
routeConfigs,
config.navigationOptions,
),
getScreenConfig: getScreenConfigDeprecated,
};
};

View File

@ -1,4 +1,5 @@
/* @flow */
/* eslint react/no-multi-comp:0 */
import React from 'react';
@ -19,15 +20,23 @@ Object.keys(ROUTERS).forEach((routerName: string) => {
describe(`General router features - ${routerName}`, () => {
test('title is configurable using navigationOptions and getScreenOptions', () => {
class FooView extends React.Component {
render() { return <div />; }
render() {
return <div />;
}
}
class BarView extends React.Component {
render() { return <div />; }
render() {
return <div />;
}
static navigationOptions = { title: 'BarTitle' };
}
class BazView extends React.Component {
render() { return <div />; }
static navigationOptions = ({navigation}) => ({ title: `Baz-${navigation.state.params.id}` });
render() {
return <div />;
}
static navigationOptions = ({ navigation }: *) => ({
title: `Baz-${navigation.state.params.id}`,
});
}
const router = Router({
Foo: { screen: FooView },
@ -39,9 +48,24 @@ Object.keys(ROUTERS).forEach((routerName: string) => {
{ key: 'B', routeName: 'Bar' },
{ key: 'A', routeName: 'Baz', params: { id: '123' } },
];
expect(router.getScreenOptions(addNavigationHelpers({ state: routes[0], dispatch: () => false }), {}).title).toEqual(undefined);
expect(router.getScreenOptions(addNavigationHelpers({ state: routes[1], dispatch: () => false }), {}).title).toEqual('BarTitle');
expect(router.getScreenOptions(addNavigationHelpers({ state: routes[2], dispatch: () => false }), {}).title).toEqual('Baz-123');
expect(
router.getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
{},
).title,
).toEqual(undefined);
expect(
router.getScreenOptions(
addNavigationHelpers({ state: routes[1], dispatch: () => false }),
{},
).title,
).toEqual('BarTitle');
expect(
router.getScreenOptions(
addNavigationHelpers({ state: routes[2], dispatch: () => false }),
{},
).title,
).toEqual('Baz-123');
});
});
});
@ -62,9 +86,15 @@ test('Handles no-op actions with tabs within stack router', () => {
},
});
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const state2 = TestRouter.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Qux' });
const state2 = TestRouter.getStateForAction({
type: NavigationActions.NAVIGATE,
routeName: 'Qux',
});
expect(state1).toEqual(state2);
const state3 = TestRouter.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Zap' }, state2);
const state3 = TestRouter.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
state2,
);
expect(state2).toEqual(state3);
});
@ -90,7 +120,14 @@ test('Handles deep action', () => {
],
};
expect(state1).toEqual(expectedState);
const state2 = TestRouter.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo', action: { type: NavigationActions.NAVIGATE, routeName: 'Zoo' } }, state1);
const state2 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
action: { type: NavigationActions.NAVIGATE, routeName: 'Zoo' },
},
state1,
);
expect(state2 && state2.index).toEqual(1);
/* $FlowFixMe */
expect(state2 && state2.routes[1].index).toEqual(1);
@ -112,8 +149,14 @@ test('Supports lazily-evaluated getScreen', () => {
},
});
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const state2 = TestRouter.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Qux' });
const state2 = TestRouter.getStateForAction({
type: NavigationActions.NAVIGATE,
routeName: 'Qux',
});
expect(state1).toEqual(state2);
const state3 = TestRouter.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Zap' }, state2);
const state3 = TestRouter.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
state2,
);
expect(state2).toEqual(state3);
});

View File

@ -1,4 +1,5 @@
/* @flow */
/* eslint no-shadow:0 react/no-multi-comp:0 */
import React from 'react';
@ -80,21 +81,25 @@ describe('StackRouter', () => {
},
});
expect(router.getComponentForState({
index: 0,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
{ key: 'c', routeName: 'foo' },
],
})).toBe(FooScreen);
expect(router.getComponentForState({
index: 1,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
],
})).toBe(BarScreen);
expect(
router.getComponentForState({
index: 0,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
{ key: 'c', routeName: 'foo' },
],
}),
).toBe(FooScreen);
expect(
router.getComponentForState({
index: 1,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
],
}),
).toBe(BarScreen);
});
test('Handles getScreen in getComponentForState', () => {
@ -109,27 +114,39 @@ describe('StackRouter', () => {
},
});
expect(router.getComponentForState({
index: 0,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
{ key: 'c', routeName: 'foo' },
],
})).toBe(FooScreen);
expect(router.getComponentForState({
index: 1,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
],
})).toBe(BarScreen);
expect(
router.getComponentForState({
index: 0,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
{ key: 'c', routeName: 'foo' },
],
}),
).toBe(FooScreen);
expect(
router.getComponentForState({
index: 1,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
],
}),
).toBe(BarScreen);
});
test('Gets the screen for given route', () => {
const FooScreen = () => <div />;
const BarScreen = class extends React.Component { render() { return <div />; } };
const BazScreen = React.createClass({ render() { return <div />; } });
const BarScreen = class extends React.Component {
render() {
return <div />;
}
};
const BazScreen = class extends React.Component {
render() {
return <div />;
}
};
const router = StackRouter({
foo: {
screen: FooScreen,
@ -149,8 +166,16 @@ describe('StackRouter', () => {
test('Handles getScreen in getComponent', () => {
const FooScreen = () => <div />;
const BarScreen = class extends React.Component { render() { return <div />; } };
const BazScreen = React.createClass({ render() { return <div />; } });
const BarScreen = class extends React.Component {
render() {
return <div />;
}
};
const BazScreen = class extends React.Component {
render() {
return <div />;
}
};
const router = StackRouter({
foo: {
getScreen: () => FooScreen,
@ -186,7 +211,9 @@ describe('StackRouter', () => {
});
test('Parses paths with a query', () => {
expect(TestStackRouter.getActionForPathAndParams('people/foo?code=test&foo=bar')).toEqual({
expect(
TestStackRouter.getActionForPathAndParams('people/foo?code=test&foo=bar'),
).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'person',
params: {
@ -198,7 +225,9 @@ describe('StackRouter', () => {
});
test('Parses paths with an empty query value', () => {
expect(TestStackRouter.getActionForPathAndParams('people/foo?code=&foo=bar')).toEqual({
expect(
TestStackRouter.getActionForPathAndParams('people/foo?code=&foo=bar'),
).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'person',
params: {
@ -288,19 +317,20 @@ describe('StackRouter', () => {
Bar.router = StackRouter({
baz: { screen: () => <div /> },
qux: { screen: () => <div /> },
})
});
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: Bar },
})
});
const initState = TestRouter.getStateForAction(NavigationActions.init());
expect(initState).toEqual({
index: 0,
routes: [
{ key: 'Init', routeName: 'foo' }
]
routes: [{ key: 'Init', routeName: 'foo' }],
});
const pushedState = TestRouter.getStateForAction(NavigationActions.navigate({ routeName: 'qux' }), initState);
const pushedState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'qux' }),
initState,
);
// $FlowFixMe
expect(pushedState.index).toEqual(1);
// $FlowFixMe
@ -330,12 +360,22 @@ describe('StackRouter', () => {
},
],
});
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { name: 'Zoom' } }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { name: 'Zoom' },
},
state,
);
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.routes[1].routeName).toEqual('Bar');
expect(state2 && state2.routes[1].params).toEqual({ name: 'Zoom' });
expect(state2 && state2.routes.length).toEqual(2);
const state3 = router.getStateForAction({ type: NavigationActions.BACK }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2,
);
expect(state3).toEqual({
index: 0,
routes: [
@ -373,12 +413,22 @@ describe('StackRouter', () => {
},
],
});
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { name: 'Zoom' } }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { name: 'Zoom' },
},
state,
);
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.routes[1].routeName).toEqual('Bar');
expect(state2 && state2.routes[1].params).toEqual({ name: 'Zoom' });
expect(state2 && state2.routes.length).toEqual(2);
const state3 = router.getStateForAction({ type: NavigationActions.BACK }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2,
);
expect(state3).toEqual({
index: 0,
routes: [
@ -402,25 +452,48 @@ describe('StackRouter', () => {
},
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { name: 'Zoom' } }, state);
const state3 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { name: 'Foo' } }, state2);
const state4 = router.getStateForAction({ type: NavigationActions.BACK, key: 'wrongKey' }, state3);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { name: 'Zoom' },
},
state,
);
const state3 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { name: 'Foo' },
},
state2,
);
const state4 = router.getStateForAction(
{ type: NavigationActions.BACK, key: 'wrongKey' },
state3,
);
expect(state3).toEqual(state4);
const state5 = router.getStateForAction({ type: NavigationActions.BACK, key: state3 && state3.routes[1].key }, state4);
const state5 = router.getStateForAction(
{ type: NavigationActions.BACK, key: state3 && state3.routes[1].key },
state4,
);
expect(state5).toEqual(state);
});
test('Handle initial route navigation', () => {
const FooScreen = () => <div />;
const BarScreen = () => <div />;
const router = StackRouter({
Foo: {
screen: FooScreen,
const router = StackRouter(
{
Foo: {
screen: FooScreen,
},
Bar: {
screen: BarScreen,
},
},
Bar: {
screen: BarScreen,
},
}, { initialRouteName: 'Bar' });
{ initialRouteName: 'Bar' },
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
@ -435,11 +508,14 @@ describe('StackRouter', () => {
test('Initial route params appear in nav state', () => {
const FooScreen = () => <div />;
const router = StackRouter({
Foo: {
screen: FooScreen,
const router = StackRouter(
{
Foo: {
screen: FooScreen,
},
},
}, { initialRouteName: 'Bar', initialRouteParams: { foo: 'bar' } });
{ initialRouteName: 'Bar', initialRouteParams: { foo: 'bar' } },
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
@ -465,30 +541,43 @@ describe('StackRouter', () => {
},
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { bar: '42' } }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { bar: '42' },
},
state,
);
expect(state2).not.toBeNull();
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.routes[1].params).toEqual({ bar: '42' });
});
test('Handles the SetParams action', () => {
const router = StackRouter({
Foo: {
screen: () => <div />,
const router = StackRouter(
{
Foo: {
screen: () => <div />,
},
Bar: {
screen: () => <div />,
},
},
Bar: {
screen: () => <div />,
{
initialRouteName: 'Bar',
initialRouteParams: { name: 'Zoo' },
},
}, {
initialRouteName: 'Bar',
initialRouteParams: { name: 'Zoo' },
});
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({
type: NavigationActions.SET_PARAMS,
params: { name: 'Qux' },
key: 'Init',
}, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
params: { name: 'Qux' },
key: 'Init',
},
state,
);
expect(state2 && state2.index).toEqual(0);
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
});
@ -497,23 +586,26 @@ describe('StackRouter', () => {
const ChildNavigator = () => <div />;
const GrandChildNavigator = () => <div />;
GrandChildNavigator.router = StackRouter({
Quux: { screen: () => <div />, },
Corge: { screen: () => <div />, },
Quux: { screen: () => <div /> },
Corge: { screen: () => <div /> },
});
ChildNavigator.router = TabRouter({
Baz: { screen: GrandChildNavigator, },
Qux: { screen: () => <div />, },
Baz: { screen: GrandChildNavigator },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: ChildNavigator, },
Bar: { screen: () => <div />, },
Foo: { screen: ChildNavigator },
Bar: { screen: () => <div /> },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({
type: NavigationActions.SET_PARAMS,
params: { name: 'foobar' },
key: 'Init',
}, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
params: { name: 'foobar' },
key: 'Init',
},
state,
);
expect(state2 && state2.index).toEqual(0);
/* $FlowFixMe */
expect(state2 && state2.routes[0].routes[0].routes).toEqual([
@ -535,7 +627,21 @@ describe('StackRouter', () => {
},
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.RESET, actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Foo', params: { bar: '42' } }, { type: NavigationActions.NAVIGATE, routeName: 'Bar' }], index: 1 }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.RESET,
actions: [
{
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
params: { bar: '42' },
},
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
],
index: 1,
},
state,
);
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.routes[0].params).toEqual({ bar: '42' });
expect(state2 && state2.routes[0].routeName).toEqual('Foo');
@ -561,7 +667,14 @@ describe('StackRouter', () => {
},
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.RESET, actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Foo' }], index: 0 }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.RESET,
actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Foo' }],
index: 0,
},
state,
);
expect(state2 && state2.index).toEqual(0);
expect(state2 && state2.routes[0].routeName).toEqual('Foo');
@ -588,9 +701,32 @@ describe('StackRouter', () => {
},
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo', action: { type: NavigationActions.NAVIGATE, routeName: 'baz' }}, state);
const state3 = router.getStateForAction({ type: NavigationActions.RESET, key: 'Init', actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Foo' }], index: 0 }, state2);
const state4 = router.getStateForAction({ type: NavigationActions.RESET, key: null, actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Bar' }], index: 0 }, state3);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
action: { type: NavigationActions.NAVIGATE, routeName: 'baz' },
},
state,
);
const state3 = router.getStateForAction(
{
type: NavigationActions.RESET,
key: 'Init',
actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Foo' }],
index: 0,
},
state2,
);
const state4 = router.getStateForAction(
{
type: NavigationActions.RESET,
key: null,
actions: [{ type: NavigationActions.NAVIGATE, routeName: 'Bar' }],
index: 0,
},
state3,
);
expect(state4 && state4.index).toEqual(0);
expect(state4 && state4.routes[0].routeName).toEqual('Bar');
@ -601,11 +737,18 @@ describe('StackRouter', () => {
ChildNavigator.router = StackRouter({ Baz: { screen: () => <div /> } });
const router = StackRouter({
Foo: { screen: () => <div />, },
Bar: { screen: ChildNavigator, },
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { foo: '42' } }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { foo: '42' },
},
state,
);
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
/* $FlowFixMe */
expect(state2 && state2.routes[1].routes).toEqual([
@ -625,11 +768,18 @@ describe('StackRouter', () => {
});
const router = StackRouter({
Foo: { screen: () => <div />, },
Bar: { screen: ChildNavigator, },
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { foo: '42' } }, state);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { foo: '42' },
},
state,
);
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
/* $FlowFixMe */
expect(state2 && state2.routes[1].routes).toEqual([
@ -647,16 +797,22 @@ describe('StackRouter', () => {
});
test('Handles empty URIs', () => {
const router = StackRouter({
Foo: {
screen: () => <div />,
const router = StackRouter(
{
Foo: {
screen: () => <div />,
},
Bar: {
screen: () => <div />,
},
},
Bar: {
screen: () => <div />,
},
}, { initialRouteName: 'Bar' });
{ initialRouteName: 'Bar' },
);
const action = router.getActionForPathAndParams('');
expect(action).toEqual({ type: NavigationActions.NAVIGATE, routeName: 'Bar' });
expect(action).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
});
let state = null;
if (action) {
state = router.getStateForAction(action);
@ -677,7 +833,17 @@ describe('StackRouter', () => {
/* $FlowFixMe: these are for deprecated action names */
const state = router.getStateForAction({ type: 'Init' });
/* $FlowFixMe: these are for deprecated action names */
const state2 = router.getStateForAction({ type: 'Reset', actions: [{ type: 'Navigate', routeName: 'Foo', params: { bar: '42' } }, { type: 'Navigate', routeName: 'Bar' }], index: 1 }, state);
const state2 = router.getStateForAction(
{
type: 'Reset',
actions: [
{ type: 'Navigate', routeName: 'Foo', params: { bar: '42' } },
{ type: 'Navigate', routeName: 'Bar' },
],
index: 1,
},
state,
);
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.routes[0].params).toEqual({ bar: '42' });
expect(state2 && state2.routes[0].routeName).toEqual('Foo');

View File

@ -22,18 +22,30 @@ describe('TabRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
const expectedState = {
index: 0,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
};
expect(state).toEqual(expectedState);
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state,
);
const expectedState2 = {
index: 1,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
};
expect(state2).toEqual(expectedState2);
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
const state3 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state2,
);
expect(state3).toEqual(null);
});
@ -47,27 +59,45 @@ describe('TabRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
const expectedState = {
index: 0,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
};
expect(state).toEqual(expectedState);
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state,
);
const expectedState2 = {
index: 1,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
};
expect(state2).toEqual(expectedState2);
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
const state3 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state2,
);
expect(state3).toEqual(null);
});
test('Can set the initial tab', () => {
const router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig }, { initialRouteName: 'Bar' });
const router = TabRouter(
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
{ initialRouteName: 'Bar' },
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 1,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
});
});
@ -89,25 +119,52 @@ describe('TabRouter', () => {
});
test('getStateForAction returns null when navigating to same tab', () => {
const router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig }, { initialRouteName: 'Bar' });
const router = TabRouter(
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
{ initialRouteName: 'Bar' },
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state,
);
expect(state2).toEqual(null);
});
test('getStateForAction returns initial navigate', () => {
const router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig });
const state = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo' });
const router = TabRouter({
Foo: BareLeafRouteConfig,
Bar: BareLeafRouteConfig,
});
const state = router.getStateForAction({
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
});
expect(state && state.index).toEqual(0);
});
test('Handles nested tabs and nested actions', () => {
const ChildTabNavigator = () => <div />;
ChildTabNavigator.router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig });
const router = TabRouter({ Foo: BareLeafRouteConfig, Baz: { screen: ChildTabNavigator }, Boo: BareLeafRouteConfig });
ChildTabNavigator.router = TabRouter({
Foo: BareLeafRouteConfig,
Bar: BareLeafRouteConfig,
});
const router = TabRouter({
Foo: BareLeafRouteConfig,
Baz: { screen: ChildTabNavigator },
Boo: BareLeafRouteConfig,
});
const params = { foo: '42' };
const action = router.getActionForPathAndParams('Baz/Bar', params);
const navAction = { type: NavigationActions.NAVIGATE, routeName: 'Baz', action: { type: NavigationActions.NAVIGATE, routeName: 'Bar', params: { foo: '42' } } };
const navAction = {
type: NavigationActions.NAVIGATE,
routeName: 'Baz',
action: {
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
params: { foo: '42' },
},
};
expect(action).toEqual(navAction);
const state = router.getStateForAction(navAction);
expect(state).toEqual({
@ -143,9 +200,19 @@ describe('TabRouter', () => {
test('Handles passing params to nested tabs', () => {
const ChildTabNavigator = () => <div />;
ChildTabNavigator.router = TabRouter({ Boo: BareLeafRouteConfig, Bar: BareLeafRouteConfig });
const router = TabRouter({ Foo: BareLeafRouteConfig, Baz: { screen: ChildTabNavigator } });
const navAction = { type: NavigationActions.NAVIGATE, routeName: 'Baz', params: { foo: '42', bar: '43' } };
ChildTabNavigator.router = TabRouter({
Boo: BareLeafRouteConfig,
Bar: BareLeafRouteConfig,
});
const router = TabRouter({
Foo: BareLeafRouteConfig,
Baz: { screen: ChildTabNavigator },
});
const navAction = {
type: NavigationActions.NAVIGATE,
routeName: 'Baz',
params: { foo: '42', bar: '43' },
};
let state = router.getStateForAction(navAction);
expect(state).toEqual({
index: 1,
@ -164,8 +231,14 @@ describe('TabRouter', () => {
});
// Ensure that navigating back and forth doesn't overwrite
state = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' }, state);
state = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Boo' }, state);
state = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state,
);
state = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Boo' },
state,
);
expect(state && state.routes[1]).toEqual({
index: 0,
key: 'Baz',
@ -179,9 +252,19 @@ describe('TabRouter', () => {
test('Handles initial deep linking into nested tabs', () => {
const ChildTabNavigator = () => <div />;
ChildTabNavigator.router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig });
const router = TabRouter({ Foo: BareLeafRouteConfig, Baz: { screen: ChildTabNavigator }, Boo: BareLeafRouteConfig });
const state = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Bar' });
ChildTabNavigator.router = TabRouter({
Foo: BareLeafRouteConfig,
Bar: BareLeafRouteConfig,
});
const router = TabRouter({
Foo: BareLeafRouteConfig,
Baz: { screen: ChildTabNavigator },
Boo: BareLeafRouteConfig,
});
const state = router.getStateForAction({
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
});
expect(state).toEqual({
index: 1,
routes: [
@ -198,7 +281,10 @@ describe('TabRouter', () => {
{ key: 'Boo', routeName: 'Boo' },
],
});
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo' }, state);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Foo' },
state,
);
expect(state2).toEqual({
index: 1,
routes: [
@ -215,101 +301,147 @@ describe('TabRouter', () => {
{ key: 'Boo', routeName: 'Boo' },
],
});
const state3 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo' }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Foo' },
state2,
);
expect(state3).toEqual(null);
});
test('Handles linking across of deeply nested tabs', () => {
const ChildNavigator0 = () => <div />;
ChildNavigator0.router = TabRouter({ Boo: BareLeafRouteConfig, Baz: BareLeafRouteConfig });
ChildNavigator0.router = TabRouter({
Boo: BareLeafRouteConfig,
Baz: BareLeafRouteConfig,
});
const ChildNavigator1 = () => <div />;
ChildNavigator1.router = TabRouter({ Zoo: BareLeafRouteConfig, Zap: BareLeafRouteConfig });
ChildNavigator1.router = TabRouter({
Zoo: BareLeafRouteConfig,
Zap: BareLeafRouteConfig,
});
const MidNavigator = () => <div />;
MidNavigator.router = TabRouter({ Foo: { screen: ChildNavigator0 }, Bar: { screen: ChildNavigator1 } });
const router = TabRouter({ Foo: { screen: MidNavigator }, Gah: BareLeafRouteConfig });
MidNavigator.router = TabRouter({
Foo: { screen: ChildNavigator0 },
Bar: { screen: ChildNavigator1 },
});
const router = TabRouter({
Foo: { screen: MidNavigator },
Gah: BareLeafRouteConfig,
});
const state = router.getStateForAction(INIT_ACTION);
expect(state).toEqual({
index: 0,
routes: [
{ index: 0,
{
index: 0,
key: 'Foo',
routeName: 'Foo',
routes: [
{ index: 0,
{
index: 0,
key: 'Foo',
routeName: 'Foo',
routes: [
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
] },
{ index: 0,
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
],
},
{
index: 0,
key: 'Bar',
routeName: 'Bar',
routes: [
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
] },
] },
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
],
},
],
},
{ key: 'Gah', routeName: 'Gah' },
],
});
const state2 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Zap' }, state);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
state,
);
expect(state2).toEqual({
index: 0,
routes: [
{ index: 1,
{
index: 1,
key: 'Foo',
routeName: 'Foo',
routes: [
{ index: 0,
{
index: 0,
key: 'Foo',
routeName: 'Foo',
routes: [
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
] },
{ index: 1,
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
],
},
{
index: 1,
key: 'Bar',
routeName: 'Bar',
routes: [
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
] },
] },
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
],
},
],
},
{ key: 'Gah', routeName: 'Gah' },
],
});
const state3 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Zap' }, state2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
state2,
);
expect(state3).toEqual(null);
const state4 = router.getStateForAction({ type: NavigationActions.NAVIGATE, routeName: 'Foo', action: { type: NavigationActions.NAVIGATE, routeName: 'Bar', action: { type: NavigationActions.NAVIGATE, routeName: 'Zap' } } });
const state4 = router.getStateForAction({
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
action: {
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
action: { type: NavigationActions.NAVIGATE, routeName: 'Zap' },
},
});
expect(state4).toEqual({
index: 0,
routes: [
{ index: 1,
{
index: 1,
key: 'Foo',
routeName: 'Foo',
routes: [
{ index: 0,
{
index: 0,
key: 'Foo',
routeName: 'Foo',
routes: [
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
] },
{ index: 1,
{ key: 'Boo', routeName: 'Boo' },
{ key: 'Baz', routeName: 'Baz' },
],
},
{
index: 1,
key: 'Bar',
routeName: 'Bar',
routes: [
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
] },
] },
{ key: 'Zoo', routeName: 'Zoo' },
{ key: 'Zap', routeName: 'Zap' },
],
},
],
},
{ key: 'Gah', routeName: 'Gah' },
],
});
});
test('Handles path configuration', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
@ -335,7 +467,10 @@ describe('TabRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
const expectedState = {
index: 0,
routes: [{ key: 'Foo', routeName: 'Foo' }, { key: 'Bar', routeName: 'Bar' }],
routes: [
{ key: 'Foo', routeName: 'Foo' },
{ key: 'Bar', routeName: 'Bar' },
],
};
expect(state).toEqual(expectedState);
const state2 = router.getStateForAction(expectedAction, state);
@ -376,7 +511,6 @@ describe('TabRouter', () => {
});
});
test('Gets deep path', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
@ -414,11 +548,17 @@ describe('TabRouter', () => {
});
test('Maps old actions (uses "getStateForAction returns null when navigating to same tab" test)', () => {
const router = TabRouter({ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig }, { initialRouteName: 'Bar' });
const router = TabRouter(
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
{ initialRouteName: 'Bar' },
);
/* $FlowFixMe: these are for deprecated action names */
const state = router.getStateForAction({ type: 'Init' });
/* $FlowFixMe: these are for deprecated action names */
const state2 = router.getStateForAction({ type: 'Navigate', routeName: 'Bar' }, state);
const state2 = router.getStateForAction(
{ type: 'Navigate', routeName: 'Bar' },
state,
);
expect(state2).toEqual(null);
});
@ -435,10 +575,7 @@ describe('TabRouter', () => {
expect(state0).toEqual({
index: 0,
routes: [
{ key: 'a', routeName: 'a' },
{ key: 'b', routeName: 'b' },
],
routes: [{ key: 'a', routeName: 'a' }, { key: 'b', routeName: 'b' }],
});
const params = { key: 'value' };

View File

@ -12,7 +12,7 @@ test('should get config for screen', () => {
/* eslint-disable react/no-multi-comp */
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
static navigationOptions = ({ navigation }: *) => ({
title: `Welcome ${navigation.state.params ? navigation.state.params.user : 'anonymous'}`,
headerVisible: true,
});
@ -34,9 +34,11 @@ test('should get config for screen', () => {
}
class NotificationScreen extends Component {
static navigationOptions = ({ navigation }) => ({
static navigationOptions = ({ navigation }: *) => ({
title: '42',
headerVisible: navigation.state.params ? !navigation.state.params.fullscreen : true,
headerVisible: navigation.state.params
? !navigation.state.params.fullscreen
: true,
});
render() {
@ -44,16 +46,18 @@ test('should get config for screen', () => {
}
}
const getScreenOptions: NavigationScreenOptionsGetter<NavigationStackScreenOptions, *> = createConfigGetter({
Home: { screen: HomeScreen },
Settings: { screen: SettingsScreen },
Notifications: {
screen: NotificationScreen,
navigationOptions: {
title: '10 new notifications',
const getScreenOptions: NavigationScreenOptionsGetter<NavigationStackScreenOptions, *> = createConfigGetter(
{
Home: { screen: HomeScreen },
Settings: { screen: SettingsScreen },
Notifications: {
screen: NotificationScreen,
navigationOptions: {
title: '10 new notifications',
},
},
},
});
);
const routes = [
{ key: 'A', routeName: 'Home' },
@ -63,14 +67,54 @@ test('should get config for screen', () => {
{ key: 'E', routeName: 'Notifications', params: { fullscreen: true } },
];
expect(getScreenOptions(addNavigationHelpers({ state: routes[0], dispatch: () => false }), {}).title).toEqual('Welcome anonymous');
expect(getScreenOptions(addNavigationHelpers({ state: routes[1], dispatch: () => false }), {}).title).toEqual('Welcome jane');
expect(getScreenOptions(addNavigationHelpers({ state: routes[0], dispatch: () => false }), {}).headerVisible).toEqual(true);
expect(getScreenOptions(addNavigationHelpers({ state: routes[2], dispatch: () => false }), {}).title).toEqual('Settings!!!');
expect(getScreenOptions(addNavigationHelpers({ state: routes[2], dispatch: () => false }), {}).headerVisible).toEqual(false);
expect(getScreenOptions(addNavigationHelpers({ state: routes[3], dispatch: () => false }), {}).title).toEqual('10 new notifications');
expect(getScreenOptions(addNavigationHelpers({ state: routes[3], dispatch: () => false }), {}).headerVisible).toEqual(true);
expect(getScreenOptions(addNavigationHelpers({ state: routes[4], dispatch: () => false }), {}).headerVisible).toEqual(false);
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
{},
).title,
).toEqual('Welcome anonymous');
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[1], dispatch: () => false }),
{},
).title,
).toEqual('Welcome jane');
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
{},
).headerVisible,
).toEqual(true);
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[2], dispatch: () => false }),
{},
).title,
).toEqual('Settings!!!');
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[2], dispatch: () => false }),
{},
).headerVisible,
).toEqual(false);
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[3], dispatch: () => false }),
{},
).title,
).toEqual('10 new notifications');
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[3], dispatch: () => false }),
{},
).headerVisible,
).toEqual(true);
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[4], dispatch: () => false }),
{},
).headerVisible,
).toEqual(false);
});
test('should throw if the route does not exist', () => {
@ -86,13 +130,15 @@ test('should throw if the route does not exist', () => {
Home: { screen: HomeScreen },
});
const routes = [
{ key: 'B', routeName: 'Settings' },
];
const routes = [{ key: 'B', routeName: 'Settings' }];
expect(
() => getScreenOptions(addNavigationHelpers({ state: routes[0], dispatch: () => false }), {}),
).toThrowError("There is no route defined for key Settings.\nMust be one of: 'Home'");
expect(() =>
getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
{},
)).toThrowError(
"There is no route defined for key Settings.\nMust be one of: 'Home'",
);
});
test('should throw if the screen is not defined under the route config', () => {
@ -102,11 +148,10 @@ test('should throw if the screen is not defined under the route config', () => {
Home: {},
});
const routes = [
{ key: 'B', routeName: 'Home' },
];
const routes = [{ key: 'B', routeName: 'Home' }];
expect(
() => getScreenOptions(addNavigationHelpers({ state: routes[0], dispatch: () => false }))
).toThrowError('Route Home must define a screen or a getScreen.');
expect(() =>
getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
)).toThrowError('Route Home must define a screen or a getScreen.');
});

View File

@ -22,6 +22,8 @@ describe('validateRouteConfigMap', () => {
});
test('Fails on bad object', () => {
const invalidMap = {
// @todo fix flow, this should error as no screen/getScreen
// provided
Home: {
foo: 'bar',
},

View File

@ -12,6 +12,7 @@ import type {
NavigationScreenProp,
NavigationAction,
NavigationRoute,
NavigationStateRoute,
NavigationRouteConfigMap,
NavigationScreenConfig,
NavigationScreenConfigProps,
@ -54,8 +55,7 @@ export default (
const { routes, index } = (route: NavigationStateRoute);
invariant(
route.routeName &&
typeof route.routeName === 'string',
route.routeName && typeof route.routeName === 'string',
'Cannot get config because the route does not have a routeName.',
);
@ -66,14 +66,17 @@ export default (
if (Component.router) {
invariant(
route && routes && index != null,
`Expect nav state to have routes and index, ${JSON.stringify(route)}`
`Expect nav state to have routes and index, ${JSON.stringify(route)}`,
);
const childRoute = routes[index];
const childNavigation = addNavigationHelpers({
state: childRoute,
dispatch,
});
outputConfig = Component.router.getScreenOptions(childNavigation, screenProps);
outputConfig = Component.router.getScreenOptions(
childNavigation,
screenProps,
);
}
const routeConfig = routeConfigs[route.routeName];
@ -83,8 +86,16 @@ export default (
const configOptions = { navigation, screenProps: screenProps || {} };
outputConfig = applyConfig(navigatorScreenConfig, outputConfig, configOptions);
outputConfig = applyConfig(componentScreenConfig, outputConfig, configOptions);
outputConfig = applyConfig(
navigatorScreenConfig,
outputConfig,
configOptions,
);
outputConfig = applyConfig(
componentScreenConfig,
outputConfig,
configOptions,
);
outputConfig = applyConfig(routeScreenConfig, outputConfig, configOptions);
validateScreenOptions(outputConfig, route);

View File

@ -3,7 +3,8 @@
*/
import invariant from 'fbjs/lib/invariant';
export default () => invariant(
false,
'`getScreenConfig` has been replaced with `getScreenOptions`',
);
export default () =>
invariant(
false,
'`getScreenConfig` has been replaced with `getScreenOptions`',
);

View File

@ -20,7 +20,9 @@ export default function getScreenForRouteName( // eslint-disable-line consistent
invariant(
routeConfig,
`There is no route defined for key ${routeName}.\n` +
`Must be one of: ${Object.keys(routeConfigs).map((a: string) => `'${a}'`).join(',')}`
`Must be one of: ${Object.keys(routeConfigs)
.map((a: string) => `'${a}'`)
.join(',')}`,
);
if (routeConfig.screen) {
@ -32,9 +34,9 @@ export default function getScreenForRouteName( // eslint-disable-line consistent
invariant(
typeof screen === 'function',
`The getScreen defined for route '${routeName} didn't return a valid ` +
'screen or navigator.\n\n' +
'Please pass it like this:\n' +
`${routeName}: {\n getScreen: () => require('./MyScreen').default\n}`
'screen or navigator.\n\n' +
'Please pass it like this:\n' +
`${routeName}: {\n getScreen: () => require('./MyScreen').default\n}`,
);
return screen;
}

View File

@ -1,5 +1,9 @@
/** @flow */
import invariant from 'fbjs/lib/invariant';
import type { NavigationRouteConfigMap } from '../TypeDefinition';
/**
* Make sure the config passed e.g. to StackRouter, TabRouter has
* the correct format, and throw a clear error if it doesn't.
@ -8,7 +12,7 @@ function validateRouteConfigMap(routeConfigs: NavigationRouteConfigMap) {
const routeNames = Object.keys(routeConfigs);
invariant(
routeNames.length > 0,
'Please specify at least one route when configuring a navigator.'
'Please specify at least one route when configuring a navigator.',
);
routeNames.forEach((routeName: string) => {
@ -17,37 +21,38 @@ function validateRouteConfigMap(routeConfigs: NavigationRouteConfigMap) {
invariant(
routeConfig.screen || routeConfig.getScreen,
`Route '${routeName}' should declare a screen. ` +
'For example:\n\n' +
'import MyScreen from \'./MyScreen\';\n' +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
'}'
'For example:\n\n' +
"import MyScreen from './MyScreen';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
'}',
);
if (routeConfig.screen && routeConfig.getScreen) {
invariant(false,
invariant(
false,
`Route '${routeName}' should declare a screen or ` +
'a getScreen, not both.'
'a getScreen, not both.',
);
}
if (routeConfig.screen) {
invariant(
typeof (routeConfig.screen) === 'function',
typeof routeConfig.screen === 'function',
`The component for route '${routeName}' must be a ` +
'a React component. For example:\n\n' +
'import MyScreen from \'./MyScreen\';\n' +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
'}\n\n' +
'You can also use a navigator:\n\n' +
'import MyNavigator from \'./MyNavigator\';\n' +
'...\n' +
`${routeName}: {\n` +
' screen: MyNavigator,\n' +
'}'
'a React component. For example:\n\n' +
"import MyScreen from './MyScreen';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyScreen,\n' +
'}\n\n' +
'You can also use a navigator:\n\n' +
"import MyNavigator from './MyNavigator';\n" +
'...\n' +
`${routeName}: {\n` +
' screen: MyNavigator,\n' +
'}',
);
}
});

View File

@ -1,9 +1,7 @@
/* @flow */
import invariant from 'fbjs/lib/invariant';
import type {
NavigationRoute,
} from '../TypeDefinition';
import type { NavigationRoute } from '../TypeDefinition';
const deprecatedKeys = ['tabBar', 'header'];
@ -14,45 +12,43 @@ const deprecatedKeys = ['tabBar', 'header'];
export default (screenOptions: *, route: NavigationRoute) => {
const keys: Array<string> = Object.keys(screenOptions);
const deprecatedKey = keys.find(
(key: *) => deprecatedKeys.includes(key),
);
const deprecatedKey = keys.find((key: *) => deprecatedKeys.includes(key));
if (typeof screenOptions.title === 'function') {
invariant(
false,
[
`\`title\` cannot be defined as a function in navigation options for \`${route.routeName}\` screen. \n`,
'Try replacing the following:',
'{',
' title: ({ state }) => state...',
'}',
'',
'with:',
'({ navigation }) => ({',
' title: navigation.state...',
'})',
].join('\n'),
[
`\`title\` cannot be defined as a function in navigation options for \`${route.routeName}\` screen. \n`,
'Try replacing the following:',
'{',
' title: ({ state }) => state...',
'}',
'',
'with:',
'({ navigation }) => ({',
' title: navigation.state...',
'})',
].join('\n'),
);
}
if (deprecatedKey && typeof screenOptions[deprecatedKey] === 'function') {
invariant(
false,
[
`\`${deprecatedKey}\` cannot be defined as a function in navigation options for \`${route.routeName}\` screen. \n`,
'Try replacing the following:',
'{',
` ${deprecatedKey}: ({ state }) => ({`,
' key: state...',
' })',
'}',
'',
'with:',
'({ navigation }) => ({',
` ${deprecatedKey}Key: navigation.state...`,
'})',
].join('\n'),
[
`\`${deprecatedKey}\` cannot be defined as a function in navigation options for \`${route.routeName}\` screen. \n`,
'Try replacing the following:',
'{',
` ${deprecatedKey}: ({ state }) => ({`,
' key: state...',
' })',
'}',
'',
'with:',
'({ navigation }) => ({',
` ${deprecatedKey}Key: navigation.state...`,
'})',
].join('\n'),
);
}
@ -65,17 +61,18 @@ export default (screenOptions: *, route: NavigationRoute) => {
'Try replacing the following navigation options:',
'{',
` ${deprecatedKey}: {`,
...Object.keys(screenOptions[deprecatedKey]).map((key: string) => (
` ${key}: ...,`
)),
...Object.keys(screenOptions[deprecatedKey]).map(
(key: string) => ` ${key}: ...,`,
),
' },',
'}',
'\n',
'with:',
'{',
...Object.keys(screenOptions[deprecatedKey]).map((key: string) => (
` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,`
)),
...Object.keys(screenOptions[deprecatedKey]).map(
(key: string) =>
` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,`,
),
'}',
].join('\n'),
);

View File

@ -2,17 +2,11 @@
import React from 'react';
import {
Animated,
StyleSheet,
} from 'react-native';
import { Animated, StyleSheet } from 'react-native';
import createPointerEventsContainer from './PointerEventsContainer';
import type {
NavigationSceneRenderer,
NavigationSceneRendererProps,
} from '../TypeDefinition';
import type { NavigationSceneRendererProps } from '../TypeDefinition';
type Props = NavigationSceneRendererProps & {
children: React.Children<*>,
@ -31,7 +25,6 @@ class Card extends React.Component<any, Props, any> {
const {
children,
pointerEvents,
scene,
style,
} = this.props;
return (

View File

@ -1,15 +1,22 @@
/* @flow */
import React, { Component } from 'react';
import { Animated, StyleSheet, PanResponder, Platform, View, I18nManager } from 'react-native';
import clamp from 'clamp';
import {
Animated,
StyleSheet,
PanResponder,
Platform,
View,
I18nManager,
} from 'react-native';
import Card from './Card';
import NavigationActions from '../NavigationActions';
import addNavigationHelpers from '../addNavigationHelpers';
import SceneView from './SceneView';
import clamp from 'clamp';
import type {
NavigationAction,
NavigationLayout,
@ -77,6 +84,15 @@ const RESPOND_THRESHOLD = 12;
*/
const GESTURE_RESPONSE_DISTANCE = 35;
const animatedSubscribeValue = (animatedValue: Animated.Value) => {
if (!animatedValue.__isNative) {
return;
}
if (Object.keys(animatedValue._listeners).length === 0) {
animatedValue.addListener(emptyFunction);
}
};
/**
* The ratio between the gesture velocity and the animation velocity. This allows
* the velocity of a swipe release to carry on into the new animation.
@ -86,7 +102,6 @@ const GESTURE_RESPONSE_DISTANCE = 35;
const GESTURE_ANIMATED_VELOCITY_RATIO = -4;
class CardStack extends Component {
/**
* Used to identify the starting point of the position when the gesture starts, such that it can
* be updated according to its relative position. This means that a card can effectively be
@ -117,16 +132,17 @@ class CardStack extends Component {
if (props.screenProps !== this.props.screenProps) {
this._screenDetails = {};
}
props.scenes.forEach(newScene => {
if (this._screenDetails[newScene.key] && this._screenDetails[newScene.key].state !== newScene.route) {
props.scenes.forEach((newScene: *) => {
if (
this._screenDetails[newScene.key] &&
this._screenDetails[newScene.key].state !== newScene.route
) {
this._screenDetails[newScene.key] = null;
}
});
}
_getScreenDetails = (
scene: NavigationScene,
): NavigationScreenDetails<*> => {
_getScreenDetails = (scene: NavigationScene): NavigationScreenDetails<*> => {
const { screenProps, navigation, router } = this.props;
let screenDetails = this._screenDetails[scene.key];
if (!screenDetails || screenDetails.state !== scene.route) {
@ -146,7 +162,7 @@ class CardStack extends Component {
_renderHeader(
scene: NavigationScene,
headerMode: HeaderMode
headerMode: HeaderMode,
): ?React.Element<*> {
return (
<this.props.headerComponent
@ -158,7 +174,8 @@ class CardStack extends Component {
);
}
_animatedSubscribe(props) {
// eslint-disable-next-line class-methods-use-this
_animatedSubscribe(props: Props) {
// Hack to make this work with native driven animations. We add a single listener
// so the JS value of the following animated values gets updated. We rely on
// some Animated private APIs and not doing so would require using a bunch of
@ -166,32 +183,23 @@ class CardStack extends Component {
// when we'd do that with the current structure we have. `stopAnimation` callback
// is also broken with native animated values that have no listeners so if we
// want to remove this we have to fix this too.
this._animatedSubscribeValue(props.layout.width);
this._animatedSubscribeValue(props.layout.height);
this._animatedSubscribeValue(props.position);
}
_animatedSubscribeValue(animatedValue) {
if (!animatedValue.__isNative) {
return;
}
if (Object.keys(animatedValue._listeners).length === 0) {
animatedValue.addListener(emptyFunction);
}
animatedSubscribeValue(props.layout.width);
animatedSubscribeValue(props.layout.height);
animatedSubscribeValue(props.position);
}
_reset(resetToIndex: number, velocity: number): void {
Animated.timing(this.props.position, {
toValue: resetToIndex,
duration: ANIMATION_DURATION,
useNativeDriver: this.props.position.__isNative,
velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO,
bounciness: 0,
})
.start();
toValue: resetToIndex,
duration: ANIMATION_DURATION,
useNativeDriver: this.props.position.__isNative,
velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO,
bounciness: 0,
}).start();
}
_goBack(backFromIndex: number, velocity: number) {
const {navigation, position, scenes} = this.props;
const { navigation, position, scenes } = this.props;
const toValue = Math.max(backFromIndex - 1, 0);
// set temporary index for gesture handler to respect until the action is
@ -199,21 +207,20 @@ class CardStack extends Component {
this._immediateIndex = toValue;
Animated.timing(position, {
toValue,
duration: ANIMATION_DURATION,
useNativeDriver: position.__isNative,
velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO,
bounciness: 0,
})
.start(({finished}) => {
this._immediateIndex = null;
const backFromScene = scenes.find(s => s.index === toValue + 1);
if (!this._isResponding && backFromScene) {
navigation.dispatch(
NavigationActions.back({ key: backFromScene.route.key })
);
}
});
toValue,
duration: ANIMATION_DURATION,
useNativeDriver: position.__isNative,
velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO,
bounciness: 0,
}).start(() => {
this._immediateIndex = null;
const backFromScene = scenes.find((s: *) => s.index === toValue + 1);
if (!this._isResponding && backFromScene) {
navigation.dispatch(
NavigationActions.back({ key: backFromScene.route.key }),
);
}
});
}
render(): React.Element<*> {
@ -222,8 +229,8 @@ class CardStack extends Component {
if (headerMode === 'float') {
floatingHeader = this._renderHeader(this.props.scene, headerMode);
}
const {navigation, position, scene, mode, scenes} = this.props;
const {index} = navigation.state;
const { navigation, position, scene, mode, scenes } = this.props;
const { index } = navigation.state;
const responder = PanResponder.create({
onPanResponderTerminate: () => {
this._isResponding = false;
@ -237,14 +244,16 @@ class CardStack extends Component {
},
onMoveShouldSetPanResponder: (
event: { nativeEvent: { pageY: number, pageX: number } },
gesture: any
gesture: any,
) => {
const layout = this.props.layout;
if (index !== scene.index) {
return false;
}
const isVertical = false; // todo: bring back gestures for mode=modal
const immediateIndex = this._immediateIndex == null ? index : this._immediateIndex;
const immediateIndex = this._immediateIndex == null
? index
: this._immediateIndex;
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
const currentDragPosition = event.nativeEvent[
isVertical ? 'pageY' : 'pageX'
@ -252,7 +261,7 @@ class CardStack extends Component {
const axisLength = isVertical
? layout.height.__getValue()
: layout.width.__getValue();
const axisHasBeenMeasured = !! axisLength;
const axisHasBeenMeasured = !!axisLength;
// Measure the distance from the touch to the edge of the screen
const screenEdgeDistance = currentDragPosition - currentDragDistance;
@ -262,10 +271,13 @@ class CardStack extends Component {
return false;
}
const hasDraggedEnough = Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
const hasDraggedEnough = Math.abs(currentDragDistance) >
RESPOND_THRESHOLD;
const isOnFirstCard = immediateIndex === 0;
const shouldSetResponder = hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
const shouldSetResponder = hasDraggedEnough &&
axisHasBeenMeasured &&
!isOnFirstCard;
return shouldSetResponder;
},
onPanResponderMove: (event: any, gesture: any) => {
@ -283,20 +295,20 @@ class CardStack extends Component {
const value = clamp(index - 1, currentValue, index);
position.setValue(value);
},
onPanResponderTerminationRequest: (event: any, gesture: any) => {
onPanResponderTerminationRequest: () =>
// Returning false will prevent other views from becoming responder while
// the navigation view is the responder (mid-gesture)
return false;
},
false,
onPanResponderRelease: (event: any, gesture: any) => {
if (!this._isResponding) {
return;
}
this._isResponding = false;
const isVertical = false;
const axis = isVertical ? 'dy' : 'dx';
const velocity = gesture[isVertical ? 'vy' : 'vx'];
const immediateIndex = this._immediateIndex == null ? index : this._immediateIndex;
const immediateIndex = this._immediateIndex == null
? index
: this._immediateIndex;
// To asyncronously get the current animated value, we need to run stopAnimation:
position.stopAnimation((value: number) => {
@ -321,21 +333,19 @@ class CardStack extends Component {
});
},
});
const gesturesEnabled = mode === 'card' && Platform.OS === 'ios';
const handlers = gesturesEnabled ? responder.panHandlers : {};
return (
<View
{...handlers}
style={styles.container}>
<View {...handlers} style={styles.container}>
<View style={styles.scenes}>
{scenes.map((scene: NavigationScene) => this._renderCard(scene))}
{scenes.map((s: *) => this._renderCard(s))}
</View>
{floatingHeader}
</View>
);
};
}
_getHeaderMode(): HeaderMode {
if (this.props.headerMode) {
@ -383,25 +393,32 @@ class CardStack extends Component {
);
}
_renderCard = (scene): React.Element<*> => {
_renderCard = (scene: NavigationScene): React.Element<*> => {
const isModal = this.props.mode === 'modal';
/* $FlowFixMe */
const { screenInterpolator } = TransitionConfigs.getTransitionConfig(this.props.transitionConfig, {}, {}, isModal);
const style = screenInterpolator && screenInterpolator({...this.props, scene});
const { screenInterpolator } = TransitionConfigs.getTransitionConfig(
this.props.transitionConfig,
{},
{},
isModal,
);
const style = screenInterpolator &&
screenInterpolator({ ...this.props, scene });
const SceneComponent = this.props.router.getComponentForRouteName(
scene.route.routeName
scene.route.routeName,
);
return (
<Card
{...this.props}
key={`card_${scene.key}`}
children={this._renderInnerScene(SceneComponent, scene)}
style={[style, this.props.cardStyle]}
scene={scene}
/>
>
{this._renderInnerScene(SceneComponent, scene)}
</Card>
);
};
}

View File

@ -1,12 +1,8 @@
/* @flow */
import {
I18nManager,
} from 'react-native';
import { I18nManager } from 'react-native';
import type {
NavigationSceneRendererProps,
} from '../TypeDefinition';
import type { NavigationSceneRendererProps } from '../TypeDefinition';
/**
* Utility that builds the style for the card in the cards stack.
@ -38,10 +34,7 @@ function forInitial(props: NavigationSceneRendererProps): Object {
const translate = focused ? 0 : 1000000;
return {
opacity,
transform: [
{ translateX: translate },
{ translateY: translate },
],
transform: [{ translateX: translate }, { translateY: translate }],
};
}
@ -65,10 +58,9 @@ function forHorizontal(props: NavigationSceneRendererProps): Object {
// Add ~30px to the interpolated width screens width for horizontal movement. This allows
// the screen's shadow to go screen fully offscreen without abruptly dissapearing
const width = layout.initWidth + 30;
const outputRange = I18nManager.isRTL ?
([-width, 0, 10, 10]: Array<number>) :
([width, 0, -10, -10]: Array<number>);
const outputRange = I18nManager.isRTL
? ([-width, 0, 10, 10]: Array<number>)
: ([width, 0, -10, -10]: Array<number>);
const opacity = position.interpolate({
inputRange,
@ -83,10 +75,7 @@ function forHorizontal(props: NavigationSceneRendererProps): Object {
return {
opacity,
transform: [
{ translateX },
{ translateY },
],
transform: [{ translateX }, { translateY }],
};
}
@ -121,10 +110,7 @@ function forVertical(props: NavigationSceneRendererProps): Object {
return {
opacity,
transform: [
{ translateX },
{ translateY },
],
transform: [{ translateX }, { translateY }],
};
}
@ -144,7 +130,6 @@ function forFadeFromBottomAndroid(props: NavigationSceneRendererProps): Object {
const index = scene.index;
const inputRange = [index - 1, index, index + 0.99, index + 1];
const height = layout.initHeight;
const opacity = position.interpolate({
inputRange,
@ -159,14 +144,11 @@ function forFadeFromBottomAndroid(props: NavigationSceneRendererProps): Object {
return {
opacity,
transform: [
{ translateX },
{ translateY },
],
transform: [{ translateX }, { translateY }],
};
}
function canUseNativeDriver(isVertical: boolean): boolean {
function canUseNativeDriver(): boolean {
// The native driver can be enabled for this interpolator animating
// opacity, translateX, and translateY is supported by the native animation
// driver on iOS and Android.

View File

@ -1,7 +1,7 @@
/* @flow */
import React, { Component } from 'react';
import { NativeModules, View } from 'react-native';
import { NativeModules } from 'react-native';
import CardStack from './CardStack';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
@ -9,8 +9,7 @@ import Transitioner from './Transitioner';
import TransitionConfigs from './TransitionConfigs';
import Header from './Header';
const NativeAnimatedModule = NativeModules &&
NativeModules.NativeAnimatedModule;
import type { TransitionConfig } from './TransitionConfigs';
import type {
NavigationAction,
@ -24,7 +23,8 @@ import type {
Style,
} from '../TypeDefinition';
import type { TransitionConfig } from './TransitionConfigs';
const NativeAnimatedModule = NativeModules &&
NativeModules.NativeAnimatedModule;
type Props = {
screenProps?: {},
@ -73,7 +73,7 @@ class CardStackTransitioner extends Component<DefaultProps, Props, void> {
// props for the new screen
transitionProps: NavigationTransitionProps,
// props for the old screen
prevTransitionProps: NavigationTransitionProps
prevTransitionProps: NavigationTransitionProps,
) => {
const isModal = this.props.mode === 'modal';
// Copy the object so we can assign useNativeDriver below

View File

@ -1,12 +1,7 @@
/* @flow */
import React from 'react';
import {
View,
Text,
Platform,
StyleSheet,
} from 'react-native';
import { View, Text, Platform, StyleSheet } from 'react-native';
import TouchableItem from '../TouchableItem';
@ -16,41 +11,43 @@ import type {
NavigationAction,
Style,
} from '../../TypeDefinition';
import type {
DrawerScene,
} from './DrawerView.js';
import type { DrawerScene } from './DrawerView.js';
type Props = {
navigation: NavigationScreenProp<NavigationState, NavigationAction>;
activeTintColor?: string;
activeBackgroundColor?: string;
inactiveTintColor?: string;
inactiveBackgroundColor?: string;
getLabel: (scene: DrawerScene) => ?(React.Element<*> | string);
renderIcon: (scene: DrawerScene) => ?React.Element<*>;
style?: Style;
labelStyle?: Style;
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
activeTintColor?: string,
activeBackgroundColor?: string,
inactiveTintColor?: string,
inactiveBackgroundColor?: string,
getLabel: (scene: DrawerScene) => ?(React.Element<*> | string),
renderIcon: (scene: DrawerScene) => ?React.Element<*>,
style?: Style,
labelStyle?: Style,
};
/**
* Component that renders the navigation list in the drawer.
*/
const DrawerNavigatorItems = ({
navigation,
activeTintColor,
activeBackgroundColor,
inactiveTintColor,
inactiveBackgroundColor,
getLabel,
renderIcon,
style,
labelStyle,
}: Props) => (
const DrawerNavigatorItems = (
{
navigation,
activeTintColor,
activeBackgroundColor,
inactiveTintColor,
inactiveBackgroundColor,
getLabel,
renderIcon,
style,
labelStyle,
}: Props,
) => (
<View style={[styles.container, style]}>
{navigation.state.routes.map((route: *, index: number) => {
const focused = navigation.state.index === index;
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused ? activeBackgroundColor : inactiveBackgroundColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const scene = { route, index, focused, tintColor: color };
const icon = renderIcon(scene);
const label = getLabel(scene);
@ -64,19 +61,18 @@ const DrawerNavigatorItems = ({
delayPressIn={0}
>
<View style={[styles.item, { backgroundColor }]}>
{icon ? (
<View style={[styles.icon, focused ? null : styles.inactiveIcon]}>
{icon}
</View>
) : null}
{icon
? <View
style={[styles.icon, focused ? null : styles.inactiveIcon]}
>
{icon}
</View>
: null}
{typeof label === 'string'
? (
<Text style={[styles.label, { color }, labelStyle]}>
? <Text style={[styles.label, { color }, labelStyle]}>
{label}
</Text>
)
: label
}
: label}
</View>
</TouchableItem>
);

View File

@ -15,11 +15,11 @@ import type {
} from '../../TypeDefinition';
type Props = {
screenProps?: {};
screenProps?: {},
router: NavigationRouter<NavigationState, NavigationAction, NavigationDrawerScreenOptions>,
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
childNavigationProps: {
[key: string]: NavigationScreenProp<NavigationRoute, NavigationAction>;
[key: string]: NavigationScreenProp<NavigationRoute, NavigationAction>,
},
};
@ -30,7 +30,12 @@ class DrawerScreen extends PureComponent<void, Props, void> {
props: Props;
render() {
const { router, navigation, childNavigationProps, screenProps } = this.props;
const {
router,
navigation,
childNavigationProps,
screenProps,
} = this.props;
const { routes, index } = navigation.state;
const childNavigation = childNavigationProps[routes[index].key];
const Content = router.getComponentForRouteName(routes[index].routeName);
@ -39,7 +44,10 @@ class DrawerScreen extends PureComponent<void, Props, void> {
screenProps={screenProps}
component={Content}
navigation={childNavigation}
navigationOptions={router.getScreenOptions(childNavigation, screenProps)}
navigationOptions={router.getScreenOptions(
childNavigation,
screenProps,
)}
/>
);
}

View File

@ -1,11 +1,7 @@
/* @flow */
import React, { PureComponent } from 'react';
import {
Platform,
StyleSheet,
View,
} from 'react-native';
import { StyleSheet, View } from 'react-native';
import withCachedChildNavigation from '../../withCachedChildNavigation';
@ -19,9 +15,7 @@ import type {
Style,
} from '../../TypeDefinition';
import type {
DrawerScene,
} from './DrawerView';
import type { DrawerScene } from './DrawerView';
type Navigation = NavigationScreenProp<NavigationRoute, NavigationAction>;
@ -32,7 +26,7 @@ type Props = {
contentComponent: ReactClass<*>,
contentOptions?: {},
screenProps?: {},
style?: Style;
style?: Style,
};
/**
@ -42,15 +36,17 @@ class DrawerSidebar extends PureComponent<void, Props, void> {
props: Props;
_getScreenOptions = (routeKey: string) => {
const DrawerScreen = this.props.router.getComponentForRouteName('DrawerClose');
const DrawerScreen = this.props.router.getComponentForRouteName(
'DrawerClose',
);
return DrawerScreen.router.getScreenOptions(
this.props.childNavigationProps[routeKey],
this.props.screenProps
this.props.screenProps,
);
}
};
_getLabel = ({ focused, tintColor, route }: DrawerScene) => {
const {drawerLabel, title} = this._getScreenOptions(route.key);
const { drawerLabel, title } = this._getScreenOptions(route.key);
if (drawerLabel) {
return typeof drawerLabel === 'function'
? drawerLabel({ tintColor, focused })
@ -65,7 +61,7 @@ class DrawerSidebar extends PureComponent<void, Props, void> {
};
_renderIcon = ({ focused, tintColor, route }: DrawerScene) => {
const {drawerIcon} = this._getScreenOptions(route.key);
const { drawerIcon } = this._getScreenOptions(route.key);
if (drawerIcon) {
return typeof drawerIcon === 'function'
? drawerIcon({ tintColor, focused })

View File

@ -18,10 +18,10 @@ import type {
} from '../../TypeDefinition';
export type DrawerScene = {
route: NavigationRoute;
focused: boolean;
index: number;
tintColor?: string;
route: NavigationRoute,
focused: boolean,
index: number,
tintColor?: string,
};
export type DrawerViewConfig = {
@ -29,11 +29,11 @@ export type DrawerViewConfig = {
drawerPosition: 'left' | 'right',
contentComponent: ReactClass<*>,
contentOptions?: {},
style?: Style;
style?: Style,
};
type Props = DrawerViewConfig & {
screenProps?: {};
screenProps?: {},
router: NavigationRouter<NavigationState, NavigationAction, NavigationDrawerScreenOptions>,
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
};
@ -42,7 +42,6 @@ type Props = DrawerViewConfig & {
* Component that renders the drawer.
*/
export default class DrawerView<T: *> extends PureComponent<void, Props, void> {
static Items = DrawerNavigatorItems;
props: Props;
@ -52,7 +51,9 @@ export default class DrawerView<T: *> extends PureComponent<void, Props, void> {
}
componentWillReceiveProps(nextProps: Props) {
if (this.props.navigation.state.index !== nextProps.navigation.state.index) {
if (
this.props.navigation.state.index !== nextProps.navigation.state.index
) {
const { routes, index } = nextProps.navigation.state;
if (routes[index].routeName === 'DrawerOpen') {
this._drawer.openDrawer();
@ -82,22 +83,29 @@ export default class DrawerView<T: *> extends PureComponent<void, Props, void> {
};
_updateScreenNavigation = (
navigation: NavigationScreenProp<NavigationState, NavigationAction>
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
) => {
const navigationState = navigation.state.routes.find((route: *) => route.routeName === 'DrawerClose');
if (this._screenNavigationProp && this._screenNavigationProp.state === navigationState) {
const navigationState = navigation.state.routes.find(
(route: *) => route.routeName === 'DrawerClose',
);
if (
this._screenNavigationProp &&
this._screenNavigationProp.state === navigationState
) {
return;
}
this._screenNavigationProp = addNavigationHelpers({
...navigation,
state: navigationState,
});
}
};
_getNavigationState = (
navigation: NavigationScreenProp<NavigationState, NavigationAction>
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
) => {
const navigationState = navigation.state.routes.find((route: *) => route.routeName === 'DrawerClose');
const navigationState = navigation.state.routes.find(
(route: *) => route.routeName === 'DrawerClose',
);
return navigationState;
};
@ -115,17 +123,22 @@ export default class DrawerView<T: *> extends PureComponent<void, Props, void> {
_drawer: any;
render() {
const DrawerScreen = this.props.router.getComponentForRouteName('DrawerClose');
const DrawerScreen = this.props.router.getComponentForRouteName(
'DrawerClose',
);
return (
<DrawerLayout
ref={(c: *) => (this._drawer = c)}
ref={(c: *) => {
this._drawer = c;
}}
drawerWidth={this.props.drawerWidth}
onDrawerOpen={this._handleDrawerOpen}
onDrawerClose={this._handleDrawerClose}
renderNavigationView={this._renderNavigationView}
drawerPosition={
this.props.drawerPosition === 'right' ?
DrawerLayout.positions.Right : DrawerLayout.positions.Left
this.props.drawerPosition === 'right'
? DrawerLayout.positions.Right
: DrawerLayout.positions.Left
}
>
<DrawerScreen

View File

@ -4,12 +4,7 @@
import React from 'react';
import {
Animated,
Platform,
StyleSheet,
View,
} from 'react-native';
import { Animated, Platform, StyleSheet, View } from 'react-native';
import HeaderTitle from './HeaderTitle';
import HeaderBackButton from './HeaderBackButton';
@ -60,7 +55,7 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
}
_getLastScene(scene: NavigationScene): ?NavigationScene {
return this.props.scenes.find(s => s.index === scene.index - 1);
return this.props.scenes.find((s: *) => s.index === scene.index - 1);
}
_getBackButtonTitleString(scene: NavigationScene): ?string {
@ -80,7 +75,9 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
if (!lastScene) {
return null;
}
return this.props.getScreenDetails(lastScene).options.headerTruncatedBackTitle;
return this.props.getScreenDetails(
lastScene,
).options.headerTruncatedBackTitle;
}
_renderTitleComponent = (props: SceneProps) => {
@ -98,13 +95,13 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
// size of the title.
const onLayoutIOS = Platform.OS === 'ios'
? (e: LayoutEvent) => {
this.setState({
widths: {
...this.state.widths,
[props.scene.key]: e.nativeEvent.layout.width,
},
});
}
this.setState({
widths: {
...this.state.widths,
[props.scene.key]: e.nativeEvent.layout.width,
},
});
}
: undefined;
return (
@ -126,13 +123,17 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
return null;
}
const backButtonTitle = this._getBackButtonTitleString(props.scene);
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(props.scene);
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
props.scene,
);
const width = this.state.widths[props.scene.key]
? (this.props.layout.initWidth - this.state.widths[props.scene.key]) / 2
: undefined;
return (
<HeaderBackButton
onPress={() => { this.props.navigation.goBack(null); }}
onPress={() => {
this.props.navigation.goBack(null);
}}
pressColorAndroid={options.headerPressColorAndroid}
tintColor={options.headerTintColor}
title={backButtonTitle}
@ -144,7 +145,7 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
_renderRightComponent = (props: SceneProps) => {
const details = this.props.getScreenDetails(props.scene);
const {headerRight} = details.options;
const { headerRight } = details.options;
return headerRight || null;
};
@ -261,12 +262,13 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
let appBar;
if (this.props.mode === 'float') {
const scenesProps: Array<SceneProps> = this.props.scenes
.map((scene: NavigationScene) => ({
const scenesProps: Array<SceneProps> = this.props.scenes.map(
(scene: NavigationScene) => ({
position: this.props.position,
progress: this.props.progress,
scene,
}));
}),
);
appBar = scenesProps.map(this._renderHeader, this);
} else {
appBar = this._renderHeader({
@ -277,7 +279,14 @@ class Header extends React.PureComponent<void, HeaderProps, HeaderState> {
}
// eslint-disable-next-line no-unused-vars
const { scenes, scene, position, screenProps, progress, ...rest } = this.props;
const {
scenes,
scene,
position,
screenProps,
progress,
...rest
} = this.props;
const { options } = this.props.getScreenDetails(scene, screenProps);
const style = options.headerStyle;
@ -322,9 +331,7 @@ const styles = StyleSheet.create({
right: TITLE_OFFSET,
top: 0,
position: 'absolute',
alignItems: Platform.OS === 'ios'
? 'center'
: 'flex-start',
alignItems: Platform.OS === 'ios' ? 'center' : 'flex-start',
},
left: {
left: 0,

View File

@ -54,12 +54,22 @@ class HeaderBackButton extends React.PureComponent<DefaultProps, Props, State> {
};
render() {
const { onPress, pressColorAndroid, width, title, tintColor, truncatedTitle } = this.props;
const {
onPress,
pressColorAndroid,
width,
title,
tintColor,
truncatedTitle,
} = this.props;
const renderTruncated = this.state.initialTextWidth && width
? this.state.initialTextWidth > width
: false;
// eslint-disable-next-line global-require
const asset = require('./assets/back-icon.png');
return (
<TouchableItem
delayPressIn={0}
@ -70,22 +80,18 @@ class HeaderBackButton extends React.PureComponent<DefaultProps, Props, State> {
>
<View style={styles.container}>
<Image
style={[
styles.icon,
title && styles.iconWithTitle,
{ tintColor },
]}
source={require('./assets/back-icon.png')}
style={[styles.icon, title && styles.iconWithTitle, { tintColor }]}
source={asset}
/>
{Platform.OS === 'ios' && title && (
{Platform.OS === 'ios' &&
title &&
<Text
onLayout={this._onTextLayout}
style={[styles.title, { color: tintColor }]}
numberOfLines={1}
>
{renderTruncated ? truncatedTitle : title}
</Text>
)}
</Text>}
</View>
</TouchableItem>
);
@ -104,25 +110,25 @@ const styles = StyleSheet.create({
},
icon: Platform.OS === 'ios'
? {
height: 20,
width: 12,
marginLeft: 10,
marginRight: 22,
marginVertical: 12,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
}
height: 20,
width: 12,
marginLeft: 10,
marginRight: 22,
marginVertical: 12,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
}
: {
height: 24,
width: 24,
margin: 16,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
height: 24,
width: 24,
margin: 16,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
iconWithTitle: Platform.OS === 'ios'
? {
marginRight: 5,
}
marginRight: 5,
}
: {},
});

View File

@ -1,12 +1,8 @@
/* @flow */
import {
I18nManager,
} from 'react-native';
import { I18nManager } from 'react-native';
import type {
NavigationSceneRendererProps,
} from '../TypeDefinition';
import type { NavigationSceneRendererProps } from '../TypeDefinition';
/**
* Utility that builds the style for the navigation header.
@ -42,9 +38,9 @@ function forCenter(props: NavigationSceneRendererProps): Object {
{
translateX: position.interpolate({
inputRange: [index - 1, index + 1],
outputRange: I18nManager.isRTL ?
([-200, 200]: Array<number>) :
([200, -200]: Array<number>),
outputRange: I18nManager.isRTL
? ([-200, 200]: Array<number>)
: ([200, -200]: Array<number>),
}),
},
],

View File

@ -2,23 +2,22 @@
import React from 'react';
import {
Platform,
StyleSheet,
Text,
} from 'react-native';
import { Platform, StyleSheet, Text } from 'react-native';
import type {
Style,
} from '../TypeDefinition';
import type { Style } from '../TypeDefinition';
type Props = {
tintColor?: ?string;
tintColor?: ?string,
style?: Style,
};
const HeaderTitle = ({ style, ...rest }: Props) => (
<Text numberOfLines={1} {...rest} style={[styles.title, style]} accessibilityTraits="header" />
<Text
numberOfLines={1}
{...rest}
style={[styles.title, style]}
accessibilityTraits="header"
/>
);
const styles = StyleSheet.create({

View File

@ -6,9 +6,7 @@ import invariant from 'fbjs/lib/invariant';
import AnimatedValueSubscription from './AnimatedValueSubscription';
import type {
NavigationSceneRendererProps,
} from '../TypeDefinition';
import type { NavigationSceneRendererProps } from '../TypeDefinition';
type Props = NavigationSceneRendererProps;
@ -19,13 +17,11 @@ const MIN_POSITION_OFFSET = 0.01;
* `pointerEvents` property for a component whenever navigation position
* changes.
*/
export default function create(
Component: ReactClass<*>,
): ReactClass<*> {
export default function create(Component: ReactClass<*>): ReactClass<*> {
class Container extends React.Component<any, Props, any> {
_component: any;
_onComponentRef: (view: any) => void;
_onPositionChange: (data: {value: number}) => void;
_onPositionChange: (data: { value: number }) => void;
_pointerEvents: string;
_positionListener: ?AnimatedValueSubscription;
@ -101,9 +97,7 @@ export default function create(
if (scene.isStale || navigation.state.index !== scene.index) {
// The scene isn't focused.
return scene.index > navigation.state.index ?
'box-only' :
'none';
return scene.index > navigation.state.index ? 'box-only' : 'none';
}
const offset = position.__getAnimatedValue() - navigation.state.index;

View File

@ -11,10 +11,10 @@ import type {
} from '../TypeDefinition';
type Props = {
screenProps?: {};
navigation: NavigationScreenProp<NavigationRoute, NavigationAction>;
screenProps?: {},
navigation: NavigationScreenProp<NavigationRoute, NavigationAction>,
navigationOptions: *,
component: ReactClass<NavigationNavigatorProps<NavigationRoute>>;
component: ReactClass<NavigationNavigatorProps<NavigationRoute>>,
};
export default class SceneView extends PureComponent<void, Props, void> {

View File

@ -28,10 +28,7 @@ function compareKey(one: string, two: string): number {
/**
* Helper function to sort scenes based on their index and view key.
*/
function compareScenes(
one: NavigationScene,
two: NavigationScene,
): number {
function compareScenes(one: NavigationScene, two: NavigationScene): number {
if (one.index > two.index) {
return 1;
}
@ -39,10 +36,7 @@ function compareScenes(
return -1;
}
return compareKey(
one.key,
two.key,
);
return compareKey(one.key, two.key);
}
/**
@ -52,13 +46,11 @@ function areScenesShallowEqual(
one: NavigationScene,
two: NavigationScene,
): boolean {
return (
one.key === two.key &&
return one.key === two.key &&
one.index === two.index &&
one.isStale === two.isStale &&
one.isActive === two.isActive &&
areRoutesShallowEqual(one.route, two.route)
);
areRoutesShallowEqual(one.route, two.route);
}
/**
@ -93,7 +85,7 @@ export default function ScenesReducer(
const staleScenes: Map<string, NavigationScene> = new Map();
// Populate stale scenes from previous scenes marked as stale.
scenes.forEach((scene) => {
scenes.forEach((scene: *) => {
const { key } = scene;
if (scene.isStale) {
staleScenes.set(key, scene);
@ -102,7 +94,7 @@ export default function ScenesReducer(
});
const nextKeys = new Set();
nextState.routes.forEach((route, index) => {
nextState.routes.forEach((route: *, index: *) => {
const key = SCENE_KEY_PREFIX + route.key;
const scene = {
index,
@ -114,7 +106,7 @@ export default function ScenesReducer(
invariant(
!nextKeys.has(key),
`navigation.state.routes[${index}].key "${key}" conflicts with ` +
'another route!'
'another route!',
);
nextKeys.add(key);
@ -128,7 +120,7 @@ export default function ScenesReducer(
if (prevState) {
// Look at the previous routes and classify any removed scenes as `stale`.
prevState.routes.forEach((route: NavigationRoute, index) => {
prevState.routes.forEach((route: NavigationRoute, index: *) => {
const key = SCENE_KEY_PREFIX + route.key;
if (freshScenes.has(key)) {
return;
@ -145,7 +137,7 @@ export default function ScenesReducer(
const nextScenes = [];
const mergeScene = ((nextScene) => {
const mergeScene = (nextScene: *) => {
const { key } = nextScene;
const prevScene = prevScenes.has(key) ? prevScenes.get(key) : null;
if (prevScene && areScenesShallowEqual(prevScene, nextScene)) {
@ -155,7 +147,7 @@ export default function ScenesReducer(
} else {
nextScenes.push(nextScene);
}
});
};
staleScenes.forEach(mergeScene);
freshScenes.forEach(mergeScene);
@ -163,7 +155,7 @@ export default function ScenesReducer(
nextScenes.sort(compareScenes);
let activeScenesCount = 0;
nextScenes.forEach((scene, ii) => {
nextScenes.forEach((scene: *, ii: *) => {
const isActive = !scene.isStale && scene.index === nextState.index;
if (isActive !== scene.isActive) {
nextScenes[ii] = {
@ -186,9 +178,11 @@ export default function ScenesReducer(
return nextScenes;
}
if (nextScenes.some(
(scene, index) => !areScenesShallowEqual(scenes[index], scene)
)) {
if (
nextScenes.some(
(scene: *, index: *) => !areScenesShallowEqual(scenes[index], scene),
)
) {
return nextScenes;
}

View File

@ -17,36 +17,34 @@ import type {
Style,
} from '../../TypeDefinition';
import type {
TabScene,
} from './TabView';
import type { TabScene } from './TabView';
type DefaultProps = {
activeTintColor: string;
activeBackgroundColor: string;
inactiveTintColor: string;
inactiveBackgroundColor: string;
showLabel: boolean;
activeTintColor: string,
activeBackgroundColor: string,
inactiveTintColor: string,
inactiveBackgroundColor: string,
showLabel: boolean,
};
type Props = {
activeTintColor: string;
activeBackgroundColor: string;
inactiveTintColor: string;
inactiveBackgroundColor: string;
position: Animated.Value;
activeTintColor: string,
activeBackgroundColor: string,
inactiveTintColor: string,
inactiveBackgroundColor: string,
position: Animated.Value,
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
jumpToIndex: (index: number) => void;
getLabel: (scene: TabScene) => ?(React.Element<*> | string);
renderIcon: (scene: TabScene) => React.Element<*>;
showLabel: boolean;
style?: Style;
labelStyle?: Style;
showIcon: boolean;
jumpToIndex: (index: number) => void,
getLabel: (scene: TabScene) => ?(React.Element<*> | string),
renderIcon: (scene: TabScene) => React.Element<*>,
showLabel: boolean,
style?: Style,
labelStyle?: Style,
showIcon: boolean,
};
export default class TabBarBottom extends PureComponent<DefaultProps, Props, void> {
export default class TabBarBottom
extends PureComponent<DefaultProps, Props, void> {
// See https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UITabBar.html
static defaultProps = {
activeTintColor: '#3478f6', // Default active tint color in iOS 10
@ -75,8 +73,9 @@ export default class TabBarBottom extends PureComponent<DefaultProps, Props, voi
const { routes } = navigation.state;
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x: *, i: number) => i)];
const outputRange = inputRange.map((inputIndex: number) =>
(inputIndex === index ? activeTintColor : inactiveTintColor)
const outputRange = inputRange.map(
(inputIndex: number) =>
inputIndex === index ? activeTintColor : inactiveTintColor,
);
const color = position.interpolate({
inputRange,
@ -140,8 +139,11 @@ export default class TabBarBottom extends PureComponent<DefaultProps, Props, voi
{routes.map((route: NavigationRoute, index: number) => {
const focused = index === navigation.state.index;
const scene = { route, index, focused };
const outputRange = inputRange.map((inputIndex: number) =>
(inputIndex === index ? activeBackgroundColor : inactiveBackgroundColor)
const outputRange = inputRange.map(
(inputIndex: number) =>
inputIndex === index
? activeBackgroundColor
: inactiveBackgroundColor,
);
const backgroundColor = position.interpolate({
inputRange,
@ -149,8 +151,13 @@ export default class TabBarBottom extends PureComponent<DefaultProps, Props, voi
});
const justifyContent = this.props.showIcon ? 'flex-end' : 'center';
return (
<TouchableWithoutFeedback key={route.key} onPress={() => jumpToIndex(index)}>
<Animated.View style={[styles.tab, { backgroundColor, justifyContent }]}>
<TouchableWithoutFeedback
key={route.key}
onPress={() => jumpToIndex(index)}
>
<Animated.View
style={[styles.tab, { backgroundColor, justifyContent }]}
>
{this._renderIcon(scene)}
{this._renderLabel(scene)}
</Animated.View>

View File

@ -1,11 +1,7 @@
/* @flow */
import React, { PureComponent } from 'react';
import {
Animated,
View,
StyleSheet,
} from 'react-native';
import { Animated, View, StyleSheet } from 'react-native';
import type {
NavigationAction,
@ -14,9 +10,7 @@ import type {
Style,
} from '../../TypeDefinition';
import type {
TabScene,
} from './TabView';
import type { TabScene } from './TabView';
type Props = {
activeTintColor: string,
@ -46,11 +40,11 @@ export default class TabBarIcon extends PureComponent<void, Props, void> {
const inputRange = [-1, ...routes.map((x: *, i: number) => i)];
const activeOpacity = position.interpolate({
inputRange,
outputRange: inputRange.map((i: number) => (i === index ? 1 : 0)),
outputRange: inputRange.map((i: number) => i === index ? 1 : 0),
});
const inactiveOpacity = position.interpolate({
inputRange,
outputRange: inputRange.map((i: number) => (i === index ? 0 : 1)),
outputRange: inputRange.map((i: number) => i === index ? 0 : 1),
});
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.

View File

@ -1,10 +1,7 @@
/* @flow */
import React, { PureComponent } from 'react';
import {
Animated,
StyleSheet,
} from 'react-native';
import { Animated, StyleSheet } from 'react-native';
import { TabBar } from 'react-native-tab-view';
import TabBarIcon from './TabBarIcon';
@ -15,34 +12,32 @@ import type {
Style,
} from '../../TypeDefinition';
import type {
TabScene,
} from './TabView';
import type { TabScene } from './TabView';
type DefaultProps = {
activeTintColor: string;
inactiveTintColor: string;
showIcon: boolean;
showLabel: boolean;
upperCaseLabel: boolean;
activeTintColor: string,
inactiveTintColor: string,
showIcon: boolean,
showLabel: boolean,
upperCaseLabel: boolean,
};
type Props = {
activeTintColor: string;
inactiveTintColor: string;
showIcon: boolean;
showLabel: boolean;
upperCaseLabel: boolean;
position: Animated.Value;
navigation: NavigationScreenProp<NavigationState, NavigationAction>;
getLabel: (scene: TabScene) => ?(React.Element<*> | string);
renderIcon: (scene: TabScene) => React.Element<*>;
labelStyle?: Style;
iconStyle?: Style;
activeTintColor: string,
inactiveTintColor: string,
showIcon: boolean,
showLabel: boolean,
upperCaseLabel: boolean,
position: Animated.Value,
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
getLabel: (scene: TabScene) => ?(React.Element<*> | string),
renderIcon: (scene: TabScene) => React.Element<*>,
labelStyle?: Style,
iconStyle?: Style,
};
export default class TabBarTop extends PureComponent<DefaultProps, Props, void> {
export default class TabBarTop
extends PureComponent<DefaultProps, Props, void> {
static defaultProps = {
activeTintColor: '#fff',
inactiveTintColor: '#fff',
@ -70,8 +65,9 @@ export default class TabBarTop extends PureComponent<DefaultProps, Props, void>
const { routes } = navigation.state;
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x: *, i: number) => i)];
const outputRange = inputRange.map((inputIndex: number) =>
(inputIndex === index ? activeTintColor : inactiveTintColor)
const outputRange = inputRange.map(
(inputIndex: number) =>
inputIndex === index ? activeTintColor : inactiveTintColor,
);
const color = position.interpolate({
inputRange,

View File

@ -1,10 +1,7 @@
/* @flow */
import React, { PureComponent } from 'react';
import {
Platform,
StyleSheet,
} from 'react-native';
import { Platform, StyleSheet } from 'react-native';
import {
TabViewAnimated,
TabViewPagerAndroid,
@ -26,33 +23,35 @@ import type {
} from '../../TypeDefinition';
export type TabViewConfig = {
tabBarComponent?: ReactClass<*>;
tabBarPosition?: 'top' | 'bottom';
tabBarOptions?: {};
swipeEnabled?: boolean;
animationEnabled?: boolean;
lazyLoad?: boolean;
tabBarComponent?: ReactClass<*>,
tabBarPosition?: 'top' | 'bottom',
tabBarOptions?: {},
swipeEnabled?: boolean,
animationEnabled?: boolean,
lazyLoad?: boolean,
};
export type TabScene = {
route: NavigationRoute;
focused: boolean;
index: number;
tintColor?: ?string;
route: NavigationRoute,
focused: boolean,
index: number,
tintColor?: ?string,
};
type Props = {
tabBarComponent?: ReactClass<*>;
tabBarPosition?: 'top' | 'bottom';
tabBarOptions?: {};
swipeEnabled?: boolean;
animationEnabled?: boolean;
lazyLoad?: boolean;
tabBarComponent?: ReactClass<*>,
tabBarPosition?: 'top' | 'bottom',
tabBarOptions?: {},
swipeEnabled?: boolean,
animationEnabled?: boolean,
lazyLoad?: boolean,
screenProps?: {},
navigation: NavigationScreenProp<NavigationState, NavigationAction>;
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
router: NavigationRouter<NavigationState, NavigationAction, NavigationTabScreenOptions>,
childNavigationProps: { [key: string]: NavigationScreenProp<NavigationRoute, NavigationAction> },
childNavigationProps: {
[key: string]: NavigationScreenProp<NavigationRoute, NavigationAction>,
},
};
let TabViewPager;
@ -69,7 +68,6 @@ switch (Platform.OS) {
}
class TabView extends PureComponent<void, Props, void> {
static TabBarTop = TabBarTop;
static TabBarBottom = TabBarBottom;
@ -77,28 +75,32 @@ class TabView extends PureComponent<void, Props, void> {
_handlePageChanged = (index: number) => {
const { navigation } = this.props;
navigation.navigate(
navigation.state.routes[index].routeName);
navigation.navigate(navigation.state.routes[index].routeName);
};
_renderScene = ({ route }: any) => {
const { screenProps } = this.props;
const childNavigation = this.props.childNavigationProps[route.key];
const TabComponent = this.props.router.getComponentForRouteName(route.routeName);
const TabComponent = this.props.router.getComponentForRouteName(
route.routeName,
);
return (
<SceneView
screenProps={screenProps}
component={TabComponent}
navigation={childNavigation}
navigationOptions={this.props.router.getScreenOptions(childNavigation, screenProps)}
navigationOptions={this.props.router.getScreenOptions(
childNavigation,
screenProps,
)}
/>
);
};
_getLabel = ({ focused, route, tintColor }: TabScene) => {
_getLabel = ({ route, tintColor, focused }: TabScene) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
this.props.screenProps || {},
);
if (options.tabBarLabel) {
@ -117,7 +119,7 @@ class TabView extends PureComponent<void, Props, void> {
_renderIcon = ({ focused, route, tintColor }: TabScene) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
this.props.screenProps || {},
);
if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
@ -168,7 +170,6 @@ class TabView extends PureComponent<void, Props, void> {
render() {
const {
router,
navigation,
tabBarComponent,
tabBarPosition,
animationEnabled,
@ -182,10 +183,12 @@ class TabView extends PureComponent<void, Props, void> {
const { state } = this.props.navigation;
const options = router.getScreenOptions(
this.props.childNavigationProps[state.routes[state.index].key],
screenProps || {}
screenProps || {},
);
const tabBarVisible = options.tabBarVisible == null ? true : options.tabBarVisible;
const tabBarVisible = options.tabBarVisible == null
? true
: options.tabBarVisible;
if (tabBarComponent !== undefined && tabBarVisible) {
if (tabBarPosition === 'bottom') {
@ -201,21 +204,21 @@ class TabView extends PureComponent<void, Props, void> {
configureTransition = this._configureTransition;
}
return (
/* $FlowFixMe */
<TabViewAnimated
style={styles.container}
navigationState={this.props.navigation.state}
lazy={lazyLoad}
renderHeader={renderHeader}
renderFooter={renderFooter}
renderScene={this._renderScene}
renderPager={this._renderPager}
configureTransition={configureTransition}
onRequestChangeTab={this._handlePageChanged}
screenProps={this.props.screenProps}
/>
);
const props = {
style: styles.container,
navigationState: this.props.navigation.state,
lazy: lazyLoad,
renderHeader,
renderFooter,
renderScene: this._renderScene,
renderPager: this._renderPager,
configureTransition,
onRequestChangeTab: this._handlePageChanged,
screenProps: this.props.screenProps,
};
/* $FlowFixMe */
return <TabViewAnimated {...props} />;
}
}

View File

@ -16,27 +16,26 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import type {
Style,
} from '../TypeDefinition';
import type { Style } from '../TypeDefinition';
const ANDROID_VERSION_LOLLIPOP = 21;
type Props = {
onPress: Function,
delayPressIn?: number;
borderless?: boolean;
pressColor?: ?string;
activeOpacity?: number;
children?: React.Element<*>;
style?: Style;
delayPressIn?: number,
borderless?: boolean,
pressColor?: ?string,
activeOpacity?: number,
children?: React.Element<*>,
style?: Style,
};
type DefaultProps = {
pressColor: ?string;
pressColor: ?string,
};
export default class TouchableItem extends Component<DefaultProps, Props, void> {
export default class TouchableItem
extends Component<DefaultProps, Props, void> {
static defaultProps = {
pressColor: 'rgba(0, 0, 0, .32)',
};
@ -50,19 +49,19 @@ export default class TouchableItem extends Component<DefaultProps, Props, void>
* platform design guidelines.
* We need to pass the background prop to specify a borderless ripple effect.
*/
if (Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP) {
if (
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP
) {
const { style, ...rest } = this.props; // eslint-disable-line no-unused-vars
return (
<TouchableNativeFeedback
{...rest}
style={null}
background={
TouchableNativeFeedback.Ripple(
this.props.pressColor,
this.props.borderless
)
}
background={TouchableNativeFeedback.Ripple(
this.props.pressColor,
this.props.borderless,
)}
>
<View style={this.props.style}>
{Children.only(this.props.children)}

View File

@ -1,5 +1,7 @@
/* @flow */
import { Animated, Easing, Platform } from 'react-native';
import type {
NavigationSceneRendererProps,
NavigationTransitionProps,
@ -7,13 +9,6 @@ import type {
} from '../TypeDefinition';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
import HeaderStyleInterpolator from './HeaderStyleInterpolator';
import {
Animated,
Easing,
Platform,
} from 'react-native';
/**
* Describes a visual transition from one screen to another.
@ -23,7 +18,7 @@ export type TransitionConfig = {
transitionSpec?: NavigationTransitionSpec,
// How to animate position and opacity of the screen
// based on the value generated by the transitionSpec
screenInterpolator?: NavigationSceneRendererProps => Object,
screenInterpolator?: (NavigationSceneRendererProps) => Object,
};
// Used for all animations unless overriden
@ -33,39 +28,39 @@ const DefaultTransitionSpec = ({
timing: Animated.spring,
bounciness: 0,
speed: 9,
} : NavigationTransitionSpec);
}: NavigationTransitionSpec);
// Standard iOS navigation transition
const SlideFromRightIOS = ({
screenInterpolator: CardStackStyleInterpolator.forHorizontal,
} : TransitionConfig);
}: TransitionConfig);
// Standard iOS navigation transition for modals
const ModalSlideFromBottomIOS = ({
screenInterpolator: CardStackStyleInterpolator.forVertical,
} : TransitionConfig);
}: TransitionConfig);
// Standard Android navigation transition when opening an Activity
const FadeInFromBottomAndroid = ({
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
transitionSpec: {
duration: 350,
easing: Easing.out(Easing.poly(5)), // decelerate
easing: Easing.out(Easing.poly(5)), // decelerate
timing: Animated.timing,
},
screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
} : TransitionConfig);
}: TransitionConfig);
// Standard Android navigation transition when closing an Activity
const FadeOutToBottomAndroid = ({
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
transitionSpec: {
duration: 230,
easing: Easing.in(Easing.poly(4)), // accelerate
easing: Easing.in(Easing.poly(4)), // accelerate
timing: Animated.timing,
},
screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
} : TransitionConfig);
}: TransitionConfig);
function defaultTransitionConfig(
// props for the new screen
@ -78,19 +73,19 @@ function defaultTransitionConfig(
if (Platform.OS === 'android') {
// Use the default Android animation no matter if the screen is a modal.
// Android doesn't have full-screen modals like iOS does, it has dialogs.
if (prevTransitionProps && (transitionProps.index < prevTransitionProps.index)) {
if (
prevTransitionProps && transitionProps.index < prevTransitionProps.index
) {
// Navigating back to the previous screen
return FadeOutToBottomAndroid;
}
return FadeInFromBottomAndroid;
} else {
// iOS and other platforms
if (isModal) {
return ModalSlideFromBottomIOS;
} else {
return SlideFromRightIOS;
}
}
// iOS and other platforms
if (isModal) {
return ModalSlideFromBottomIOS;
}
return SlideFromRightIOS;
}
function getTransitionConfig(
@ -104,7 +99,7 @@ function getTransitionConfig(
const defaultConfig = defaultTransitionConfig(
transitionProps,
prevTransitionProps,
isModal
isModal,
);
if (transitionConfigurer) {
return {

View File

@ -2,11 +2,7 @@
import React from 'react';
import {
Animated,
StyleSheet,
View,
} from 'react-native';
import { Animated, StyleSheet, View } from 'react-native';
import invariant from 'fbjs/lib/invariant';
@ -33,7 +29,7 @@ type Props = {
onTransitionStart?: () => void,
render: (
transitionProps: NavigationTransitionProps,
prevTransitionProps: ?NavigationTransitionProps
prevTransitionProps: ?NavigationTransitionProps,
) => any,
style?: any,
};
@ -107,14 +103,15 @@ class Transitioner extends React.Component<*, Props, State> {
const nextScenes = NavigationScenesReducer(
this.state.scenes,
nextProps.navigation.state,
this.props.navigation.state
this.props.navigation.state,
);
if (nextScenes === this.state.scenes) {
return;
}
const indexHasChanged = nextProps.navigation.state.index !== this.props.navigation.state.index;
const indexHasChanged = nextProps.navigation.state.index !==
this.props.navigation.state.index;
if (this._isTransitionRunning) {
this._queuedTransition = { nextProps, nextScenes, indexHasChanged };
return;
@ -123,7 +120,11 @@ class Transitioner extends React.Component<*, Props, State> {
this._startTransition(nextProps, nextScenes, indexHasChanged);
}
_startTransition(nextProps: Props, nextScenes: Array<NavigationScene>, indexHasChanged: boolean) {
_startTransition(
nextProps: Props,
nextScenes: Array<NavigationScene>,
indexHasChanged: boolean,
) {
const nextState = {
...this.state,
scenes: nextScenes,
@ -139,13 +140,13 @@ class Transitioner extends React.Component<*, Props, State> {
this._prevTransitionProps = this._transitionProps;
this._transitionProps = buildTransitionProps(nextProps, nextState);
// get the transition spec.
const transitionUserSpec = nextProps.configureTransition ?
nextProps.configureTransition(
this._transitionProps,
this._prevTransitionProps,
) :
null;
// get the transition spec.
const transitionUserSpec = nextProps.configureTransition
? nextProps.configureTransition(
this._transitionProps,
this._prevTransitionProps,
)
: null;
const transitionSpec = {
...DefaultTransitionSpec,
@ -157,40 +158,32 @@ class Transitioner extends React.Component<*, Props, State> {
const animations = indexHasChanged
? [
timing(
progress,
{
timing(progress, {
...transitionSpec,
toValue: 1,
},
),
timing(
position,
{
}),
timing(position, {
...transitionSpec,
toValue: nextProps.navigation.state.index,
},
),
]
}),
]
: [];
// update scenes and play the transition
this._isTransitionRunning = true;
this.setState(nextState, () => {
nextProps.onTransitionStart && nextProps.onTransitionStart(
this._transitionProps,
this._prevTransitionProps,
);
nextProps.onTransitionStart &&
nextProps.onTransitionStart(
this._transitionProps,
this._prevTransitionProps,
);
Animated.parallel(animations).start(this._onTransitionEnd);
});
}
render() {
return (
<View
onLayout={this._onLayout}
style={[styles.main, this.props.style]}
>
<View onLayout={this._onLayout} style={[styles.main, this.props.style]}>
{this.props.render(this._transitionProps, this._prevTransitionProps)}
</View>
);
@ -198,8 +191,10 @@ class Transitioner extends React.Component<*, Props, State> {
_onLayout(event: any): void {
const { height, width } = event.nativeEvent.layout;
if (this.state.layout.initWidth === width &&
this.state.layout.initHeight === height) {
if (
this.state.layout.initWidth === width &&
this.state.layout.initHeight === height
) {
return;
}
const layout = {
@ -236,15 +231,13 @@ class Transitioner extends React.Component<*, Props, State> {
this._transitionProps = buildTransitionProps(this.props, nextState);
this.setState(nextState, () => {
this.props.onTransitionEnd && this.props.onTransitionEnd(
this._transitionProps,
prevTransitionProps,
);
this.props.onTransitionEnd &&
this.props.onTransitionEnd(this._transitionProps, prevTransitionProps);
if (this._queuedTransition) {
this._startTransition(
this._queuedTransition.nextProps,
this._queuedTransition.nextScenes,
this._queuedTransition.indexHasChanged
this._queuedTransition.nextProps,
this._queuedTransition.nextScenes,
this._queuedTransition.indexHasChanged,
);
this._queuedTransition = null;
} else {

View File

@ -5,15 +5,15 @@ import ScenesReducer from '../ScenesReducer';
/**
* Simulate scenes transtion with changes of navigation states.
*/
function testTransition(states) {
const routes = states.map(keys => ({
function testTransition(states: *) {
const routes = states.map((keys: *) => ({
index: 0,
routes: keys.map(key => ({ key, routeName: '' })),
routes: keys.map((key: *) => ({ key, routeName: '' })),
}));
let scenes = [];
let prevState = null;
routes.forEach((nextState) => {
routes.forEach((nextState: *) => {
scenes = ScenesReducer(scenes, nextState, prevState);
prevState = nextState;
});
@ -23,9 +23,7 @@ function testTransition(states) {
describe('ScenesReducer', () => {
it('gets initial scenes', () => {
const scenes = testTransition([
['1', '2'],
]);
const scenes = testTransition([['1', '2']]);
expect(scenes).toEqual([
{
@ -53,10 +51,7 @@ describe('ScenesReducer', () => {
it('pushes new scenes', () => {
// Transition from ['1', '2'] to ['1', '2', '3'].
const scenes = testTransition([
['1', '2'],
['1', '2', '3'],
]);
const scenes = testTransition([['1', '2'], ['1', '2', '3']]);
expect(scenes).toEqual([
{
@ -145,12 +140,18 @@ describe('ScenesReducer', () => {
it('gets different scenes when routes are different', () => {
const state1 = {
index: 0,
routes: [{ key: '1', x: 1, routeName: '' }, { key: '2', x: 2, routeName: '' }],
routes: [
{ key: '1', x: 1, routeName: '' },
{ key: '2', x: 2, routeName: '' },
],
};
const state2 = {
index: 0,
routes: [{ key: '1', x: 3, routeName: '' }, { key: '2', x: 4, routeName: '' }],
routes: [
{ key: '1', x: 3, routeName: '' },
{ key: '2', x: 4, routeName: '' },
],
};
const scenes1 = ScenesReducer([], state1, null);
@ -158,16 +159,21 @@ describe('ScenesReducer', () => {
expect(scenes1).not.toBe(scenes2);
});
it('gets different scenes when state index changes', () => {
const state1 = {
index: 0,
routes: [{ key: '1', x: 1, routeName: '' }, { key: '2', x: 2, routeName: '' }],
routes: [
{ key: '1', x: 1, routeName: '' },
{ key: '2', x: 2, routeName: '' },
],
};
const state2 = {
index: 1,
routes: [{ key: '1', x: 1, routeName: '' }, { key: '2', x: 2, routeName: '' }],
routes: [
{ key: '1', x: 1, routeName: '' },
{ key: '2', x: 2, routeName: '' },
],
};
const scenes1 = ScenesReducer([], state1, null);
@ -177,10 +183,7 @@ describe('ScenesReducer', () => {
it('pops scenes', () => {
// Transition from ['1', '2', '3'] to ['1', '2'].
const scenes = testTransition([
['1', '2', '3'],
['1', '2'],
]);
const scenes = testTransition([['1', '2', '3'], ['1', '2']]);
expect(scenes).toEqual([
{
@ -217,10 +220,7 @@ describe('ScenesReducer', () => {
});
it('replaces scenes', () => {
const scenes = testTransition([
['1', '2'],
['3'],
]);
const scenes = testTransition([['1', '2'], ['3']]);
expect(scenes).toEqual([
{
@ -257,11 +257,7 @@ describe('ScenesReducer', () => {
});
it('revives scenes', () => {
const scenes = testTransition([
['1', '2'],
['3'],
['2'],
]);
const scenes = testTransition([['1', '2'], ['3'], ['2']]);
expect(scenes).toEqual([
{

View File

@ -4,11 +4,7 @@ import React from 'react';
import propTypes from 'prop-types';
import hoistStatics from 'hoist-non-react-statics';
import type {
NavigationState,
NavigationRoute,
NavigationAction,
} from '../TypeDefinition';
import type { NavigationState, NavigationAction } from '../TypeDefinition';
type Context = {
navigation: InjectedProps<NavigationState, NavigationAction>,
@ -18,8 +14,9 @@ type InjectedProps = {
navigation: InjectedProps<NavigationState, NavigationAction>,
};
export default function withNavigation<T: *>(Component: ReactClass<T & InjectedProps>) {
export default function withNavigation<T: *>(
Component: ReactClass<T & InjectedProps>,
) {
const componentWithNavigation = (props: T, { navigation }: Context) => (
<Component {...props} navigation={navigation} />
);

View File

@ -4,10 +4,7 @@ import React, { PureComponent } from 'react';
import addNavigationHelpers from './addNavigationHelpers';
import type {
NavigationScreenProp,
NavigationAction,
} from './TypeDefinition';
import type { NavigationScreenProp, NavigationAction } from './TypeDefinition';
type InjectedProps<N> = {
childNavigationProps: {
@ -19,10 +16,9 @@ type InjectedProps<N> = {
* HOC which caches the child navigation items.
*/
export default function withCachedChildNavigation<T: *, N: *>(
Comp: ReactClass<T & InjectedProps<N>>
Comp: ReactClass<T & InjectedProps<N>>,
): ReactClass<T> {
return class extends PureComponent {
static displayName = `withCachedChildNavigation(${Comp.displayName || Comp.name})`;
props: T;
@ -40,7 +36,7 @@ export default function withCachedChildNavigation<T: *, N: *>(
};
_updateNavigationProps = (
navigation: NavigationScreenProp<N, NavigationAction>
navigation: NavigationScreenProp<N, NavigationAction>,
) => {
// Update props for each child route
if (!this._childNavigationProps) {
@ -56,7 +52,7 @@ export default function withCachedChildNavigation<T: *, N: *>(
state: route,
});
});
}
};
render() {
return (

104
yarn.lock
View File

@ -208,6 +208,14 @@ ast-types-flow@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
ast-types@0.8.18:
version "0.8.18"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.18.tgz#c8b98574898e8914e9d8de74b947564a9fe929af"
ast-types@0.9.4:
version "0.9.4"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.4.tgz#410d1f81890aeb8e0a38621558ba5869ae53c91b"
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@ -259,7 +267,7 @@ babel-cli@^6.24.0:
optionalDependencies:
chokidar "^1.6.1"
babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
babel-code-frame@6.22.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
dependencies:
@ -1109,7 +1117,7 @@ babel-types@^6.15.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.21
lodash "^4.2.0"
to-fast-properties "^1.0.1"
babylon@^6.11.0, babylon@^6.13.0, babylon@^6.14.1, babylon@^6.15.0:
babylon@6.15.0, babylon@^6.11.0, babylon@^6.13.0, babylon@^6.14.1, babylon@^6.15.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
@ -1278,7 +1286,7 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@ -1367,6 +1375,10 @@ color-name@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d"
colors@>=0.6.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@ -1836,6 +1848,12 @@ eslint-config-airbnb@^14.1.0:
dependencies:
eslint-config-airbnb-base "^11.1.0"
eslint-config-prettier@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-1.6.0.tgz#56e53a8eb461c06eced20cec40d765c185100fd5"
dependencies:
get-stdin "^5.0.1"
eslint-import-resolver-node@^0.2.0:
version "0.2.3"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c"
@ -1883,6 +1901,12 @@ eslint-plugin-jsx-a11y@^4.0.0:
jsx-ast-utils "^1.0.0"
object-assign "^4.0.1"
eslint-plugin-prettier@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.0.1.tgz#2ae1216cf053dd728360ca8560bf1aabc8af3fa9"
dependencies:
requireindex "~1.1.0"
eslint-plugin-react@^6.10.0:
version "6.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.0.tgz#9c48b48d101554b5355413e7c64238abde6ef1ef"
@ -1962,7 +1986,7 @@ estraverse@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2"
esutils@^2.0.0, esutils@^2.0.2:
esutils@2.0.2, esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@ -2149,6 +2173,14 @@ flow-bin@^0.40.0:
version "0.40.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.40.0.tgz#e10d60846d923124e47f548f16ba60fd8baff5a5"
flow-parser@0.40.0:
version "0.40.0"
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.40.0.tgz#b3444742189093323c4319c4fe9d35391f46bcbc"
dependencies:
ast-types "0.8.18"
colors ">=0.6.2"
minimist ">=0.2.0"
for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@ -2263,6 +2295,10 @@ get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
get-stdin@5.0.1, get-stdin@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
getpass@^0.1.1:
version "0.1.6"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
@ -2282,17 +2318,7 @@ glob-parent@^2.0.0:
dependencies:
is-glob "^2.0.0"
glob@^5.0.15:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
@ -2303,6 +2329,16 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^5.0.15:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
global@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.1.tgz#5f757908c7cbabce54f386ae440e11e26b7916df"
@ -2972,6 +3008,15 @@ jest-util@^19.0.2:
leven "^2.0.0"
mkdirp "^0.5.1"
jest-validate@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.0.tgz#8c6318a20ecfeaba0ba5378bfbb8277abded4173"
dependencies:
chalk "^1.1.1"
jest-matcher-utils "^19.0.0"
leven "^2.0.0"
pretty-format "^19.0.0"
jest-validate@^19.0.2:
version "19.0.2"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c"
@ -3366,7 +3411,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
minimist@1.2.0, minimist@>=0.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@ -3744,6 +3789,21 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-0.22.0.tgz#7b37c4480d0858180407e5a8e13f0f47da7385d2"
dependencies:
ast-types "0.9.4"
babel-code-frame "6.22.0"
babylon "6.15.0"
chalk "1.1.3"
esutils "2.0.2"
flow-parser "0.40.0"
get-stdin "5.0.1"
glob "7.1.1"
jest-validate "19.0.0"
minimist "1.2.0"
pretty-format@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-19.0.0.tgz#56530d32acb98a3fa4851c4e2b9d37b420684c84"
@ -3861,9 +3921,11 @@ react-native-drawer-layout@1.2.0:
dependencies:
react-native-dismiss-keyboard "1.0.0"
react-native-tab-view@^0.0.57:
version "0.0.57"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.57.tgz#715e2ea4100fa50168e134df3947dd76ebd55743"
react-native-tab-view@^0.0.59:
version "0.0.59"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.59.tgz#2cd1d809a5699fcc5d45fa6fc08deb21d3f81fea"
dependencies:
eslint-plugin-prettier "^2.0.1"
react-native-vector-icons@^3.0.0:
version "3.0.0"
@ -4168,6 +4230,10 @@ require-uncached@^1.0.2:
caller-path "^0.1.0"
resolve-from "^1.0.0"
requireindex@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162"
resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"