mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-19 17:27:26 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26d2acfca | ||
|
|
a1b3d2213d | ||
|
|
eb39df507e | ||
|
|
cca06bb530 | ||
|
|
2187d8fe66 | ||
|
|
67f98b69eb | ||
|
|
c0c5c86f63 | ||
|
|
4388b6403c | ||
|
|
2cb740fbf6 | ||
|
|
ac741a703b | ||
|
|
5641b42975 |
@@ -6,6 +6,11 @@
|
||||
"development": {
|
||||
"plugins": [
|
||||
"transform-react-jsx-source"
|
||||
],
|
||||
},
|
||||
"production": {
|
||||
"plugins": [
|
||||
"transform-remove-console"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import TabsInDrawer from './TabsInDrawer';
|
||||
import ModalStack from './ModalStack';
|
||||
import StacksInTabs from './StacksInTabs';
|
||||
import StacksOverTabs from './StacksOverTabs';
|
||||
import StacksWithKeys from './StacksWithKeys';
|
||||
import SimpleStack from './SimpleStack';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
import TabAnimations from './TabAnimations';
|
||||
@@ -76,6 +77,10 @@ const ExampleInfo = {
|
||||
name: 'Stacks over Tabs',
|
||||
description: 'Nested stack navigation that pushes on top of tabs',
|
||||
},
|
||||
StacksWithKeys: {
|
||||
name: 'Link in Stack with keys',
|
||||
description: 'Use keys to link between screens',
|
||||
},
|
||||
LinkStack: {
|
||||
name: 'Link in Stack',
|
||||
description: 'Deep linking into a route in stack',
|
||||
@@ -114,6 +119,9 @@ const ExampleRoutes = {
|
||||
ModalStack: {
|
||||
screen: ModalStack,
|
||||
},
|
||||
StacksWithKeys: {
|
||||
screen: StacksWithKeys,
|
||||
},
|
||||
StacksInTabs: {
|
||||
screen: StacksInTabs,
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SafeAreaView, StackNavigator } from 'react-navigation';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView contentInsetAdjustmentBehavior="automatic">
|
||||
<ScrollView>
|
||||
<SafeAreaView
|
||||
forceInset={{
|
||||
top: navigation.state.routeName === 'HeaderTest' ? 'always' : 'never',
|
||||
|
||||
97
examples/NavigationPlayground/js/StacksWithKeys.js
Normal file
97
examples/NavigationPlayground/js/StacksWithKeys.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import { Button, StatusBar, Text, View } from 'react-native';
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
|
||||
class HomeScreen extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>Home</Text>
|
||||
<Button
|
||||
title="Navigate to 'Profile' with key 'A'"
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate({
|
||||
routeName: 'Profile',
|
||||
key: 'A',
|
||||
params: { homeKey: this.props.navigation.state.key },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<StatusBar barStyle="default" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileScreen extends React.Component<any, any> {
|
||||
render() {
|
||||
const { homeKey } = this.props.navigation.state.params;
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>Profile</Text>
|
||||
<Button
|
||||
title="Navigate to 'Settings' with key 'B'"
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate({
|
||||
routeName: 'Settings',
|
||||
key: 'B',
|
||||
params: { homeKey },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
title={`Navigate back to 'Home' with key ${homeKey}`}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate({ routeName: 'Home', key: homeKey })
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsScreen extends React.Component<any, any> {
|
||||
render() {
|
||||
const { homeKey } = this.props.navigation.state.params;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>Settings</Text>
|
||||
<Button
|
||||
title={`Navigate back to 'Home' with key ${homeKey}`}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate({ routeName: 'Home', key: homeKey })
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
title="Navigate back to 'Profile' with key 'A'"
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate({
|
||||
routeName: 'Profile',
|
||||
key: 'A'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Stack = StackNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: HomeScreen,
|
||||
},
|
||||
Profile: {
|
||||
screen: ProfileScreen,
|
||||
},
|
||||
Settings: {
|
||||
screen: SettingsScreen,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerMode: 'none',
|
||||
}
|
||||
);
|
||||
|
||||
export default Stack;
|
||||
@@ -17,6 +17,7 @@
|
||||
"react-navigation": "link:../.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-transform-remove-console": "^6.9.0",
|
||||
"babel-jest": "^21.0.0",
|
||||
"flow-bin": "^0.61.0",
|
||||
"jest": "^21.0.1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/react-navigation.js",
|
||||
"repository": {
|
||||
|
||||
@@ -45,9 +45,6 @@ const navigate = createAction(NAVIGATE, payload => {
|
||||
if (payload.key) {
|
||||
action.key = payload.key;
|
||||
}
|
||||
if (payload.immediate) {
|
||||
action.immediate = payload.immediate;
|
||||
}
|
||||
return action;
|
||||
});
|
||||
|
||||
@@ -73,9 +70,6 @@ const push = createAction(PUSH, payload => {
|
||||
if (payload.action) {
|
||||
action.action = payload.action;
|
||||
}
|
||||
if (payload.immediate) {
|
||||
action.immediate = payload.immediate;
|
||||
}
|
||||
return action;
|
||||
});
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('NavigationContainer', () => {
|
||||
// First dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
@@ -119,7 +119,7 @@ describe('NavigationContainer', () => {
|
||||
// Second dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'baz', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'baz' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('NavigationContainer', () => {
|
||||
// First dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'bar', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'bar' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
@@ -157,28 +157,28 @@ describe('NavigationContainer', () => {
|
||||
// Second dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'baz', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'baz' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Third dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'car', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'car' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fourth dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'dog', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'dog' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
// Fifth dispatch
|
||||
expect(
|
||||
navigationContainer.dispatch(
|
||||
NavigationActions.navigate({ routeName: 'elk', immediate: true })
|
||||
NavigationActions.navigate({ routeName: 'elk' })
|
||||
)
|
||||
).toEqual(true);
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
"key": "StackRouterRoot",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"key": "Init-id-0-1",
|
||||
"key": "id-0-1",
|
||||
"routeName": "Home",
|
||||
},
|
||||
],
|
||||
@@ -347,7 +347,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
"key": "StackRouterRoot",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"key": "Init-id-0-0",
|
||||
"key": "id-0-0",
|
||||
"routeName": "Home",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -101,23 +101,32 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
// Set up the initial state if needed
|
||||
if (!state) {
|
||||
let route = {};
|
||||
if (
|
||||
behavesLikePushAction(action) &&
|
||||
childRouters[action.routeName] !== undefined
|
||||
) {
|
||||
const childRouter = childRouters[action.routeName];
|
||||
// This is a push-like action, and childRouter will be a router or null if we are responsible for this routeName
|
||||
if (behavesLikePushAction(action) && childRouter !== undefined) {
|
||||
let childState = {};
|
||||
// The router is null for normal leaf routes
|
||||
if (childRouter !== null) {
|
||||
const childAction =
|
||||
action.action ||
|
||||
NavigationActions.init({ params: action.params });
|
||||
childState = childRouter.getStateForAction(childAction);
|
||||
}
|
||||
return {
|
||||
key: 'StackRouterRoot',
|
||||
isTransitioning: false,
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
routeName: action.routeName,
|
||||
params: action.params,
|
||||
key: `Init-${generateKey()}`,
|
||||
...childState,
|
||||
key: action.key || generateKey(),
|
||||
routeName: action.routeName,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (initialChildRouter) {
|
||||
route = initialChildRouter.getStateForAction(
|
||||
NavigationActions.navigate({
|
||||
@@ -135,11 +144,10 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
};
|
||||
route = {
|
||||
...route,
|
||||
routeName: initialRouteName,
|
||||
key: `Init-${generateKey()}`,
|
||||
...(params ? { params } : {}),
|
||||
routeName: initialRouteName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state = {
|
||||
key: 'StackRouterRoot',
|
||||
isTransitioning: false,
|
||||
@@ -148,17 +156,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
||||
(action.key == null || action.key === state.key) &&
|
||||
state.isTransitioning
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isTransitioning: false,
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -217,8 +214,8 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
params: action.params,
|
||||
// merge the child state in this order to allow params override
|
||||
...childState,
|
||||
key: action.newKey || generateKey(),
|
||||
routeName: action.routeName,
|
||||
key: action.newKey || generateKey(),
|
||||
};
|
||||
return { ...state, routes };
|
||||
}
|
||||
@@ -229,10 +226,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
behavesLikePushAction(action) &&
|
||||
childRouters[action.routeName] !== undefined
|
||||
) {
|
||||
if (state.isTransitioning) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
const childRouter = childRouters[action.routeName];
|
||||
let route;
|
||||
|
||||
@@ -250,7 +243,10 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
if (state.index === lastRouteIndex && !action.params) {
|
||||
return state;
|
||||
}
|
||||
const routes = [...state.routes];
|
||||
|
||||
// Remove the now unused routes at the tail of the routes array
|
||||
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
||||
|
||||
// Apply params if provided, otherwise leave route identity intact
|
||||
if (action.params) {
|
||||
const route = state.routes.find(r => r.key === action.key);
|
||||
@@ -274,7 +270,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
};
|
||||
}
|
||||
}
|
||||
const key = action.key || generateKey();
|
||||
|
||||
if (childRouter) {
|
||||
const childAction =
|
||||
action.action || NavigationActions.init({ params: action.params });
|
||||
@@ -282,14 +278,14 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
params: action.params,
|
||||
// merge the child state in this order to allow params override
|
||||
...childRouter.getStateForAction(childAction),
|
||||
key,
|
||||
routeName: action.routeName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
} else {
|
||||
route = {
|
||||
params: action.params,
|
||||
key,
|
||||
routeName: action.routeName,
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -305,6 +301,17 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
||||
(action.key == null || action.key === state.key) &&
|
||||
state.isTransitioning
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isTransitioning: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle navigation to other child routers that are not yet pushed
|
||||
if (behavesLikePushAction(action)) {
|
||||
const childRouterNames = Object.keys(childRouters);
|
||||
@@ -330,11 +337,12 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
routeToPush = navigatedChildRoute;
|
||||
}
|
||||
if (routeToPush) {
|
||||
return StateUtils.push(state, {
|
||||
const route = {
|
||||
...routeToPush,
|
||||
key: generateKey(),
|
||||
routeName: childRouterName,
|
||||
});
|
||||
key: action.key || generateKey(),
|
||||
};
|
||||
return StateUtils.push(state, route);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,20 +381,16 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
...state,
|
||||
routes: resetAction.actions.map(childAction => {
|
||||
const router = childRouters[childAction.routeName];
|
||||
let childState = {};
|
||||
if (router) {
|
||||
return {
|
||||
...childAction,
|
||||
...router.getStateForAction(childAction),
|
||||
routeName: childAction.routeName,
|
||||
key: generateKey(),
|
||||
};
|
||||
childState = router.getStateForAction(childAction);
|
||||
}
|
||||
const route = {
|
||||
...childAction,
|
||||
key: generateKey(),
|
||||
return {
|
||||
params: childAction.params,
|
||||
...childState,
|
||||
routeName: childAction.routeName,
|
||||
key: childAction.key || generateKey(),
|
||||
};
|
||||
delete route.type;
|
||||
return route;
|
||||
}),
|
||||
index: action.index,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import TabRouter from '../TabRouter';
|
||||
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||
|
||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -19,7 +18,7 @@ const ROUTERS = {
|
||||
StackRouter,
|
||||
};
|
||||
|
||||
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
||||
const dummyEventSubscriber = (name, handler) => ({
|
||||
remove: () => {},
|
||||
});
|
||||
|
||||
@@ -111,8 +110,8 @@ test('Handles no-op actions with tabs within stack router', () => {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Qux',
|
||||
});
|
||||
expect(state1.routes[0].key).toEqual('Init-id-0');
|
||||
expect(state2.routes[0].key).toEqual('Init-id-1');
|
||||
expect(state1.routes[0].key).toEqual('id-0');
|
||||
expect(state2.routes[0].key).toEqual('id-1');
|
||||
state1.routes[0].key = state2.routes[0].key;
|
||||
expect(state1).toEqual(state2);
|
||||
const state3 = TestRouter.getStateForAction(
|
||||
@@ -140,7 +139,7 @@ test('Handles deep action', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Bar',
|
||||
},
|
||||
],
|
||||
@@ -180,8 +179,8 @@ test('Supports lazily-evaluated getScreen', () => {
|
||||
immediate: true,
|
||||
routeName: 'Qux',
|
||||
});
|
||||
expect(state1.routes[0].key).toEqual('Init-id-0');
|
||||
expect(state2.routes[0].key).toEqual('Init-id-1');
|
||||
expect(state1.routes[0].key).toEqual('id-0');
|
||||
expect(state2.routes[0].key).toEqual('id-1');
|
||||
state1.routes[0].key = state2.routes[0].key;
|
||||
expect(state1).toEqual(state2);
|
||||
const state3 = TestRouter.getStateForAction(
|
||||
|
||||
@@ -355,7 +355,7 @@ describe('StackRouter', () => {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'StackRouterRoot',
|
||||
routes: [{ key: 'Init-id-0', routeName: 'foo' }],
|
||||
routes: [{ key: 'id-0', routeName: 'foo' }],
|
||||
});
|
||||
const pushedState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'qux' }),
|
||||
@@ -493,23 +493,6 @@ describe('StackRouter', () => {
|
||||
expect(poppedImmediatelyState.isTransitioning).toBe(false);
|
||||
});
|
||||
|
||||
test('Navigate does not happen while transitioning', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
bar: { screen: () => <div /> },
|
||||
});
|
||||
const initState = {
|
||||
...TestRouter.getStateForAction(NavigationActions.init()),
|
||||
isTransitioning: true,
|
||||
};
|
||||
const pushedState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'bar' }),
|
||||
initState
|
||||
);
|
||||
expect(pushedState.index).toEqual(0);
|
||||
expect(pushedState.routes.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('Navigate Pushes duplicate routeName', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
@@ -517,13 +500,13 @@ describe('StackRouter', () => {
|
||||
});
|
||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||
const pushedState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'bar', immediate: true }),
|
||||
NavigationActions.navigate({ routeName: 'bar' }),
|
||||
initState
|
||||
);
|
||||
expect(pushedState.index).toEqual(1);
|
||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
||||
const pushedTwiceState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'bar', immediate: true }),
|
||||
NavigationActions.navigate({ routeName: 'bar' }),
|
||||
pushedState
|
||||
);
|
||||
expect(pushedTwiceState.index).toEqual(2);
|
||||
@@ -557,7 +540,7 @@ describe('StackRouter', () => {
|
||||
});
|
||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||
const pushedState = TestRouter.getStateForAction(
|
||||
NavigationActions.push({ routeName: 'bar', immediate: true }),
|
||||
NavigationActions.push({ routeName: 'bar' }),
|
||||
initState
|
||||
);
|
||||
expect(pushedState.index).toEqual(1);
|
||||
@@ -570,6 +553,37 @@ describe('StackRouter', () => {
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('Navigate backwards with key removes leading routes', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
bar: { screen: () => <div /> },
|
||||
});
|
||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||
const pushedState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
|
||||
initState
|
||||
);
|
||||
const pushedTwiceState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'bar', key: 'b`' }),
|
||||
pushedState
|
||||
);
|
||||
const pushedThriceState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'foo', key: 'c`' }),
|
||||
pushedTwiceState
|
||||
);
|
||||
expect(pushedThriceState.routes.length).toEqual(4);
|
||||
|
||||
const navigatedBackToFirstRouteState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({
|
||||
routeName: 'foo',
|
||||
key: pushedThriceState.routes[0].key,
|
||||
}),
|
||||
pushedThriceState
|
||||
);
|
||||
expect(navigatedBackToFirstRouteState.index).toEqual(0);
|
||||
expect(navigatedBackToFirstRouteState.routes.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('Handle basic stack logic for plain components', () => {
|
||||
const FooScreen = () => <div />;
|
||||
const BarScreen = () => <div />;
|
||||
@@ -588,7 +602,7 @@ describe('StackRouter', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Foo',
|
||||
},
|
||||
],
|
||||
@@ -616,7 +630,7 @@ describe('StackRouter', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Foo',
|
||||
},
|
||||
],
|
||||
@@ -713,7 +727,7 @@ describe('StackRouter', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Foo',
|
||||
},
|
||||
],
|
||||
@@ -741,7 +755,7 @@ describe('StackRouter', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Foo',
|
||||
},
|
||||
],
|
||||
@@ -815,7 +829,7 @@ describe('StackRouter', () => {
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Bar',
|
||||
},
|
||||
],
|
||||
@@ -924,14 +938,14 @@ describe('StackRouter', () => {
|
||||
{
|
||||
type: NavigationActions.SET_PARAMS,
|
||||
params: { name: 'foobar' },
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
},
|
||||
state
|
||||
);
|
||||
expect(state2 && state2.index).toEqual(0);
|
||||
expect(state2 && state2.routes[0].routes[0].routes).toEqual([
|
||||
{
|
||||
key: 'Init-id-0',
|
||||
key: 'id-0',
|
||||
routeName: 'Quux',
|
||||
params: { name: 'foobar' },
|
||||
},
|
||||
@@ -1150,6 +1164,126 @@ describe('StackRouter', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('Handles the navigate action with params and nested StackRouter as a first action', () => {
|
||||
const state = TestStackRouter.getStateForAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'main',
|
||||
params: {
|
||||
code: 'test',
|
||||
foo: 'bar',
|
||||
},
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'profile',
|
||||
params: {
|
||||
id: '4',
|
||||
code: 'test',
|
||||
foo: 'bar',
|
||||
},
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'list',
|
||||
params: {
|
||||
id: '10259959195',
|
||||
code: 'test',
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'id-2',
|
||||
params: { code: 'test', foo: 'bar' },
|
||||
routeName: 'main',
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'id-1',
|
||||
params: { code: 'test', foo: 'bar', id: '4' },
|
||||
routeName: 'profile',
|
||||
routes: [
|
||||
{
|
||||
key: 'id-0',
|
||||
params: { code: 'test', foo: 'bar', id: '10259959195' },
|
||||
routeName: 'list',
|
||||
type: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const state2 = TestStackRouter.getStateForAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'main',
|
||||
params: {
|
||||
code: '',
|
||||
foo: 'bar',
|
||||
},
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'profile',
|
||||
params: {
|
||||
id: '4',
|
||||
code: '',
|
||||
foo: 'bar',
|
||||
},
|
||||
action: {
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'list',
|
||||
params: {
|
||||
id: '10259959195',
|
||||
code: '',
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(state2).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'StackRouterRoot',
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'id-5',
|
||||
params: { code: '', foo: 'bar' },
|
||||
routeName: 'main',
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
key: 'id-4',
|
||||
params: { code: '', foo: 'bar', id: '4' },
|
||||
routeName: 'profile',
|
||||
routes: [
|
||||
{
|
||||
key: 'id-3',
|
||||
params: { code: '', foo: 'bar', id: '10259959195' },
|
||||
routeName: 'list',
|
||||
type: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('Handles the navigate action with params and nested TabRouter', () => {
|
||||
const ChildNavigator = () => <div />;
|
||||
ChildNavigator.router = TabRouter({
|
||||
|
||||
@@ -206,132 +206,137 @@ class CardStack extends React.Component {
|
||||
const { options } = this._getScreenDetails(scene);
|
||||
const gestureDirectionInverted = options.gestureDirection === 'inverted';
|
||||
|
||||
const responder = PanResponder.create({
|
||||
onPanResponderTerminate: () => {
|
||||
this._isResponding = false;
|
||||
this._reset(index, 0);
|
||||
},
|
||||
onPanResponderGrant: () => {
|
||||
position.stopAnimation(value => {
|
||||
this._isResponding = true;
|
||||
this._gestureStartValue = value;
|
||||
});
|
||||
},
|
||||
onMoveShouldSetPanResponder: (event, gesture) => {
|
||||
if (index !== scene.index) {
|
||||
return false;
|
||||
}
|
||||
const immediateIndex =
|
||||
this._immediateIndex == null ? index : this._immediateIndex;
|
||||
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
||||
const currentDragPosition =
|
||||
event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
|
||||
const axisLength = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const axisHasBeenMeasured = !!axisLength;
|
||||
|
||||
// Measure the distance from the touch to the edge of the screen
|
||||
const screenEdgeDistance = gestureDirectionInverted
|
||||
? axisLength - (currentDragPosition - currentDragDistance)
|
||||
: currentDragPosition - currentDragDistance;
|
||||
// Compare to the gesture distance relavant to card or modal
|
||||
const {
|
||||
gestureResponseDistance: userGestureResponseDistance = {},
|
||||
} = this._getScreenDetails(scene).options;
|
||||
const gestureResponseDistance = isVertical
|
||||
? userGestureResponseDistance.vertical ||
|
||||
GESTURE_RESPONSE_DISTANCE_VERTICAL
|
||||
: userGestureResponseDistance.horizontal ||
|
||||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
|
||||
// GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
|
||||
if (screenEdgeDistance > gestureResponseDistance) {
|
||||
// Reject touches that started in the middle of the screen
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasDraggedEnough =
|
||||
Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
|
||||
|
||||
const isOnFirstCard = immediateIndex === 0;
|
||||
const shouldSetResponder =
|
||||
hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
|
||||
return shouldSetResponder;
|
||||
},
|
||||
onPanResponderMove: (event, gesture) => {
|
||||
// Handle the moving touches for our granted responder
|
||||
const startValue = this._gestureStartValue;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const axisDistance = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const currentValue =
|
||||
(I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
|
||||
? startValue + gesture[axis] / axisDistance
|
||||
: startValue - gesture[axis] / axisDistance;
|
||||
const value = clamp(index - 1, currentValue, index);
|
||||
position.setValue(value);
|
||||
},
|
||||
onPanResponderTerminationRequest: () =>
|
||||
// Returning false will prevent other views from becoming responder while
|
||||
// the navigation view is the responder (mid-gesture)
|
||||
false,
|
||||
onPanResponderRelease: (event, gesture) => {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
this._isResponding = false;
|
||||
|
||||
const immediateIndex =
|
||||
this._immediateIndex == null ? index : this._immediateIndex;
|
||||
|
||||
// Calculate animate duration according to gesture speed and moved distance
|
||||
const axisDistance = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const movementDirection = gestureDirectionInverted ? -1 : 1;
|
||||
const movedDistance =
|
||||
movementDirection * gesture[isVertical ? 'dy' : 'dx'];
|
||||
const gestureVelocity =
|
||||
movementDirection * gesture[isVertical ? 'vy' : 'vx'];
|
||||
const defaultVelocity = axisDistance / ANIMATION_DURATION;
|
||||
const velocity = Math.max(Math.abs(gestureVelocity), defaultVelocity);
|
||||
const resetDuration = gestureDirectionInverted
|
||||
? (axisDistance - movedDistance) / velocity
|
||||
: movedDistance / velocity;
|
||||
const goBackDuration = gestureDirectionInverted
|
||||
? movedDistance / velocity
|
||||
: (axisDistance - movedDistance) / velocity;
|
||||
|
||||
// To asyncronously get the current animated value, we need to run stopAnimation:
|
||||
position.stopAnimation(value => {
|
||||
// If the speed of the gesture release is significant, use that as the indication
|
||||
// of intent
|
||||
if (gestureVelocity < -0.5) {
|
||||
this._reset(immediateIndex, resetDuration);
|
||||
return;
|
||||
}
|
||||
if (gestureVelocity > 0.5) {
|
||||
this._goBack(immediateIndex, goBackDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
|
||||
// and the back will happen.
|
||||
if (value <= index - POSITION_THRESHOLD) {
|
||||
this._goBack(immediateIndex, goBackDuration);
|
||||
} else {
|
||||
this._reset(immediateIndex, resetDuration);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const gesturesEnabled =
|
||||
typeof options.gesturesEnabled === 'boolean'
|
||||
? options.gesturesEnabled
|
||||
: Platform.OS === 'ios';
|
||||
|
||||
const responder = !gesturesEnabled
|
||||
? null
|
||||
: PanResponder.create({
|
||||
onPanResponderTerminate: () => {
|
||||
this._isResponding = false;
|
||||
this._reset(index, 0);
|
||||
},
|
||||
onPanResponderGrant: () => {
|
||||
position.stopAnimation(value => {
|
||||
this._isResponding = true;
|
||||
this._gestureStartValue = value;
|
||||
});
|
||||
},
|
||||
onMoveShouldSetPanResponder: (event, gesture) => {
|
||||
if (index !== scene.index) {
|
||||
return false;
|
||||
}
|
||||
const immediateIndex =
|
||||
this._immediateIndex == null ? index : this._immediateIndex;
|
||||
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
||||
const currentDragPosition =
|
||||
event.nativeEvent[isVertical ? 'pageY' : 'pageX'];
|
||||
const axisLength = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const axisHasBeenMeasured = !!axisLength;
|
||||
|
||||
// Measure the distance from the touch to the edge of the screen
|
||||
const screenEdgeDistance = gestureDirectionInverted
|
||||
? axisLength - (currentDragPosition - currentDragDistance)
|
||||
: currentDragPosition - currentDragDistance;
|
||||
// Compare to the gesture distance relavant to card or modal
|
||||
const {
|
||||
gestureResponseDistance: userGestureResponseDistance = {},
|
||||
} = this._getScreenDetails(scene).options;
|
||||
const gestureResponseDistance = isVertical
|
||||
? userGestureResponseDistance.vertical ||
|
||||
GESTURE_RESPONSE_DISTANCE_VERTICAL
|
||||
: userGestureResponseDistance.horizontal ||
|
||||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
|
||||
// GESTURE_RESPONSE_DISTANCE is about 25 or 30. Or 135 for modals
|
||||
if (screenEdgeDistance > gestureResponseDistance) {
|
||||
// Reject touches that started in the middle of the screen
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasDraggedEnough =
|
||||
Math.abs(currentDragDistance) > RESPOND_THRESHOLD;
|
||||
|
||||
const isOnFirstCard = immediateIndex === 0;
|
||||
const shouldSetResponder =
|
||||
hasDraggedEnough && axisHasBeenMeasured && !isOnFirstCard;
|
||||
return shouldSetResponder;
|
||||
},
|
||||
onPanResponderMove: (event, gesture) => {
|
||||
// Handle the moving touches for our granted responder
|
||||
const startValue = this._gestureStartValue;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const axisDistance = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const currentValue =
|
||||
(I18nManager.isRTL && axis === 'dx') !== gestureDirectionInverted
|
||||
? startValue + gesture[axis] / axisDistance
|
||||
: startValue - gesture[axis] / axisDistance;
|
||||
const value = clamp(index - 1, currentValue, index);
|
||||
position.setValue(value);
|
||||
},
|
||||
onPanResponderTerminationRequest: () =>
|
||||
// Returning false will prevent other views from becoming responder while
|
||||
// the navigation view is the responder (mid-gesture)
|
||||
false,
|
||||
onPanResponderRelease: (event, gesture) => {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
this._isResponding = false;
|
||||
|
||||
const immediateIndex =
|
||||
this._immediateIndex == null ? index : this._immediateIndex;
|
||||
|
||||
// Calculate animate duration according to gesture speed and moved distance
|
||||
const axisDistance = isVertical
|
||||
? layout.height.__getValue()
|
||||
: layout.width.__getValue();
|
||||
const movementDirection = gestureDirectionInverted ? -1 : 1;
|
||||
const movedDistance =
|
||||
movementDirection * gesture[isVertical ? 'dy' : 'dx'];
|
||||
const gestureVelocity =
|
||||
movementDirection * gesture[isVertical ? 'vy' : 'vx'];
|
||||
const defaultVelocity = axisDistance / ANIMATION_DURATION;
|
||||
const velocity = Math.max(
|
||||
Math.abs(gestureVelocity),
|
||||
defaultVelocity
|
||||
);
|
||||
const resetDuration = gestureDirectionInverted
|
||||
? (axisDistance - movedDistance) / velocity
|
||||
: movedDistance / velocity;
|
||||
const goBackDuration = gestureDirectionInverted
|
||||
? movedDistance / velocity
|
||||
: (axisDistance - movedDistance) / velocity;
|
||||
|
||||
// To asyncronously get the current animated value, we need to run stopAnimation:
|
||||
position.stopAnimation(value => {
|
||||
// If the speed of the gesture release is significant, use that as the indication
|
||||
// of intent
|
||||
if (gestureVelocity < -0.5) {
|
||||
this._reset(immediateIndex, resetDuration);
|
||||
return;
|
||||
}
|
||||
if (gestureVelocity > 0.5) {
|
||||
this._goBack(immediateIndex, goBackDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
|
||||
// and the back will happen.
|
||||
if (value <= index - POSITION_THRESHOLD) {
|
||||
this._goBack(immediateIndex, goBackDuration);
|
||||
} else {
|
||||
this._reset(immediateIndex, resetDuration);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handlers = gesturesEnabled ? responder.panHandlers : {};
|
||||
const containerStyle = [
|
||||
styles.container,
|
||||
|
||||
@@ -262,7 +262,11 @@ class Header extends React.PureComponent {
|
||||
let appBar;
|
||||
|
||||
if (this.props.mode === 'float') {
|
||||
const scenesProps = this.props.scenes.map(scene => ({
|
||||
const scenesByIndex = {};
|
||||
this.props.scenes.forEach(scene => {
|
||||
scenesByIndex[scene.index] = scene;
|
||||
});
|
||||
const scenesProps = Object.values(scenesByIndex).map(scene => ({
|
||||
position: this.props.position,
|
||||
progress: this.props.progress,
|
||||
scene,
|
||||
|
||||
@@ -3,16 +3,25 @@ import propTypes from 'prop-types';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
|
||||
export default function withNavigation(Component) {
|
||||
const componentWithNavigation = (props, { navigation }) => (
|
||||
<Component {...props} navigation={navigation} />
|
||||
);
|
||||
class ComponentWithNavigation extends React.Component {
|
||||
static displayName = `withNavigation(${Component.displayName ||
|
||||
Component.name})`;
|
||||
|
||||
const displayName = Component.displayName || Component.name;
|
||||
componentWithNavigation.displayName = `withNavigation(${displayName})`;
|
||||
static contextTypes = {
|
||||
navigation: propTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentWithNavigation.contextTypes = {
|
||||
navigation: propTypes.object.isRequired,
|
||||
};
|
||||
render() {
|
||||
const { navigation } = this.context;
|
||||
return (
|
||||
<Component
|
||||
{...this.props}
|
||||
navigation={navigation}
|
||||
ref={this.props.onRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return hoistStatics(componentWithNavigation, Component);
|
||||
return hoistStatics(ComponentWithNavigation, Component);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user