[BREAKING] New createNavigator API (#3392)

* New createNavigator and View API

See the RFC here:
https://github.com/react-navigation/rfcs/blob/master/text/0002-navigator-view-api.md

* shattered dreams of flow

* fix export

* Fix tab view issues found by brent
This commit is contained in:
Eric Vicenti
2018-02-27 17:27:58 -08:00
committed by Brent Vatne
parent 6785729fb5
commit e27ad22c57
32 changed files with 494 additions and 793 deletions

View File

@@ -18,7 +18,6 @@ import {
createNavigationContainer,
SafeAreaView,
TabRouter,
addNavigationHelpers,
} from 'react-navigation';
import SampleText from './SampleText';
@@ -66,19 +65,14 @@ const CustomTabBar = ({ navigation }) => {
);
};
const CustomTabView = ({ router, navigation }) => {
const CustomTabView = ({ descriptors, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForRouteName(routes[index].routeName);
const descriptor = descriptors[routes[index].key];
const ActiveScreen = descriptor.getComponent();
return (
<SafeAreaView forceInset={{ top: 'always' }}>
<CustomTabBar navigation={navigation} />
<ActiveScreen
navigation={addNavigationHelpers({
dispatch: navigation.dispatch,
state: routes[index],
})}
screenProps={{}}
/>
<ActiveScreen navigation={descriptor.navigation} />
</SafeAreaView>
);
};
@@ -105,7 +99,7 @@ const CustomTabRouter = TabRouter(
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
createNavigator(CustomTabView, CustomTabRouter, {})
);
const styles = StyleSheet.create({

View File

@@ -14,7 +14,6 @@ import {
SafeAreaView,
StackRouter,
createNavigationContainer,
addNavigationHelpers,
createNavigator,
} from 'react-navigation';
import SampleText from './SampleText';
@@ -45,11 +44,12 @@ const MySettingsScreen = ({ navigation }) => (
class CustomNavigationView extends Component {
render() {
const { navigation, router } = this.props;
const { navigation, router, descriptors } = this.props;
return (
<Transitioner
configureTransition={this._configureTransition}
descriptors={descriptors}
navigation={navigation}
render={this._render}
/>
@@ -86,16 +86,10 @@ class CustomNavigationView extends Component {
transform: [{ scale: animatedValue }],
};
// The prop `router` is populated when we call `createNavigator`.
const Scene = router.getComponentForRouteName(scene.route.routeName);
const Scene = scene.descriptor.getComponent();
return (
<Animated.View key={index} style={[styles.view, animation]}>
<Scene
navigation={addNavigationHelpers({
...navigation,
state: routes[index],
})}
/>
<Scene navigation={scene.descriptor.navigation} />
</Animated.View>
);
};
@@ -107,7 +101,7 @@ const CustomRouter = StackRouter({
});
const CustomTransitioner = createNavigationContainer(
createNavigator(CustomRouter)(CustomNavigationView)
createNavigator(CustomNavigationView, CustomRouter, {})
);
export default CustomTransitioner;

View File

@@ -260,7 +260,8 @@ declare module 'react-navigation' {
declare export type NavigationComponent =
| NavigationScreenComponent<NavigationRoute, *, *>
| NavigationContainer<NavigationStateRoute, *, *>;
| NavigationContainer<*, *, *>
| any;
declare export type NavigationScreenComponent<
Route: NavigationRoute,
@@ -492,29 +493,6 @@ declare module 'react-navigation' {
navigationOptions?: O,
}>;
//declare export type NavigationNavigatorProps<O: {}, S: {}> =
// | {}
// | { navigation: NavigationScreenProp<S> }
// | { screenProps: {} }
// | { navigationOptions: O }
// | {
// navigation: NavigationScreenProp<S>,
// screenProps: {},
// }
// | {
// navigation: NavigationScreenProp<S>,
// navigationOptions: O,
// }
// | {
// screenProps: {},
// navigationOptions: O,
// }
// | {
// navigation: NavigationScreenProp<S>,
// screenProps: {},
// navigationOptions: O,
// };
/**
* Navigation container
*/
@@ -689,10 +667,6 @@ declare module 'react-navigation' {
) => NavigationState,
};
declare export function addNavigationHelpers<S: {}>(
navigation: NavigationProp<S>
): NavigationScreenProp<S>;
declare export var NavigationActions: {
BACK: 'Navigation/BACK',
INIT: 'Navigation/INIT',
@@ -740,23 +714,24 @@ declare module 'react-navigation' {
declare type _RouterProp<S: NavigationState, O: {}> = {
router: NavigationRouter<S, O>,
};
declare type _NavigatorCreator<
NavigationViewProps: {},
S: NavigationState,
O: {}
> = (
NavigationView: React$ComponentType<_RouterProp<S, O> & NavigationViewProps>
) => NavigationNavigator<S, O, NavigationViewProps>;
declare export function createNavigator<
S: NavigationState,
O: {},
NavigatorConfig: {},
NavigationViewProps: NavigationNavigatorProps<O, S>
>(
declare type NavigationDescriptor = {
key: string,
state: NavigationLeafRoute | NavigationStateRoute,
navigation: NavigationScreenProp<*>,
getComponent: () => React$ComponentType<{}>,
};
declare type NavigationView<O, S> = React$ComponentType<{
descriptors: { [key: string]: NavigationDescriptor },
navigation: NavigationScreenProp<S>,
}>;
declare export function createNavigator<O: *, S: *, NavigatorConfig: *>(
view: NavigationView<O, S>,
router: NavigationRouter<S, O>,
routeConfigs?: NavigationRouteConfigMap,
navigatorConfig?: NavigatorConfig
): _NavigatorCreator<NavigationViewProps, S, O>;
): any;
declare export function StackNavigator(
routeConfigMap: NavigationRouteConfigMap,

View File

@@ -4,7 +4,7 @@ import 'react-native';
import renderer from 'react-test-renderer';
import NavigationActions from '../NavigationActions';
import StackNavigator from '../navigators/StackNavigator';
import StackNavigator from '../navigators/createStackNavigator';
const FooScreen = () => <div />;
const BarScreen = () => <div />;

View File

@@ -49,57 +49,37 @@ const DefaultDrawerConfig = {
const DrawerNavigator = (routeConfigs, config = {}) => {
const mergedConfig = { ...DefaultDrawerConfig, ...config };
const {
containerConfig,
drawerWidth,
drawerLockMode,
contentComponent,
contentOptions,
drawerPosition,
useNativeAnimations,
drawerBackgroundColor,
drawerOpenRoute,
drawerCloseRoute,
drawerToggleRoute,
...tabsConfig
order,
paths,
initialRouteName,
backBehavior,
...drawerConfig
} = mergedConfig;
const tabsConfig = {
order,
paths,
initialRouteName,
backBehavior,
};
const contentRouter = TabRouter(routeConfigs, tabsConfig);
const drawerRouter = TabRouter(
{
[drawerCloseRoute]: {
screen: createNavigator(contentRouter, routeConfigs, config)(props => (
<DrawerScreen {...props} />
)),
[drawerConfig.drawerCloseRoute]: {
screen: createNavigator(DrawerScreen, contentRouter, config),
},
[drawerOpenRoute]: {
[drawerConfig.drawerOpenRoute]: {
screen: () => null,
},
[drawerToggleRoute]: {
[drawerConfig.drawerToggleRoute]: {
screen: () => null,
},
},
{
initialRouteName: drawerCloseRoute,
initialRouteName: drawerConfig.drawerCloseRoute,
}
);
const navigator = createNavigator(drawerRouter, routeConfigs, config)(
props => (
<DrawerView
{...props}
drawerBackgroundColor={drawerBackgroundColor}
drawerLockMode={drawerLockMode}
useNativeAnimations={useNativeAnimations}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
drawerOpenRoute={drawerOpenRoute}
drawerCloseRoute={drawerCloseRoute}
drawerToggleRoute={drawerToggleRoute}
/>
)
);
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
return createNavigationContainer(navigator);
};

View File

@@ -1,57 +0,0 @@
import React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createNavigator from './createNavigator';
import CardStackTransitioner from '../views/CardStack/CardStackTransitioner';
import StackRouter from '../routers/StackRouter';
import NavigationActions from '../NavigationActions';
// A stack navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
export default (routeConfigMap, stackConfig = {}) => {
const {
initialRouteName,
initialRouteParams,
paths,
headerMode,
headerTransitionPreset,
mode,
cardStyle,
transitionConfig,
onTransitionStart,
onTransitionEnd,
navigationOptions,
} = stackConfig;
const stackRouterConfig = {
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
// Create a navigator with CardStackTransitioner as the view
const navigator = createNavigator(router, routeConfigMap, stackConfig)(
props => (
<CardStackTransitioner
{...props}
headerMode={headerMode}
headerTransitionPreset={headerTransitionPreset}
mode={mode}
cardStyle={cardStyle}
transitionConfig={transitionConfig}
onTransitionStart={onTransitionStart}
onTransitionEnd={(lastTransition, transition) => {
const { state, dispatch } = props.navigation;
dispatch(NavigationActions.completeTransition({ key: state.key }));
onTransitionEnd && onTransitionEnd();
}}
/>
)
);
return createNavigationContainer(navigator);
};

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import renderer from 'react-test-renderer';
import StackNavigator from '../StackNavigator';
import StackNavigator from '../createStackNavigator';
const styles = StyleSheet.create({
header: {

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import TabNavigator from '../TabNavigator';
import TabNavigator from '../createTabNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({

View File

@@ -48,7 +48,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
pointerEvents="auto"
style={
Object {
"backgroundColor": "#EFEFF4",
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"opacity": 1,
@@ -234,7 +234,7 @@ exports[`StackNavigator renders successfully 1`] = `
pointerEvents="auto"
style={
Object {
"backgroundColor": "#EFEFF4",
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"opacity": 1,

View File

@@ -20,60 +20,110 @@ exports[`TabNavigator renders successfully 1`] = `
]
}
>
<View
collapsable={undefined}
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
<RCTScrollView
DEPRECATED_sendUpdatedChildFrames={false}
alwaysBounceHorizontal={false}
alwaysBounceVertical={false}
automaticallyAdjustContentInsets={false}
bounces={false}
contentContainerStyle={
Object {
"alignItems": "stretch",
"flex": 1,
"flexDirection": "row",
}
}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
contentOffset={
Object {
"x": 0,
"y": 0,
}
}
directionalLockEnabled={true}
horizontal={true}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
onContentSizeChange={null}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onResponderGrant={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={undefined}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
onScrollShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
onTouchCancel={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
overScrollMode="never"
pagingEnabled={true}
scrollEnabled={undefined}
scrollEventThrottle={1}
scrollsToTop={false}
sendMomentumEvents={true}
showsHorizontalScrollIndicator={false}
style={
Array [
Object {
"flexDirection": "row",
"flexGrow": 1,
"flexShrink": 1,
"overflow": "scroll",
},
Object {
"flex": 1,
},
]
}
>
<RCTScrollContentView
collapsable={false}
removeClippedSubviews={undefined}
style={
Array [
Object {
"flexDirection": "row",
},
Object {
"flex": 1,
},
]
}
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
/>
>
<View
style={
Object {
"flex": 1,
}
}
/>
</View>
</View>
</View>
</View>
</RCTScrollContentView>
</RCTScrollView>
<View
collapsable={undefined}
style={undefined}

View File

@@ -1,19 +1,49 @@
import React from 'react';
/**
* Creates a navigator based on a router and a view that renders the screens.
*/
export default function createNavigator(router, routeConfigs, navigatorConfig) {
return NavigationView => {
class Navigator extends React.Component {
static router = router;
static navigationOptions = null;
import getChildEventSubscriber from '../getChildEventSubscriber';
import addNavigationHelpers from '../addNavigationHelpers';
render() {
return <NavigationView {...this.props} router={router} />;
}
function createNavigator(NavigatorView, router, navigationConfig) {
class Navigator extends React.Component {
static router = router;
static navigationOptions = null;
render() {
const { navigation, screenProps } = this.props;
const { dispatch, state, addListener } = navigation;
const { routes } = state;
const descriptors = {};
routes.forEach(route => {
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
const childNavigation = addNavigationHelpers({
dispatch,
state: route,
addListener: getChildEventSubscriber(addListener, route.key),
});
const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = {
key: route.key,
getComponent,
options,
state: route,
navigation: childNavigation,
};
});
return (
<NavigatorView
screenProps={screenProps}
navigation={navigation}
navigationConfig={navigationConfig}
descriptors={descriptors}
/>
);
}
return Navigator;
};
}
return Navigator;
}
export default createNavigator;

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createNavigator from './createNavigator';
import StackView from '../views/StackView/StackView';
import StackRouter from '../routers/StackRouter';
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const {
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
} = stackConfig;
const stackRouterConfig = {
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
// Create a navigator with StackView as the view
const Navigator = createNavigator(StackView, router, stackConfig);
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
return createNavigationContainer(Navigator);
}
export default createStackNavigator;

View File

@@ -8,42 +8,13 @@ import TabView from '../views/TabView/TabView';
import TabBarTop from '../views/TabView/TabBarTop';
import TabBarBottom from '../views/TabView/TabBarBottom';
// A tab navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
const TabNavigator = (routeConfigs, config = {}) => {
// Use the look native to the platform by default
const mergedConfig = { ...TabNavigator.Presets.Default, ...config };
const {
tabBarComponent,
tabBarPosition,
tabBarOptions,
lazy,
removeClippedSubviews,
swipeEnabled,
animationEnabled,
configureTransition,
initialLayout,
...tabsConfig
} = mergedConfig;
const tabsConfig = { ...TabNavigator.Presets.Default, ...config };
const router = TabRouter(routeConfigs, tabsConfig);
const navigator = createNavigator(router, routeConfigs, config)(props => (
<TabView
{...props}
lazy={lazy}
removeClippedSubviews={removeClippedSubviews}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
configureTransition={configureTransition}
initialLayout={initialLayout}
/>
));
const navigator = createNavigator(TabView, router, tabsConfig);
return createNavigationContainer(navigator);
};

View File

@@ -20,10 +20,10 @@ module.exports = {
return require('./navigators/createNavigator').default;
},
get StackNavigator() {
return require('./navigators/StackNavigator').default;
return require('./navigators/createStackNavigator').default;
},
get TabNavigator() {
return require('./navigators/TabNavigator').default;
return require('./navigators/createTabNavigator').default;
},
get DrawerNavigator() {
return require('./navigators/DrawerNavigator').default;
@@ -41,14 +41,11 @@ module.exports = {
get Transitioner() {
return require('./views/Transitioner').default;
},
get CardStackTransitioner() {
return require('./views/CardStack/CardStackTransitioner').default;
get StackView() {
return require('./views/StackView/StackView').default;
},
get CardStack() {
return require('./views/CardStack/CardStack').default;
},
get Card() {
return require('./views/CardStack/Card').default;
get StackViewCard() {
return require('./views/StackView/StackViewCard').default;
},
get SafeAreaView() {
return require('react-native-safe-area-view').default;

View File

@@ -1,7 +1,6 @@
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import addNavigationHelpers from '../addNavigationHelpers';
import validateScreenOptions from './validateScreenOptions';
import getChildEventSubscriber from '../getChildEventSubscriber';
@@ -38,28 +37,6 @@ export default (routeConfigs, navigatorScreenConfig) => (
const Component = getScreenForRouteName(routeConfigs, route.routeName);
let outputConfig = {};
const router = Component.router;
if (router) {
const { routes, index } = route;
if (!route || !routes || index == null) {
throw new Error(
`Expect nav state to have routes and index, ${JSON.stringify(route)}`
);
}
const childRoute = routes[index];
const childNavigation = addNavigationHelpers({
state: childRoute,
dispatch,
addListener: getChildEventSubscriber(
navigation.addListener,
childRoute.key
),
});
outputConfig = router.getScreenOptions(childNavigation, screenProps);
}
const routeConfig = routeConfigs[route.routeName];
const routeScreenConfig = routeConfig.navigationOptions;
@@ -67,11 +44,7 @@ export default (routeConfigs, navigatorScreenConfig) => (
const configOptions = { navigation, screenProps: screenProps || {} };
outputConfig = applyConfig(
navigatorScreenConfig,
outputConfig,
configOptions
);
let outputConfig = applyConfig(navigatorScreenConfig, {}, configOptions);
outputConfig = applyConfig(
componentScreenConfig,
outputConfig,

View File

@@ -1,82 +0,0 @@
import React from 'react';
import { NativeModules } from 'react-native';
import CardStack from './CardStack';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
import Transitioner from '../Transitioner';
import TransitionConfigs from './TransitionConfigs';
const NativeAnimatedModule =
NativeModules && NativeModules.NativeAnimatedModule;
class CardStackTransitioner extends React.Component {
static defaultProps = {
mode: 'card',
};
render() {
return (
<Transitioner
configureTransition={this._configureTransition}
navigation={this.props.navigation}
render={this._render}
onTransitionStart={this.props.onTransitionStart}
onTransitionEnd={this.props.onTransitionEnd}
/>
);
}
_configureTransition = (
// props for the new screen
transitionProps,
// props for the old screen
prevTransitionProps
) => {
const isModal = this.props.mode === 'modal';
// Copy the object so we can assign useNativeDriver below
const transitionSpec = {
...TransitionConfigs.getTransitionConfig(
this.props.transitionConfig,
transitionProps,
prevTransitionProps,
isModal
).transitionSpec,
};
if (
!!NativeAnimatedModule &&
// Native animation support also depends on the transforms used:
CardStackStyleInterpolator.canUseNativeDriver()
) {
// Internal undocumented prop
transitionSpec.useNativeDriver = true;
}
return transitionSpec;
};
_render = (props, prevProps) => {
const {
screenProps,
headerMode,
headerTransitionPreset,
mode,
router,
cardStyle,
transitionConfig,
} = this.props;
return (
<CardStack
screenProps={screenProps}
headerMode={headerMode}
headerTransitionPreset={headerTransitionPreset}
mode={mode}
router={router}
cardStyle={cardStyle}
transitionConfig={transitionConfig}
transitionProps={props}
prevTransitionProps={prevProps}
/>
);
};
}
export default CardStackTransitioner;

View File

@@ -1,30 +1,24 @@
import React from 'react';
import SceneView from '../SceneView';
import withCachedChildNavigation from '../../withCachedChildNavigation';
/**
* Component that renders the child screen of the drawer.
*/
class DrawerScreen extends React.PureComponent {
render() {
const {
router,
navigation,
childNavigationProps,
screenProps,
} = this.props;
const { descriptors, navigation, screenProps } = this.props;
const { routes, index } = navigation.state;
const childNavigation = childNavigationProps[routes[index].key];
const Content = router.getComponentForRouteName(routes[index].routeName);
const descriptor = descriptors[routes[index].key];
const Content = descriptor.getComponent();
return (
<SceneView
screenProps={screenProps}
component={Content}
navigation={childNavigation}
navigation={descriptor.navigation}
/>
);
}
}
export default withCachedChildNavigation(DrawerScreen);
export default DrawerScreen;

View File

@@ -2,32 +2,21 @@ import React from 'react';
import { StyleSheet, View } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import withCachedChildNavigation from '../../withCachedChildNavigation';
import NavigationActions from '../../NavigationActions';
import invariant from '../../utils/invariant';
/**
* Component that renders the sidebar screen of the drawer.
*/
class DrawerSidebar extends React.PureComponent {
_getScreenOptions = routeKey => {
const DrawerScreen = this.props.router.getComponentForRouteName(
'DrawerClose'
);
const descriptor = this.props.descriptors[routeKey];
invariant(
DrawerScreen.router,
'NavigationComponent with routeName DrawerClose should be a Navigator'
);
const { [routeKey]: childNavigation } = this.props.childNavigationProps;
return DrawerScreen.router.getScreenOptions(
childNavigation.state.index !== undefined // if the child screen is a StackRouter then always show the screen options of its first screen (see #1914)
? {
...childNavigation,
state: { ...childNavigation.state, index: 0 },
}
: childNavigation,
this.props.screenProps
descriptor.options,
'Cannot access screen descriptor options from drawer sidebar'
);
return descriptor.options;
};
_getLabel = ({ focused, tintColor, route }) => {
@@ -86,6 +75,7 @@ class DrawerSidebar extends React.PureComponent {
<ContentComponent
{...this.props.contentOptions}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
items={state.routes}
activeItemKey={
state.routes[state.index] ? state.routes[state.index].key : null
@@ -94,7 +84,6 @@ class DrawerSidebar extends React.PureComponent {
getLabel={this._getLabel}
renderIcon={this._renderIcon}
onItemPress={this._onItemPress}
router={this.props.router}
drawerPosition={this.props.drawerPosition}
/>
</View>
@@ -102,7 +91,7 @@ class DrawerSidebar extends React.PureComponent {
}
}
export default withCachedChildNavigation(DrawerSidebar);
export default DrawerSidebar;
const styles = StyleSheet.create({
container: {

View File

@@ -12,14 +12,12 @@ import getChildEventSubscriber from '../../getChildEventSubscriber';
export default class DrawerView extends React.PureComponent {
state = {
drawerWidth:
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth,
typeof this.props.navigationConfig.drawerWidth === 'function'
? this.props.navigationConfig.drawerWidth()
: this.props.navigationConfig.drawerWidth,
};
componentWillMount() {
this._updateScreenNavigation(this.props.navigation);
Dimensions.addEventListener('change', this._updateWidth);
}
@@ -35,7 +33,7 @@ export default class DrawerView extends React.PureComponent {
drawerOpenRoute,
drawerCloseRoute,
drawerToggleRoute,
} = this.props;
} = this.props.navigationConfig;
const { routes, index } = nextProps.navigation.state;
if (routes[index].routeName === drawerOpenRoute) {
this._drawer.openDrawer();
@@ -49,11 +47,11 @@ export default class DrawerView extends React.PureComponent {
this._drawer.closeDrawer();
}
}
this._updateScreenNavigation(nextProps.navigation);
}
_handleDrawerOpen = () => {
const { navigation, drawerOpenRoute } = this.props;
const { navigation, navigationConfig } = this.props;
const { drawerOpenRoute } = navigationConfig;
const { routes, index } = navigation.state;
if (routes[index].routeName !== drawerOpenRoute) {
this.props.navigation.navigate(drawerOpenRoute);
@@ -61,39 +59,19 @@ export default class DrawerView extends React.PureComponent {
};
_handleDrawerClose = () => {
const { navigation, drawerCloseRoute } = this.props;
const { navigation, navigationConfig } = this.props;
const { drawerCloseRoute } = navigationConfig;
const { routes, index } = navigation.state;
if (routes[index].routeName !== drawerCloseRoute) {
this.props.navigation.navigate(drawerCloseRoute);
}
};
_updateScreenNavigation = navigation => {
const { drawerCloseRoute } = this.props;
const navigationState = navigation.state.routes.find(
route => route.routeName === drawerCloseRoute
);
if (
this._screenNavigationProp &&
this._screenNavigationProp.state === navigationState
) {
return;
}
this._screenNavigationProp = addNavigationHelpers({
dispatch: navigation.dispatch,
state: navigationState,
addListener: getChildEventSubscriber(
navigation.addListener,
navigationState.key
),
});
};
_updateWidth = () => {
const drawerWidth =
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth;
typeof this.props.navigationConfig.drawerWidth === 'function'
? this.props.navigationConfig.drawerWidth()
: this.props.navigationConfig.drawerWidth;
if (this.state.drawerWidth !== drawerWidth) {
this.setState({ drawerWidth });
@@ -101,34 +79,68 @@ export default class DrawerView extends React.PureComponent {
};
_getNavigationState = navigation => {
const { drawerCloseRoute } = this.props;
const { drawerCloseRoute } = this.props.navigationConfig;
const navigationState = navigation.state.routes.find(
route => route.routeName === drawerCloseRoute
);
return navigationState;
};
_renderNavigationView = () => (
<DrawerSidebar
screenProps={this.props.screenProps}
navigation={this._screenNavigationProp}
router={this.props.router}
contentComponent={this.props.contentComponent}
contentOptions={this.props.contentOptions}
drawerPosition={this.props.drawerPosition}
style={this.props.style}
/>
);
_renderNavigationView = () => {
const details = Object.values(this.props.descriptors).find(
d => d.state.routeName === this.props.navigationConfig.drawerCloseRoute
);
const router = details.getComponent().router;
const { state, addListener, dispatch } = this.props.navigation;
const { routes } = details.state;
const tabDescriptors = {};
routes.forEach(route => {
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
const childNavigation = addNavigationHelpers({
dispatch,
state: route,
addListener: getChildEventSubscriber(addListener, route.key),
});
const options = router.getScreenOptions(
childNavigation,
this.props.screenProps
);
tabDescriptors[route.key] = {
key: route.key,
getComponent,
options,
state: route,
navigation: childNavigation,
};
});
return (
<DrawerSidebar
screenProps={this.props.screenProps}
navigation={details.navigation}
descriptors={tabDescriptors}
contentComponent={this.props.navigationConfig.contentComponent}
contentOptions={this.props.navigationConfig.contentOptions}
drawerPosition={this.props.navigationConfig.drawerPosition}
style={this.props.navigationConfig.style}
{...this.props.navigationConfig}
/>
);
};
render() {
const DrawerScreen = this.props.router.getComponentForRouteName(
this.props.drawerCloseRoute
const descriptor = Object.values(this.props.descriptors).find(
d => d.state.routeName === this.props.navigationConfig.drawerCloseRoute
);
const config = this.props.router.getScreenOptions(
this._screenNavigationProp,
this.props.screenProps
);
const DrawerScreen = descriptor.getComponent();
const { drawerLockMode } = descriptor.options;
return (
<DrawerLayout
@@ -137,23 +149,25 @@ export default class DrawerView extends React.PureComponent {
}}
drawerLockMode={
(this.props.screenProps && this.props.screenProps.drawerLockMode) ||
(config && config.drawerLockMode)
this.props.navigationConfig.drawerLockMode
}
drawerBackgroundColor={
this.props.navigationConfig.drawerBackgroundColor
}
drawerBackgroundColor={this.props.drawerBackgroundColor}
drawerWidth={this.state.drawerWidth}
onDrawerOpen={this._handleDrawerOpen}
onDrawerClose={this._handleDrawerClose}
useNativeAnimations={this.props.useNativeAnimations}
useNativeAnimations={this.props.navigationConfig.useNativeAnimations}
renderNavigationView={this._renderNavigationView}
drawerPosition={
this.props.drawerPosition === 'right'
this.props.navigationConfig.drawerPosition === 'right'
? DrawerLayout.positions.Right
: DrawerLayout.positions.Left
}
>
<DrawerScreen
screenProps={this.props.screenProps}
navigation={this._screenNavigationProp}
navigation={descriptor.navigation}
/>
</DrawerLayout>
);

View File

@@ -47,11 +47,11 @@ class Header extends React.PureComponent {
};
_getHeaderTitleString(scene) {
const sceneOptions = this.props.getScreenDetails(scene).options;
if (typeof sceneOptions.headerTitle === 'string') {
return sceneOptions.headerTitle;
const options = scene.descriptor.options;
if (typeof options.headerTitle === 'string') {
return options.headerTitle;
}
return sceneOptions.title;
return options.title;
}
_getLastScene(scene) {
@@ -63,7 +63,7 @@ class Header extends React.PureComponent {
if (!lastScene) {
return null;
}
const { headerBackTitle } = this.props.getScreenDetails(lastScene).options;
const { headerBackTitle } = lastScene.descriptor.options;
if (headerBackTitle || headerBackTitle === null) {
return headerBackTitle;
}
@@ -75,27 +75,20 @@ class Header extends React.PureComponent {
if (!lastScene) {
return null;
}
return this.props.getScreenDetails(lastScene).options
.headerTruncatedBackTitle;
return lastScene.descriptor.options.headerTruncatedBackTitle;
}
_navigateBack = () => {
requestAnimationFrame(() => {
this.props.navigation.goBack(this.props.scene.route.key);
});
};
_renderTitleComponent = props => {
const details = this.props.getScreenDetails(props.scene);
const headerTitle = details.options.headerTitle;
const { options } = props.scene.descriptor;
const headerTitle = options.headerTitle;
if (React.isValidElement(headerTitle)) {
return headerTitle;
}
const titleString = this._getHeaderTitleString(props.scene);
const titleStyle = details.options.headerTitleStyle;
const color = details.options.headerTintColor;
const allowFontScaling = details.options.headerTitleAllowFontScaling;
const titleStyle = options.headerTitleStyle;
const color = options.headerTintColor;
const allowFontScaling = options.headerTitleAllowFontScaling;
// On iOS, width of left/right components depends on the calculated
// size of the title.
@@ -127,8 +120,7 @@ class Header extends React.PureComponent {
};
_renderLeftComponent = props => {
const { options } = this.props.getScreenDetails(props.scene);
const { options } = props.scene.descriptor;
if (
React.isValidElement(options.headerLeft) ||
options.headerLeft === null
@@ -148,9 +140,15 @@ class Header extends React.PureComponent {
? (this.props.layout.initWidth - this.state.widths[props.scene.key]) / 2
: undefined;
const RenderedLeftComponent = options.headerLeft || HeaderBackButton;
const goBack = () => {
// Go back on next tick because button ripple effect needs to happen on Android
requestAnimationFrame(() => {
this.props.navigation.goBack(props.scene.descriptor.key);
});
};
return (
<RenderedLeftComponent
onPress={this._navigateBack}
onPress={goBack}
pressColorAndroid={options.headerPressColorAndroid}
tintColor={options.headerTintColor}
buttonImage={options.headerBackImage}
@@ -167,7 +165,7 @@ class Header extends React.PureComponent {
ButtonContainerComponent,
LabelContainerComponent
) => {
const { options } = this.props.getScreenDetails(props.scene);
const { options } = props.scene.descriptor;
const backButtonTitle = this._getBackButtonTitleString(props.scene);
const truncatedBackButtonTitle = this._getTruncatedBackButtonTitle(
props.scene
@@ -193,13 +191,12 @@ class Header extends React.PureComponent {
};
_renderRightComponent = props => {
const details = this.props.getScreenDetails(props.scene);
const { headerRight } = details.options;
const { headerRight } = props.scene.descriptor.options;
return headerRight || null;
};
_renderLeft(props) {
const { options } = this.props.getScreenDetails(props.scene);
const { options } = props.scene.descriptor;
const { transitionPreset } = this.props;
@@ -374,7 +371,7 @@ class Header extends React.PureComponent {
});
const { isLandscape, transitionPreset } = this.props;
const { options } = this.props.getScreenDetails(props.scene);
const { options } = props.scene.descriptor;
const wrapperProps = {
style: styles.header,
@@ -439,7 +436,7 @@ class Header extends React.PureComponent {
});
}
const { options } = this.props.getScreenDetails(scene);
const { options } = scene.descriptor;
const { headerStyle = {} } = options;
const headerStyleObj = StyleSheet.flatten(headerStyle);
const appBarHeight = getAppBarHeight(isLandscape);

View File

@@ -59,7 +59,12 @@ function areRoutesShallowEqual(one, two) {
return shallowEqual(one, two);
}
export default function ScenesReducer(scenes, nextState, prevState) {
export default function ScenesReducer(
scenes,
nextState,
prevState,
descriptors
) {
if (prevState === nextState) {
return scenes;
}
@@ -80,12 +85,16 @@ export default function ScenesReducer(scenes, nextState, prevState) {
const nextKeys = new Set();
nextState.routes.forEach((route, index) => {
const key = SCENE_KEY_PREFIX + route.key;
let descriptor = descriptors && descriptors[route.key];
const scene = {
index,
isActive: false,
isStale: false,
key,
route,
descriptor,
};
invariant(
!nextKeys.has(key),
@@ -109,12 +118,16 @@ export default function ScenesReducer(scenes, nextState, prevState) {
if (freshScenes.has(key)) {
return;
}
const lastScene = scenes.find(scene => scene.route.key === route.key);
const descriptor = lastScene && lastScene.descriptor;
staleScenes.set(key, {
index,
isActive: false,
isStale: true,
key,
route,
descriptor,
});
});
}

View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import { NativeModules } from 'react-native';
import StackViewLayout from './StackViewLayout';
import Transitioner from '../Transitioner';
import NavigationActions from '../../NavigationActions';
import TransitionConfigs from './StackViewTransitionConfigs';
const NativeAnimatedModule =
NativeModules && NativeModules.NativeAnimatedModule;
class StackView extends React.Component {
static defaultProps = {
navigationConfig: {
mode: 'card',
},
};
render() {
return (
<Transitioner
render={this._render}
configureTransition={this._configureTransition}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
onTransitionStart={this.props.onTransitionStart}
onTransitionEnd={(lastTransition, transition) => {
const { onTransitionEnd, navigation } = this.props;
navigation.dispatch(
NavigationActions.completeTransition({
key: navigation.state.key,
})
);
onTransitionEnd && onTransitionEnd(lastTransition, transition);
}}
/>
);
}
_configureTransition = (transitionProps, prevTransitionProps) => {
return {
...TransitionConfigs.getTransitionConfig(
this.props.navigationConfig.transitionConfig,
transitionProps,
prevTransitionProps,
this.props.navigationConfig.mode === 'modal'
).transitionSpec,
useNativeDriver: !!NativeAnimatedModule,
};
};
_render = (transitionProps, lastTransitionProps) => {
const { screenProps, navigationConfig } = this.props;
return (
<StackViewLayout
{...navigationConfig}
screenProps={screenProps}
descriptors={this.props.descriptors}
transitionProps={transitionProps}
lastTransitionProps={lastTransitionProps}
/>
);
};
}
export default StackView;

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Animated, StyleSheet } from 'react-native';
import createPointerEventsContainer from './PointerEventsContainer';
import createPointerEventsContainer from './createPointerEventsContainer';
/**
* Component that renders the scene as card for the <NavigationCardStack />.
* Component that renders the scene as card for the <StackView />.
*/
class Card extends React.Component {
render() {
@@ -22,16 +22,12 @@ class Card extends React.Component {
const styles = StyleSheet.create({
main: {
backgroundColor: '#EFEFF4',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
...StyleSheet.absoluteFillObject,
backgroundColor: '#E9E9EF',
shadowColor: 'black',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.2,
shadowRadius: 5,
top: 0,
},
});

View File

@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import clamp from 'clamp';
import {
@@ -11,14 +11,13 @@ import {
Easing,
} from 'react-native';
import Card from './Card';
import Card from './StackViewCard';
import Header from '../Header/Header';
import NavigationActions from '../../NavigationActions';
import addNavigationHelpers from '../../addNavigationHelpers';
import getChildEventSubscriber from '../../getChildEventSubscriber';
import SceneView from '../SceneView';
import TransitionConfigs from './TransitionConfigs';
import TransitionConfigs from './StackViewTransitionConfigs';
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
const emptyFunction = () => {};
@@ -59,7 +58,7 @@ const animatedSubscribeValue = animatedValue => {
}
};
class CardStack extends React.Component {
class StackViewLayout extends React.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
@@ -80,52 +79,15 @@ class CardStack extends React.Component {
*/
_immediateIndex = null;
_screenDetails = {};
componentWillReceiveProps(props) {
if (props.screenProps !== this.props.screenProps) {
this._screenDetails = {};
}
props.transitionProps.scenes.forEach(newScene => {
if (
this._screenDetails[newScene.key] &&
this._screenDetails[newScene.key].state !== newScene.route
) {
this._screenDetails[newScene.key] = null;
}
});
}
_getScreenDetails = scene => {
const { screenProps, transitionProps: { navigation }, router } = this.props;
let screenDetails = this._screenDetails[scene.key];
if (!screenDetails || screenDetails.state !== scene.route) {
const screenNavigation = addNavigationHelpers({
dispatch: navigation.dispatch,
state: scene.route,
addListener: getChildEventSubscriber(
navigation.addListener,
scene.route.key
),
});
screenDetails = {
state: scene.route,
navigation: screenNavigation,
options: router.getScreenOptions(screenNavigation, screenProps),
};
this._screenDetails[scene.key] = screenDetails;
}
return screenDetails;
};
_renderHeader(scene, headerMode) {
const { header } = this._getScreenDetails(scene).options;
const { options } = scene.descriptor;
const { header } = options;
if (typeof header !== 'undefined' && typeof header !== 'function') {
return header;
}
const renderHeader = header || (props => <Header {...props} />);
const renderHeader = header || ((props: *) => <Header {...props} />);
const {
headerLeftInterpolator,
headerTitleInterpolator,
@@ -145,7 +107,6 @@ class CardStack extends React.Component {
scene,
mode: headerMode,
transitionPreset: this._getHeaderTransitionPreset(),
getScreenDetails: this._getScreenDetails,
leftInterpolator: headerLeftInterpolator,
titleInterpolator: headerTitleInterpolator,
rightInterpolator: headerRightInterpolator,
@@ -245,7 +206,8 @@ class CardStack extends React.Component {
} = this.props;
const { index } = navigation.state;
const isVertical = mode === 'modal';
const { options } = this._getScreenDetails(scene);
const { options } = scene.descriptor;
const gestureDirectionInverted = options.gestureDirection === 'inverted';
const gesturesEnabled =
@@ -261,7 +223,7 @@ class CardStack extends React.Component {
this._reset(index, 0);
},
onPanResponderGrant: () => {
position.stopAnimation(value => {
position.stopAnimation((value: number) => {
this._isResponding = true;
this._gestureStartValue = value;
});
@@ -285,9 +247,12 @@ class CardStack extends React.Component {
? axisLength - (currentDragPosition - currentDragDistance)
: currentDragPosition - currentDragDistance;
// Compare to the gesture distance relavant to card or modal
const { options } = scene.descriptor;
const {
gestureResponseDistance: userGestureResponseDistance = {},
} = this._getScreenDetails(scene).options;
} = options;
const gestureResponseDistance = isVertical
? userGestureResponseDistance.vertical ||
GESTURE_RESPONSE_DISTANCE_VERTICAL
@@ -388,7 +353,7 @@ class CardStack extends React.Component {
return (
<View {...handlers} style={containerStyle}>
<View style={styles.scenes}>
{scenes.map(s => this._renderCard(s))}
{scenes.map((s: *) => this._renderCard(s))}
</View>
{floatingHeader}
</View>
@@ -420,8 +385,10 @@ class CardStack extends React.Component {
}
}
_renderInnerScene(SceneComponent, scene) {
const { navigation } = this._getScreenDetails(scene);
_renderInnerScene(scene) {
const { options, navigation, getComponent } = scene.descriptor;
const SceneComponent = getComponent();
const { screenProps } = this.props;
const headerMode = this._getHeaderMode();
if (headerMode === 'screen') {
@@ -440,7 +407,7 @@ class CardStack extends React.Component {
}
return (
<SceneView
screenProps={this.props.screenProps}
screenProps={screenProps}
navigation={navigation}
component={SceneComponent}
/>
@@ -464,21 +431,14 @@ class CardStack extends React.Component {
screenInterpolator &&
screenInterpolator({ ...this.props.transitionProps, scene });
const SceneComponent = this.props.router.getComponentForRouteName(
scene.route.routeName
);
const { transitionProps, ...props } = this.props;
return (
<Card
{...props}
{...transitionProps}
{...this.props.transitionProps}
key={`card_${scene.key}`}
style={[style, this.props.cardStyle]}
scene={scene}
>
{this._renderInnerScene(SceneComponent, scene)}
{this._renderInnerScene(scene)}
</Card>
);
};
@@ -498,4 +458,4 @@ const styles = StyleSheet.create({
},
});
export default CardStack;
export default StackViewLayout;

View File

@@ -159,17 +159,9 @@ function forFade(props) {
};
}
function canUseNativeDriver() {
// 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.
return true;
}
export default {
forHorizontal,
forVertical,
forFadeFromBottomAndroid,
forFade,
canUseNativeDriver,
};

View File

@@ -1,5 +1,5 @@
import { Animated, Easing, Platform } from 'react-native';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
import StyleInterpolator from './StackViewStyleInterpolator';
import * as ReactNativeFeatures from '../../utils/ReactNativeFeatures';
let IOSTransitionSpec;
@@ -23,7 +23,7 @@ if (ReactNativeFeatures.supportsImprovedSpringAnimation()) {
// Standard iOS navigation transition
const SlideFromRightIOS = {
transitionSpec: IOSTransitionSpec,
screenInterpolator: CardStackStyleInterpolator.forHorizontal,
screenInterpolator: StyleInterpolator.forHorizontal,
containerStyle: {
backgroundColor: '#000',
},
@@ -32,7 +32,7 @@ const SlideFromRightIOS = {
// Standard iOS navigation transition for modals
const ModalSlideFromBottomIOS = {
transitionSpec: IOSTransitionSpec,
screenInterpolator: CardStackStyleInterpolator.forVertical,
screenInterpolator: StyleInterpolator.forVertical,
containerStyle: {
backgroundColor: '#000',
},
@@ -46,7 +46,7 @@ const FadeInFromBottomAndroid = {
easing: Easing.out(Easing.poly(5)), // decelerate
timing: Animated.timing,
},
screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
};
// Standard Android navigation transition when closing an Activity
@@ -57,15 +57,12 @@ const FadeOutToBottomAndroid = {
easing: Easing.in(Easing.poly(4)), // accelerate
timing: Animated.timing,
},
screenInterpolator: CardStackStyleInterpolator.forFadeFromBottomAndroid,
screenInterpolator: StyleInterpolator.forFadeFromBottomAndroid,
};
function defaultTransitionConfig(
// props for the new screen
transitionProps,
// props for the old screen
prevTransitionProps,
// whether we're animating in/out a modal screen
isModal
) {
if (Platform.OS === 'android') {
@@ -89,9 +86,7 @@ function defaultTransitionConfig(
function getTransitionConfig(
transitionConfigurer,
// props for the new screen
transitionProps,
// props for the old screen
prevTransitionProps,
isModal
) {

View File

@@ -9,7 +9,7 @@ const MIN_POSITION_OFFSET = 0.01;
* `pointerEvents` property for a component whenever navigation position
* changes.
*/
export default function create(Component) {
export default function createPointerEventsContainer(Component) {
class Container extends React.Component {
constructor(props, context) {
super(props, context);

View File

@@ -4,7 +4,6 @@ import { TabViewAnimated, TabViewPagerPan } from 'react-native-tab-view';
import SafeAreaView from 'react-native-safe-area-view';
import ResourceSavingSceneView from '../ResourceSavingSceneView';
import withCachedChildNavigation from '../../withCachedChildNavigation';
class TabView extends React.PureComponent {
static defaultProps = {
@@ -22,31 +21,33 @@ class TabView extends React.PureComponent {
};
_renderScene = ({ route }) => {
const { screenProps } = this.props;
const childNavigation = this.props.childNavigationProps[route.key];
const TabComponent = this.props.router.getComponentForRouteName(
route.routeName
);
const { screenProps, descriptors } = this.props;
const {
lazy,
removeClippedSubviews,
animationEnabled,
swipeEnabled,
} = this.props.navigationConfig;
const descriptor = descriptors[route.key];
const TabComponent = descriptor.getComponent();
return (
<ResourceSavingSceneView
lazy={this.props.lazy}
removeClippedSubViews={this.props.removeClippedSubviews}
animationEnabled={this.props.animationEnabled}
swipeEnabled={this.props.swipeEnabled}
lazy={lazy}
removeClippedSubViews={removeClippedSubviews}
animationEnabled={animationEnabled}
swipeEnabled={swipeEnabled}
screenProps={screenProps}
component={TabComponent}
navigation={this.props.navigation}
childNavigation={childNavigation}
childNavigation={descriptor.navigation}
/>
);
};
_getLabel = ({ route, tintColor, focused }) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
);
const { screenProps, descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarLabel) {
return typeof options.tabBarLabel === 'function'
@@ -62,19 +63,17 @@ class TabView extends React.PureComponent {
};
_getOnPress = (previousScene, { route }) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
);
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
return options.tabBarOnPress;
};
_getTestIDProps = ({ route, focused }) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
);
_getTestIDProps = ({ route }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
return typeof options.tabBarTestIDProps === 'function'
? options.tabBarTestIDProps({ focused })
@@ -82,10 +81,10 @@ class TabView extends React.PureComponent {
};
_renderIcon = ({ focused, route, tintColor }) => {
const options = this.props.router.getScreenOptions(
this.props.childNavigationProps[route.key],
this.props.screenProps || {}
);
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
? options.tabBarIcon({ tintColor, focused })
@@ -99,7 +98,8 @@ class TabView extends React.PureComponent {
tabBarOptions,
tabBarComponent: TabBarComponent,
animationEnabled,
} = this.props;
tabBarPosition,
} = this.props.navigationConfig;
if (typeof TabBarComponent === 'undefined') {
return null;
}
@@ -108,7 +108,7 @@ class TabView extends React.PureComponent {
<TabBarComponent
{...props}
{...tabBarOptions}
tabBarPosition={this.props.tabBarPosition}
tabBarPosition={tabBarPosition}
screenProps={this.props.screenProps}
navigation={this.props.navigation}
getLabel={this._getLabel}
@@ -124,31 +124,29 @@ class TabView extends React.PureComponent {
render() {
const {
router,
tabBarComponent,
tabBarPosition,
animationEnabled,
configureTransition,
initialLayout,
screenProps,
} = this.props;
} = this.props.navigationConfig;
let renderHeader;
let renderFooter;
let renderPager;
const { state } = this.props.navigation;
const options = router.getScreenOptions(
this.props.childNavigationProps[state.routes[state.index].key],
screenProps || {}
);
const route = state.routes[state.index];
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
const tabBarVisible =
options.tabBarVisible == null ? true : options.tabBarVisible;
let swipeEnabled =
options.swipeEnabled == null
? this.props.swipeEnabled
? this.props.navigationConfig.swipeEnabled
: options.swipeEnabled;
if (typeof swipeEnabled === 'function') {
@@ -181,7 +179,6 @@ class TabView extends React.PureComponent {
renderScene: this._renderScene,
onIndexChange: this._handlePageChanged,
navigationState: this.props.navigation.state,
screenProps: this.props.screenProps,
style: styles.container,
};
@@ -189,7 +186,7 @@ class TabView extends React.PureComponent {
}
}
export default withCachedChildNavigation(TabView);
export default TabView;
const styles = StyleSheet.create({
container: {

View File

@@ -29,7 +29,12 @@ class Transitioner extends React.Component {
layout,
position: new Animated.Value(this.props.navigation.state.index),
progress: new Animated.Value(1),
scenes: NavigationScenesReducer([], this.props.navigation.state),
scenes: NavigationScenesReducer(
[],
this.props.navigation.state,
null,
this.props.descriptors
),
};
this._prevTransitionProps = null;
@@ -56,7 +61,8 @@ class Transitioner extends React.Component {
const nextScenes = NavigationScenesReducer(
this.state.scenes,
nextProps.navigation.state,
this.props.navigation.state
this.props.navigation.state,
nextProps.descriptors
);
if (nextScenes === this.state.scenes) {

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import TabRouter from '../../routers/TabRouter';
import TabView from '../TabView/TabView';
import TabBarBottom from '../TabView/TabBarBottom';
@@ -12,21 +11,30 @@ const dummyEventSubscriber = (name, handler) => ({
describe('TabBarBottom', () => {
it('renders successfully', () => {
const route = { key: 's1', routeName: 's1' };
const navigation = {
state: {
index: 0,
routes: [{ key: 's1', routeName: 's1' }],
routes: [route],
},
addListener: dummyEventSubscriber,
};
const router = TabRouter({ s1: { screen: View } });
const rendered = renderer
.create(
<TabView
tabBarComponent={TabBarBottom}
navigation={navigation}
router={router}
navigationConfig={{}}
descriptors={{
s1: {
state: route,
key: route.key,
options: {},
navigation: { state: route },
getComponent: () => View,
},
}}
/>
)
.toJSON();

View File

@@ -20,127 +20,6 @@ exports[`TabBarBottom renders successfully 1`] = `
]
}
>
<View
collapsable={undefined}
style={undefined}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
"borderTopColor": "rgba(0, 0, 0, .3)",
"borderTopWidth": 0.5,
"flexDirection": "row",
"height": 49,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 0,
}
}
>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"backgroundColor": "rgba(0, 0, 0, 0)",
"flex": 1,
}
}
testID={undefined}
>
<View
style={
Array [
Object {
"alignItems": "center",
"flex": 1,
},
Object {
"flexDirection": "column",
"justifyContent": "flex-end",
},
undefined,
]
}
>
<View
style={
Object {
"flexGrow": 1,
}
}
>
<View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
collapsable={undefined}
style={
Object {
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
<Text
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
style={
Object {
"backgroundColor": "transparent",
"color": "rgba(52, 120, 246, 1)",
"fontSize": 10,
"marginBottom": 1.5,
"textAlign": "center",
}
}
>
s1
</Text>
</View>
</View>
</View>
</View>
<RCTScrollView
DEPRECATED_sendUpdatedChildFrames={false}
alwaysBounceHorizontal={false}
@@ -244,16 +123,6 @@ exports[`TabBarBottom renders successfully 1`] = `
<View
navigation={
Object {
"addListener": [Function],
"dispatch": undefined,
"getParam": [Function],
"goBack": [Function],
"navigate": [Function],
"pop": [Function],
"popToTop": [Function],
"push": [Function],
"replace": [Function],
"setParams": [Function],
"state": Object {
"key": "s1",
"routeName": "s1",

View File

@@ -1,51 +0,0 @@
import React from 'react';
import addNavigationHelpers from './addNavigationHelpers';
import getChildEventSubscriber from './getChildEventSubscriber';
/**
* HOC which caches the child navigation items.
*/
export default function withCachedChildNavigation(Comp) {
const displayName = Comp.displayName || Comp.name;
return class extends React.PureComponent {
static displayName = `withCachedChildNavigation(${displayName})`;
componentWillMount() {
this._updateNavigationProps(this.props.navigation);
}
componentWillReceiveProps(nextProps) {
this._updateNavigationProps(nextProps.navigation);
}
_updateNavigationProps = navigation => {
// Update props for each child route
if (!this._childNavigationProps) {
this._childNavigationProps = {};
}
navigation.state.routes.forEach(route => {
const childNavigation = this._childNavigationProps[route.key];
if (childNavigation && childNavigation.state === route) {
return;
}
this._childNavigationProps[route.key] = addNavigationHelpers({
dispatch: navigation.dispatch,
state: route,
addListener: getChildEventSubscriber(
navigation.addListener,
route.key
),
});
});
};
render() {
return (
<Comp
{...this.props}
childNavigationProps={this._childNavigationProps}
/>
);
}
};
}