Clean up APIs.

Reviewed By: ericvicenti

Differential Revision: D3010136

fb-gh-sync-id: 310864450bfc86ebc2d696f8ef4876b14fa3a57f
shipit-source-id: 310864450bfc86ebc2d696f8ef4876b14fa3a57f
This commit is contained in:
Hedger Wang 2016-03-04 14:56:37 -08:00 committed by Facebook Github Bot 7
parent fbef6f6893
commit 71e59761c9
23 changed files with 788 additions and 730 deletions

View File

@ -48,7 +48,10 @@ const NavigationBasicReducer = NavigationReducer.StackReducer({
class NavigationAnimatedExample extends React.Component { class NavigationAnimatedExample extends React.Component {
componentWillMount() { componentWillMount() {
this._renderNavigated = this._renderNavigated.bind(this); this._renderNavigation = this._renderNavigation.bind(this);
this._renderCard = this._renderCard.bind(this);
this._renderScene = this._renderScene.bind(this);
this._renderHeader = this._renderHeader.bind(this);
} }
render() { render() {
return ( return (
@ -56,7 +59,7 @@ class NavigationAnimatedExample extends React.Component {
reducer={NavigationBasicReducer} reducer={NavigationBasicReducer}
ref={navRootContainer => { this.navRootContainer = navRootContainer; }} ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
persistenceKey="NavigationAnimExampleState" persistenceKey="NavigationAnimExampleState"
renderNavigation={this._renderNavigated} renderNavigation={this._renderNavigation}
/> />
); );
} }
@ -66,7 +69,7 @@ class NavigationAnimatedExample extends React.Component {
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
); );
} }
_renderNavigated(navigationState, onNavigate) { _renderNavigation(navigationState, onNavigate) {
if (!navigationState) { if (!navigationState) {
return null; return null;
} }
@ -74,46 +77,56 @@ class NavigationAnimatedExample extends React.Component {
<NavigationAnimatedView <NavigationAnimatedView
navigationState={navigationState} navigationState={navigationState}
style={styles.animatedView} style={styles.animatedView}
renderOverlay={(props) => ( renderOverlay={this._renderHeader}
<NavigationHeader
navigationState={props.navigationParentState}
position={props.position}
getTitle={state => state.key}
/>
)}
setTiming={(pos, navState) => { setTiming={(pos, navState) => {
Animated.timing(pos, {toValue: navState.index, duration: 1000}).start(); Animated.timing(pos, {toValue: navState.index, duration: 1000}).start();
}} }}
renderScene={(props) => ( renderScene={this._renderCard}
<NavigationCard
key={props.navigationState.key}
index={props.index}
navigationState={props.navigationParentState}
position={props.position}
layout={props.layout}>
<ScrollView style={styles.scrollView}>
<NavigationExampleRow
text={props.navigationState.key}
/>
<NavigationExampleRow
text="Push!"
onPress={() => {
onNavigate({
type: 'push',
key: 'Route #' + props.navigationParentState.children.length
});
}}
/>
<NavigationExampleRow
text="Exit Animated Nav Example"
onPress={this.props.onExampleExit}
/>
</ScrollView>
</NavigationCard>
)}
/> />
); );
} }
_renderHeader(/*NavigationSceneRendererProps*/ props) {
return (
<NavigationHeader
{...props}
getTitle={state => state.key}
/>
);
}
_renderCard(/*NavigationSceneRendererProps*/ props) {
return (
<NavigationCard
{...props}
key={'card_' + props.scene.navigationState.key}
renderScene={this._renderScene}
/>
);
}
_renderScene(/*NavigationSceneRendererProps*/ props) {
return (
<ScrollView style={styles.scrollView}>
<NavigationExampleRow
text={props.scene.navigationState.key}
/>
<NavigationExampleRow
text="Push!"
onPress={() => {
props.onNavigate({
type: 'push',
key: 'Route #' + props.scenes.length,
});
}}
/>
<NavigationExampleRow
text="Exit Animated Nav Example"
onPress={this.props.onExampleExit}
/>
</ScrollView>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@ -49,6 +49,7 @@ function reduceNavigationState(initialState) {
const ExampleReducer = reduceNavigationState({ const ExampleReducer = reduceNavigationState({
index: 0, index: 0,
key: 'exmaple',
children: [{key: 'First Route'}], children: [{key: 'First Route'}],
}); });
@ -56,12 +57,13 @@ class NavigationCardStackExample extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = {isHorizontal: true};
}
componentWillMount() {
this._renderNavigation = this._renderNavigation.bind(this); this._renderNavigation = this._renderNavigation.bind(this);
this._renderScene = this._renderScene.bind(this); this._renderScene = this._renderScene.bind(this);
this._toggleDirection = this._toggleDirection.bind(this); this._toggleDirection = this._toggleDirection.bind(this);
this.state = {isHorizontal: true};
} }
render() { render() {
@ -86,8 +88,7 @@ class NavigationCardStackExample extends React.Component {
); );
} }
_renderScene(props) { _renderScene(/*NavigationSceneRendererProps*/ props) {
const {navigationParentState, onNavigate} = props;
return ( return (
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
<NavigationExampleRow <NavigationExampleRow
@ -99,21 +100,21 @@ class NavigationCardStackExample extends React.Component {
onPress={this._toggleDirection} onPress={this._toggleDirection}
/> />
<NavigationExampleRow <NavigationExampleRow
text={'route = ' + props.navigationState.key} text={'route = ' + props.scene.navigationState.key}
/> />
<NavigationExampleRow <NavigationExampleRow
text="Push Route" text="Push Route"
onPress={() => { onPress={() => {
onNavigate({ props.onNavigate({
type: 'push', type: 'push',
key: 'Route ' + navigationParentState.children.length, key: 'Route ' + props.scenes.length,
}); });
}} }}
/> />
<NavigationExampleRow <NavigationExampleRow
text="Pop Route" text="Pop Route"
onPress={() => { onPress={() => {
onNavigate({ props.onNavigate({
type: 'pop', type: 'pop',
}); });
}} }}

View File

@ -16,25 +16,32 @@
'use strict'; 'use strict';
const React = require('react-native'); const React = require('react-native');
const NavigationExampleRow = require('./NavigationExampleRow');
const NavigationExampleTabBar = require('./NavigationExampleTabBar');
const { const {
NavigationExperimental, NavigationExperimental,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
View, View,
} = React; } = React;
const { const {
AnimatedView: NavigationAnimatedView, AnimatedView: NavigationAnimatedView,
Card: NavigationCard, CardStack: NavigationCardStack,
Container: NavigationContainer, Container: NavigationContainer,
RootContainer: NavigationRootContainer,
Header: NavigationHeader, Header: NavigationHeader,
Reducer: NavigationReducer, Reducer: NavigationReducer,
RootContainer: NavigationRootContainer,
View: NavigationView, View: NavigationView,
} = NavigationExperimental; } = NavigationExperimental;
const NavigationExampleRow = require('./NavigationExampleRow');
const NavigationExampleTabBar = require('./NavigationExampleTabBar');
import type {NavigationParentState} from 'NavigationStateUtils';
import type {
NavigationParentState,
NavigationSceneRenderer,
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
type Action = { type Action = {
isExitAction?: boolean, isExitAction?: boolean,
@ -43,6 +50,7 @@ type Action = {
const ExampleExitAction = () => ({ const ExampleExitAction = () => ({
isExitAction: true, isExitAction: true,
}); });
ExampleExitAction.match = (action: Action) => ( ExampleExitAction.match = (action: Action) => (
action && action.isExitAction === true action && action.isExitAction === true
); );
@ -51,6 +59,7 @@ const PageAction = (type) => ({
type, type,
isPageAction: true, isPageAction: true,
}); });
PageAction.match = (action) => ( PageAction.match = (action) => (
action && action.isPageAction === true action && action.isPageAction === true
); );
@ -59,6 +68,7 @@ const ExampleProfilePageAction = (type) => ({
...PageAction(type), ...PageAction(type),
isProfilePageAction: true, isProfilePageAction: true,
}); });
ExampleProfilePageAction.match = (action) => ( ExampleProfilePageAction.match = (action) => (
action && action.isProfilePageAction === true action && action.isProfilePageAction === true
); );
@ -68,7 +78,9 @@ const ExampleInfoAction = () => PageAction('InfoPage');
const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage'); const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage');
const _jsInstanceUniqueId = '' + Date.now(); const _jsInstanceUniqueId = '' + Date.now();
let _uniqueIdCount = 0; let _uniqueIdCount = 0;
function pageStateActionMap(action) { function pageStateActionMap(action) {
return { return {
key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++), key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++),
@ -144,55 +156,57 @@ function stateTypeTitleMap(pageState) {
} }
class ExampleTabScreen extends React.Component { class ExampleTabScreen extends React.Component {
_renderCard: NavigationSceneRenderer;
_renderHeader: NavigationSceneRenderer;
_renderScene: NavigationSceneRenderer;
componentWillMount() {
this._renderHeader = this._renderHeader.bind(this);
this._renderScene = this._renderScene.bind(this);
}
render() { render() {
return ( return (
<NavigationAnimatedView <NavigationCardStack
style={styles.tabContent} style={styles.tabContent}
navigationState={this.props.navigationState} navigationState={this.props.navigationState}
renderOverlay={this._renderHeader.bind(this)} renderOverlay={this._renderHeader}
renderScene={this._renderScene.bind(this)} renderScene={this._renderScene}
/> />
); );
} }
_renderHeader(props) { _renderHeader(props: NavigationSceneRendererProps) {
return ( return (
<NavigationHeader <NavigationHeader
navigationState={props.navigationParentState} {...props}
position={props.position}
layout={props.layout}
getTitle={state => stateTypeTitleMap(state)} getTitle={state => stateTypeTitleMap(state)}
/> />
); );
} }
_renderScene(props) {
_renderScene(props: NavigationSceneRendererProps) {
const {onNavigate} = props;
return ( return (
<NavigationCard <ScrollView style={styles.scrollView}>
key={props.navigationState.key} <NavigationExampleRow
index={props.index} text="Open page"
navigationState={props.navigationParentState} onPress={() => {
position={props.position} onNavigate(ExampleInfoAction());
layout={props.layout}> }}
<ScrollView style={styles.scrollView}> />
<NavigationExampleRow <NavigationExampleRow
text="Open page" text="Open a page in the profile tab"
onPress={() => { onPress={() => {
this.props.onNavigate(ExampleInfoAction()); onNavigate(ExampleNotifProfileAction());
}} }}
/> />
<NavigationExampleRow <NavigationExampleRow
text="Open a page in the profile tab" text="Exit Composition Example"
onPress={() => { onPress={() => {
this.props.onNavigate(ExampleNotifProfileAction()); onNavigate(ExampleExitAction());
}} }}
/> />
<NavigationExampleRow </ScrollView>
text="Exit Composition Example"
onPress={() => {
this.props.onNavigate(ExampleExitAction());
}}
/>
</ScrollView>
</NavigationCard>
); );
} }
} }

View File

@ -36,8 +36,7 @@ const {
} = React; } = React;
const { const {
AnimatedView: NavigationAnimatedView, CardStack: NavigationCardStack,
Card: NavigationCard,
Header: NavigationHeader, Header: NavigationHeader,
Reducer: NavigationReducer, Reducer: NavigationReducer,
RootContainer: NavigationRootContainer, RootContainer: NavigationRootContainer,
@ -45,7 +44,7 @@ const {
import type { Value } from 'Animated'; import type { Value } from 'Animated';
import type { NavigationStateRendererProps } from 'NavigationAnimatedView'; import type { NavigationSceneRendererProps } from 'NavigationTypeDefinition';
import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer'; import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer';
@ -78,7 +77,6 @@ function URIActionMap(uri: ?string): ?Object {
return PathActionMap(path); return PathActionMap(path);
} }
class UIExplorerApp extends React.Component { class UIExplorerApp extends React.Component {
_navigationRootRef: ?NavigationRootContainer; _navigationRootRef: ?NavigationRootContainer;
_renderNavigation: Function; _renderNavigation: Function;
@ -89,7 +87,6 @@ class UIExplorerApp extends React.Component {
this._renderNavigation = this._renderNavigation.bind(this); this._renderNavigation = this._renderNavigation.bind(this);
this._renderOverlay = this._renderOverlay.bind(this); this._renderOverlay = this._renderOverlay.bind(this);
this._renderScene = this._renderScene.bind(this); this._renderScene = this._renderScene.bind(this);
this._renderCard = this._renderCard.bind(this);
} }
render() { render() {
return ( return (
@ -118,39 +115,27 @@ class UIExplorerApp extends React.Component {
} }
const {stack} = navigationState; const {stack} = navigationState;
return ( return (
<NavigationAnimatedView <NavigationCardStack
navigationState={stack} navigationState={stack}
style={styles.container} style={styles.container}
renderOverlay={this._renderOverlay} renderOverlay={this._renderOverlay}
renderScene={this._renderCard} renderScene={this._renderScene}
/> />
); );
} }
_renderOverlay(props: NavigationStateRendererProps): ReactElement { _renderOverlay(props: NavigationSceneRendererProps): ReactElement {
return ( return (
<NavigationHeader <NavigationHeader
navigationState={props.navigationParentState} {...props}
position={props.position} key={'header_' + props.scene.navigationState.key}
getTitle={UIExplorerStateTitleMap} getTitle={UIExplorerStateTitleMap}
/> />
); );
} }
_renderCard(props: NavigationStateRendererProps): ReactElement { _renderScene(props: NavigationSceneRendererProps): ?ReactElement {
return ( const state = props.scene.navigationState;
<NavigationCard
index={props.index}
key={props.navigationState.key}
layout={props.layout}
navigationState={props.navigationParentState}
position={props.position}>
{this._renderScene(props.navigationState)}
</NavigationCard>
);
}
_renderScene(state: Object): ?ReactElement {
if (state.key === 'AppList') { if (state.key === 'AppList') {
return ( return (
<UIExplorerExampleList <UIExplorerExampleList

View File

@ -26,7 +26,7 @@ const {
} = NavigationExperimental; } = NavigationExperimental;
const StackReducer = NavigationReducer.StackReducer; const StackReducer = NavigationReducer.StackReducer;
import type {NavigationState} from 'NavigationStateUtils'; import type {NavigationState} from 'NavigationTypeDefinition';
import type {UIExplorerAction} from './UIExplorerActions'; import type {UIExplorerAction} from './UIExplorerActions';
@ -93,7 +93,7 @@ function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, acti
if (newStack !== lastState.stack) { if (newStack !== lastState.stack) {
return { return {
externalExample: null, externalExample: null,
stack: newStack, stack: newStack,
} }
} }
return lastState; return lastState;

View File

@ -18,7 +18,7 @@
// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? // $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS?
const UIExplorerList = require('./UIExplorerList'); const UIExplorerList = require('./UIExplorerList');
import type {NavigationState} from 'NavigationStateUtils'; import type {NavigationState} from 'NavigationTypeDefinition';
function StateTitleMap(state: NavigationState): string { function StateTitleMap(state: NavigationState): string {
if (UIExplorerList.Modules[state.key]) { if (UIExplorerList.Modules[state.key]) {

View File

@ -28,142 +28,260 @@
'use strict'; 'use strict';
const Animated = require('Animated'); const Animated = require('Animated');
const NavigationRootContainer = require('NavigationRootContainer');
const NavigationContainer = require('NavigationContainer'); const NavigationContainer = require('NavigationContainer');
const PanResponder = require('PanResponder'); const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
const Platform = require('Platform'); const NavigationPropTypes = require('NavigationPropTypes');
const React = require('React'); const React = require('React');
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
const View = require('View'); const View = require('View');
const ENABLE_GESTURES = Platform.OS !== 'android'; const {Directions} = NavigationLinearPanResponder;
import type {
NavigationAnimatedValue,
NavigationLayout,
NavigationPosition,
NavigationSceneRenderer,
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
import type { import type {
NavigationParentState NavigationGestureDirection
} from 'NavigationStateUtils'; } from 'NavigationLinearPanResponder';
type Layout = { type State = {
initWidth: number, hash: string,
initHeight: number, height: number,
width: Animated.Value; width: number,
height: Animated.Value;
}; };
type Props = { type Props = NavigationSceneRendererProps & {
navigationState: NavigationParentState; direction: NavigationGestureDirection,
index: number; renderScene: NavigationSceneRenderer,
position: Animated.Value;
layout: Layout;
onNavigate: Function;
children: Object;
}; };
class NavigationCard extends React.Component { const {PropTypes} = React;
_responder: ?Object;
_lastHeight: number; const propTypes = {
_lastWidth: number; ...NavigationPropTypes.SceneRenderer,
_widthListener: string; direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
_heightListener: string; renderScene: PropTypes.func.isRequired,
props: Props; };
componentWillMount() {
if (ENABLE_GESTURES) { const defaultProps = {
this._enableGestures(); direction: Directions.HORIZONTAL,
} };
class AmimatedValueSubscription {
_value: NavigationAnimatedValue;
_token: string;
constructor(value: NavigationAnimatedValue, callback: Function) {
this._value = value;
this._token = value.addListener(callback);
} }
_enableGestures() {
this._responder = PanResponder.create({ remove() {
onMoveShouldSetPanResponder: (e, {dx, dy, moveX, moveY, x0, y0}) => { this._value.removeListener(this._token);
if (this.props.navigationState.index === 0) {
return false;
}
if (moveX > 30) {
return false;
}
if (dx > 5 && Math.abs(dy) < 4) {
return true;
}
return false;
},
onPanResponderGrant: (e, {dx, dy, moveX, moveY, x0, y0}) => {
},
onPanResponderMove: (e, {dx}) => {
const a = (-dx / this._lastWidth) + this.props.navigationState.index;
this.props.position.setValue(a);
},
onPanResponderRelease: (e, {vx, dx}) => {
const xRatio = dx / this._lastWidth;
const doesPop = (xRatio + vx) > 0.45;
if (doesPop) {
// todo: add an action which accepts velocity of the pop action/gesture, which is caught and used by NavigationAnimatedView
this.props.onNavigate(NavigationRootContainer.getBackAction());
return;
}
Animated.spring(this.props.position, {
toValue: this.props.navigationState.index,
}).start();
},
onPanResponderTerminate: (e, {vx, dx}) => {
Animated.spring(this.props.position, {
toValue: this.props.navigationState.index,
}).start();
},
});
}
componentDidMount() {
this._lastHeight = this.props.layout.initHeight;
this._lastWidth = this.props.layout.initWidth;
this._widthListener = this.props.layout.width.addListener(({value}) => {
this._lastWidth = value;
});
this._heightListener = this.props.layout.height.addListener(({value}) => {
this._lastHeight = value;
});
// todo: fix listener and last layout dimentsions when props change. potential bugs here
}
componentWillUnmount() {
this.props.layout.width.removeListener(this._widthListener);
this.props.layout.height.removeListener(this._heightListener);
}
render() {
const cardPosition = Animated.add(this.props.position, new Animated.Value(-this.props.index));
const gestureValue = Animated.multiply(cardPosition, this.props.layout.width);
const touchResponderHandlers = this._responder ? this._responder.panHandlers : null;
return (
<Animated.View
{...touchResponderHandlers}
style={[
styles.card,
{
right: gestureValue,
left: gestureValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -1],
}),
opacity: cardPosition.interpolate({
inputRange: [-1,0,1],
outputRange: [0,1,1],
}),
}
]}>
{this.props.children}
</Animated.View>
);
} }
} }
NavigationCard = NavigationContainer.create(NavigationCard); /**
* Class that provides the required information for the
* `NavigationLinearPanResponder`. This class must implement
* the interface `NavigationLinearPanResponderDelegate`.
*/
class PanResponderDelegate {
_props : Props;
constructor(props: Props) {
this._props = props;
}
getDirection(): NavigationGestureDirection {
return this._props.direction;
}
getIndex(): number {
return this._props.navigationState.index;
}
getLayout(): NavigationLayout {
return this._props.layout;
}
getPosition(): NavigationPosition {
return this._props.position;
}
onNavigate(action: {type: string}): void {
this._props.onNavigate && this._props.onNavigate(action);
}
}
/**
* Component that renders the scene as card for the <NavigationCardStack />.
*/
class NavigationCard extends React.Component {
props: Props;
state: State;
_calculateState: (t: NavigationLayout) => State;
_layoutListeners: Array<AmimatedValueSubscription>;
constructor(props: Props, context: any) {
super(props, context);
this.state = this._calculateState(props.layout);
this._layoutListeners = [];
}
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
this,
nextProps,
nextState
);
}
componentWillMount(): void {
this._calculateState = this._calculateState.bind(this);
}
componentDidMount(): void {
this._applyLayout(this.props.layout);
}
componentWillUnmount(): void {
this._layoutListeners.forEach(subscription => subscription.remove);
}
componentWillReceiveProps(nextProps: Props): void {
this._applyLayout(nextProps.layout);
}
render(): ReactElement {
const {
direction,
layout,
navigationState,
onNavigate,
position,
scene,
scenes,
} = this.props;
const {
height,
width,
} = this.state;
const index = scene.index;
const isVertical = direction === 'vertical';
const inputRange = [index - 1, index, index + 1];
const animatedStyle = {
opacity: position.interpolate({
inputRange,
outputRange: [1, 1, 0.3],
}),
transform: [
{
scale: position.interpolate({
inputRange,
outputRange: [1, 1, 0.95],
}),
},
{
translateX: isVertical ? 0 :
position.interpolate({
inputRange,
outputRange: [width, 0, -10],
}),
},
{
translateY: !isVertical ? 0 :
position.interpolate({
inputRange,
outputRange: [height, 0, -10],
}),
},
],
};
let panHandlers = null;
if (navigationState.index === index) {
const delegate = new PanResponderDelegate(this.props);
const panResponder = new NavigationLinearPanResponder(delegate);
panHandlers = panResponder.panHandlers;
}
const sceneProps = {
layout,
navigationState,
onNavigate,
position,
scene,
scenes,
};
return (
<Animated.View
{...panHandlers}
style={[styles.main, animatedStyle]}>
{this.props.renderScene(sceneProps)}
</Animated.View>
);
}
_calculateState(layout: NavigationLayout): State {
const width = layout.width.__getValue();
const height = layout.height.__getValue();
const hash = 'layout-' + width + '-' + height;
const state = {
height,
width,
hash,
};
return state;
}
_applyLayout(layout: NavigationLayout) {
this._layoutListeners.forEach(subscription => subscription.remove);
this._layoutListeners.length = 0;
const callback = this._applyLayout.bind(this, layout);
this._layoutListeners.push(
new AmimatedValueSubscription(layout.width, callback),
new AmimatedValueSubscription(layout.height, callback),
);
const nextState = this._calculateState(layout);
if (nextState.hash !== this.state.hash) {
this.setState(nextState);
}
}
}
NavigationCard.propTypes = propTypes;
NavigationCard.defaultProps = defaultProps;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { main: {
backgroundColor: '#E9E9EF', backgroundColor: '#E9E9EF',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
shadowColor: 'black', shadowColor: 'black',
shadowOpacity: 0.4,
shadowOffset: {width: 0, height: 0}, shadowOffset: {width: 0, height: 0},
shadowOpacity: 0.4,
shadowRadius: 10, shadowRadius: 10,
top: 0, top: 0,
bottom: 0,
position: 'absolute',
}, },
}); });
module.exports = NavigationCard; module.exports = NavigationContainer.create(NavigationCard);

View File

@ -29,9 +29,10 @@
const Animated = require('Animated'); const Animated = require('Animated');
const NavigationAnimatedView = require('NavigationAnimatedView'); const NavigationAnimatedView = require('NavigationAnimatedView');
const NavigationCardStackItem = require('NavigationCardStackItem'); const NavigationCard = require('NavigationCard');
const NavigationContainer = require('NavigationContainer'); const NavigationContainer = require('NavigationContainer');
const NavigationLinearPanResponder = require('NavigationLinearPanResponder'); const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
const NavigationPropTypes = require('NavigationPropTypes');
const React = require('React'); const React = require('React');
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
@ -42,32 +43,48 @@ const {PropTypes} = React;
const {Directions} = NavigationLinearPanResponder; const {Directions} = NavigationLinearPanResponder;
import type { import type {
NavigationAnimatedValue,
NavigationAnimationSetter,
NavigationParentState, NavigationParentState,
} from 'NavigationStateUtils'; NavigationSceneRenderer,
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
import type { import type {
NavigationStateRenderer, NavigationGestureDirection,
NavigationStateRendererProps, } from 'NavigationLinearPanResponder';
Position,
TimingSetter,
} from 'NavigationAnimatedView';
type Props = { type Props = {
direction: string, direction: NavigationGestureDirection,
navigationState: NavigationParentState, navigationState: NavigationParentState,
renderOverlay: ?NavigationStateRenderer, renderOverlay: ?NavigationSceneRenderer,
renderScene: NavigationStateRenderer, renderScene: NavigationSceneRenderer,
};
const propTypes = {
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
navigationState: NavigationPropTypes.navigationParentState.isRequired,
renderOverlay: PropTypes.func,
renderScene: PropTypes.func.isRequired,
};
const defaultProps = {
direction: Directions.HORIZONTAL,
renderOverlay: emptyFunction.thatReturnsNull,
}; };
/** /**
* A controlled navigation view that renders a list of cards. * A controlled navigation view that renders a list of cards.
*/ */
class NavigationCardStack extends React.Component { class NavigationCardStack extends React.Component {
_renderScene : NavigationStateRenderer; _renderScene : NavigationSceneRenderer;
_setTiming: TimingSetter; _setTiming: NavigationAnimationSetter;
constructor(props: Props, context: any) { constructor(props: Props, context: any) {
super(props, context); super(props, context);
}
componentWillMount() {
this._renderScene = this._renderScene.bind(this); this._renderScene = this._renderScene.bind(this);
this._setTiming = this._setTiming.bind(this); this._setTiming = this._setTiming.bind(this);
} }
@ -92,30 +109,21 @@ class NavigationCardStack extends React.Component {
); );
} }
_renderScene(props: NavigationStateRendererProps): ReactElement { _renderScene(props: NavigationSceneRendererProps): ReactElement {
const {
index,
layout,
navigationState,
position,
navigationParentState,
} = props;
return ( return (
<NavigationCardStackItem <NavigationCard
{...props}
direction={this.props.direction} direction={this.props.direction}
index={index} key={'card_' + props.scene.navigationState.key}
key={navigationState.key}
layout={layout}
navigationParentState={navigationParentState}
navigationState={navigationState}
position={position}
renderScene={this.props.renderScene} renderScene={this.props.renderScene}
/> />
); );
} }
_setTiming(position: Position, navigationState: NavigationParentState): void { _setTiming(
position: NavigationAnimatedValue,
navigationState: NavigationParentState,
): void {
Animated.timing( Animated.timing(
position, position,
{ {
@ -126,17 +134,8 @@ class NavigationCardStack extends React.Component {
} }
} }
NavigationCardStack.propTypes = { NavigationCardStack.propTypes = propTypes;
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]), NavigationCardStack.defaultProps = defaultProps;
navigationState: PropTypes.object.isRequired,
renderOverlay: PropTypes.func,
renderScene: PropTypes.func.isRequired,
};
NavigationCardStack.defaultProps = {
direction: Directions.HORIZONTAL,
renderOverlay: emptyFunction.thatReturnsNull,
};
const styles = StyleSheet.create({ const styles = StyleSheet.create({
animatedView: { animatedView: {

View File

@ -1,280 +0,0 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the "Software"). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* ("Your Software"). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @providesModule NavigationCardStackItem
* @flow
*/
'use strict';
const Animated = require('Animated');
const NavigationContainer = require('NavigationContainer');
const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
const React = require('React');
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
const StyleSheet = require('StyleSheet');
const View = require('View');
const {PropTypes} = React;
const {Directions} = NavigationLinearPanResponder;
import type {
NavigationParentState,
} from 'NavigationStateUtils';
import type {
Layout,
Position,
NavigationStateRenderer,
} from 'NavigationAnimatedView';
import type {
Direction,
OnNavigateHandler,
} from 'NavigationLinearPanResponder';
type AnimatedValue = Animated.Value;
type Props = {
direction: Direction,
index: number;
layout: Layout;
navigationParentState: NavigationParentState,
navigationState: NavigationParentState,
position: Position,
onNavigate: ?OnNavigateHandler,
renderScene: NavigationStateRenderer,
};
type State = {
hash: string,
height: number,
width: number,
};
class AmimatedValueSubscription {
_value: AnimatedValue;
_token: string;
constructor(value: AnimatedValue, callback: Function) {
this._value = value;
this._token = value.addListener(callback);
}
remove() {
this._value.removeListener(this._token);
}
}
/**
* Class that provides the required information for the
* `NavigationLinearPanResponder`. This class must implement
* the interface `NavigationLinearPanResponderDelegate`.
*/
class PanResponderDelegate {
_props : Props;
constructor(props: Props) {
this._props = props;
}
getDirection(): Direction {
return this._props.direction;
}
getIndex(): number {
return this._props.navigationParentState.index;
}
getLayout(): Layout {
return this._props.layout;
}
getPosition(): Position {
return this._props.position;
}
onNavigate(action: {type: string}): void {
this._props.onNavigate && this._props.onNavigate(action);
}
}
/**
* Component that renders the scene as card for the <NavigationCardStack />.
*/
class NavigationCardStackItem extends React.Component {
props: Props;
state: State;
_calculateState: (t: Layout) => State;
_layoutListeners: Array<AmimatedValueSubscription>;
constructor(props: Props, context: any) {
super(props, context);
this._calculateState = this._calculateState.bind(this);
this.state = this._calculateState(props.layout);
this._layoutListeners = [];
}
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
this,
nextProps,
nextState
);
}
componentDidMount(): void {
this._applyLayout(this.props.layout);
}
componentWillUnmount(): void {
this._layoutListeners.forEach(subscription => subscription.remove);
}
componentWillReceiveProps(nextProps: Props): void {
this._applyLayout(nextProps.layout);
}
render(): ReactElement {
const {
direction,
index,
navigationParentState,
position,
} = this.props;
const {
height,
width,
} = this.state;
const isVertical = direction === 'vertical';
const inputRange = [index - 1, index, index + 1];
const animatedStyle = {
opacity: position.interpolate({
inputRange,
outputRange: [1, 1, 0.3],
}),
transform: [
{
scale: position.interpolate({
inputRange,
outputRange: [1, 1, 0.95],
}),
},
{
translateX: isVertical ? 0 :
position.interpolate({
inputRange,
outputRange: [width, 0, -10],
}),
},
{
translateY: !isVertical ? 0 :
position.interpolate({
inputRange,
outputRange: [height, 0, -10],
}),
},
],
};
let panHandlers = null;
if (navigationParentState.index === index) {
const delegate = new PanResponderDelegate(this.props);
const panResponder = new NavigationLinearPanResponder(delegate);
panHandlers = panResponder.panHandlers;
}
return (
<Animated.View
{...panHandlers}
style={[styles.main, animatedStyle]}>
{this.props.renderScene(this.props)}
</Animated.View>
);
}
_calculateState(layout: Layout): State {
const width = layout.width.__getValue();
const height = layout.height.__getValue();
const hash = 'layout-' + width + '-' + height;
const state = {
height,
width,
hash,
};
return state;
}
_applyLayout(layout: Layout) {
this._layoutListeners.forEach(subscription => subscription.remove);
this._layoutListeners.length = 0;
const callback = this._applyLayout.bind(this, layout);
this._layoutListeners.push(
new AmimatedValueSubscription(layout.width, callback),
new AmimatedValueSubscription(layout.height, callback),
);
const nextState = this._calculateState(layout);
if (nextState.hash !== this.state.hash) {
this.setState(nextState);
}
}
}
NavigationCardStackItem.propTypes = {
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
index: PropTypes.number.isRequired,
layout: PropTypes.object.isRequired,
navigationState: PropTypes.object.isRequired,
navigationParentState: PropTypes.object.isRequired,
position: PropTypes.object.isRequired,
renderScene: PropTypes.func.isRequired,
};
NavigationCardStackItem.defaultProps = {
direction: Directions.HORIZONTAL,
};
const styles = StyleSheet.create({
main: {
backgroundColor: '#E9E9EF',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
shadowColor: 'black',
shadowOffset: {width: 0, height: 0},
shadowOpacity: 0.4,
shadowRadius: 10,
top: 0,
},
});
module.exports = NavigationContainer.create(NavigationCardStackItem);

View File

@ -30,6 +30,7 @@
const Animated = require('Animated'); const Animated = require('Animated');
const Image = require('Image'); const Image = require('Image');
const NavigationContainer = require('NavigationContainer'); const NavigationContainer = require('NavigationContainer');
const NavigationPropTypes = require('NavigationPropTypes');
const NavigationRootContainer = require('NavigationRootContainer'); const NavigationRootContainer = require('NavigationRootContainer');
const React = require('react-native'); const React = require('react-native');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
@ -37,25 +38,32 @@ const Text = require('Text');
const TouchableOpacity = require('TouchableOpacity'); const TouchableOpacity = require('TouchableOpacity');
const View = require('View'); const View = require('View');
import type { import type {
NavigationState, NavigationState,
NavigationParentState NavigationSceneRendererProps,
} from 'NavigationStateUtils'; } from 'NavigationTypeDefinition';
type Props = { type Props = NavigationSceneRendererProps & {
navigationState: NavigationParentState,
onNavigate: Function,
position: Animated.Value,
getTitle: (navState: NavigationState) => string, getTitle: (navState: NavigationState) => string,
}; };
const {PropTypes} = React;
const NavigationHeaderPropTypes = {
...NavigationPropTypes.SceneRenderer,
getTitle: PropTypes.func.isRequired,
};
class NavigationHeader extends React.Component { class NavigationHeader extends React.Component {
_handleBackPress: Function; _handleBackPress: Function;
props: Props; props: Props;
componentWillMount() {
componentWillMount(): void {
this._handleBackPress = this._handleBackPress.bind(this); this._handleBackPress = this._handleBackPress.bind(this);
} }
render() {
render(): ReactElement {
var state = this.props.navigationState; var state = this.props.navigationState;
return ( return (
<Animated.View <Animated.View
@ -67,7 +75,8 @@ class NavigationHeader extends React.Component {
</Animated.View> </Animated.View>
); );
} }
_renderBackButton() {
_renderBackButton(): ?ReactElement {
if (this.props.navigationState.index === 0) { if (this.props.navigationState.index === 0) {
return null; return null;
} }
@ -77,7 +86,8 @@ class NavigationHeader extends React.Component {
</TouchableOpacity> </TouchableOpacity>
); );
} }
_renderTitle(childState, index) {
_renderTitle(childState: NavigationState, index:number): ?ReactElement {
return ( return (
<Animated.Text <Animated.Text
key={childState.key} key={childState.key}
@ -102,11 +112,14 @@ class NavigationHeader extends React.Component {
</Animated.Text> </Animated.Text>
); );
} }
_handleBackPress() {
_handleBackPress(): void {
this.props.onNavigate(NavigationRootContainer.getBackAction()); this.props.onNavigate(NavigationRootContainer.getBackAction());
} }
} }
NavigationHeader.propTypes = NavigationHeaderPropTypes;
NavigationHeader = NavigationContainer.create(NavigationHeader); NavigationHeader = NavigationContainer.create(NavigationHeader);
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@ -11,8 +11,7 @@ const invariant = require('fbjs/lib/invariant');
import type { import type {
NavigationState, NavigationState,
NavigationParentState, NavigationParentState,
} from 'NavigationStateUtils'; } from 'NavigationTypeDefinition';
type IterationCallback = (route: any, index: number, key: string) => void; type IterationCallback = (route: any, index: number, key: string) => void;

View File

@ -11,23 +11,20 @@
*/ */
'use strict'; 'use strict';
var Animated = require('Animated'); const Animated = require('Animated');
var Map = require('Map'); const NavigationContainer = require('NavigationContainer');
var NavigationStateUtils = require('NavigationStateUtils'); const NavigationPropTypes = require('NavigationPropTypes');
var NavigationContainer = require('NavigationContainer'); const NavigationStateUtils = require('NavigationStateUtils');
var React = require('React'); const React = require('react-native');
var View = require('View'); const View = require('View');
import type { import type {
NavigationState, NavigationAnimatedValue,
NavigationAnimationSetter,
NavigationParentState, NavigationParentState,
} from 'NavigationStateUtils'; NavigationScene,
NavigationSceneRenderer,
type NavigationScene = { } from 'NavigationTypeDefinition';
index: number,
state: NavigationState,
isStale: boolean,
};
/** /**
* Helper function to compare route keys (e.g. "9", "11"). * Helper function to compare route keys (e.g. "9", "11").
@ -48,7 +45,7 @@ function compareKey(one: string, two: string): number {
*/ */
function compareScenes( function compareScenes(
one: NavigationScene, one: NavigationScene,
two: NavigationScene two: NavigationScene,
): number { ): number {
if (one.index > two.index) { if (one.index > two.index) {
return 1; return 1;
@ -58,64 +55,62 @@ function compareScenes(
} }
return compareKey( return compareKey(
one.state.key, one.navigationState.key,
two.state.key two.navigationState.key,
); );
} }
type Layout = {
initWidth: number,
initHeight: number,
width: Animated.Value;
height: Animated.Value;
};
type Position = Animated.Value;
/**
* Definition of the props object that is passed to the functions
* that render the overlay and the scene.
*/
type NavigationStateRendererProps = {
// The state of the child view.
navigationState: NavigationState,
// The index of the child view.
index: number,
// The "progressive index" of the containing navigation state.
position: Position,
// The layout of the the containing navigation view.
layout: Layout,
// The state of the the containing navigation view.
navigationParentState: NavigationParentState,
onNavigate: (action: any) => void,
};
type NavigationStateRenderer = (
props: NavigationStateRendererProps,
) => ReactElement;
type TimingSetter = (
position: Animated.Value,
newState: NavigationParentState,
lastState: NavigationParentState,
) => void;
type Props = { type Props = {
navigationState: NavigationParentState, navigationState: NavigationParentState,
onNavigate: (action: any) => void, onNavigate: (action: any) => void,
renderScene: NavigationStateRenderer, renderScene: NavigationSceneRenderer,
renderOverlay: ?NavigationStateRenderer, renderOverlay: ?NavigationSceneRenderer,
style: any, style: any,
setTiming: ?TimingSetter, setTiming: NavigationAnimationSetter,
}; };
class NavigationAnimatedView extends React.Component { type State = {
position: NavigationAnimatedValue,
scenes: Array<NavigationScene>,
};
const {PropTypes} = React;
const propTypes = {
navigationState: NavigationPropTypes.navigationState.isRequired,
onNavigate: PropTypes.func.isRequired,
renderScene: PropTypes.func.isRequired,
renderOverlay: PropTypes.func,
setTiming: PropTypes.func,
};
const defaultProps = {
setTiming: (
position: NavigationAnimatedValue,
navigationState: NavigationParentState,
) => {
Animated.spring(
position,
{
bounciness: 0,
toValue: navigationState.index,
}
).start();
},
};
class NavigationAnimatedView
extends React.Component<any, Props, State> {
_animatedHeight: Animated.Value; _animatedHeight: Animated.Value;
_animatedWidth: Animated.Value; _animatedWidth: Animated.Value;
_lastHeight: number; _lastHeight: number;
_lastWidth: number; _lastWidth: number;
_postionListener: any;
props: Props; props: Props;
state: State;
constructor(props) { constructor(props) {
super(props); super(props);
this._lastWidth = 0; this._lastWidth = 0;
@ -125,7 +120,7 @@ class NavigationAnimatedView extends React.Component {
this.state = { this.state = {
position: new Animated.Value(this.props.navigationState.index), position: new Animated.Value(this.props.navigationState.index),
scenes: new Map(), scenes: [],
}; };
} }
componentWillMount() { componentWillMount() {
@ -134,7 +129,7 @@ class NavigationAnimatedView extends React.Component {
}); });
} }
componentDidMount() { componentDidMount() {
this.postionListener = this.state.position.addListener(this._onProgressChange.bind(this)); this._postionListener = this.state.position.addListener(this._onProgressChange.bind(this));
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.navigationState !== this.props.navigationState) { if (nextProps.navigationState !== this.props.navigationState) {
@ -150,8 +145,8 @@ class NavigationAnimatedView extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
if (this.postionListener) { if (this.postionListener) {
this.state.position.removeListener(this.postionListener); this.state.position.removeListener(this._postionListener);
this.postionListener = null; this._postionListener = null;
} }
} }
_onProgressChange(data: Object): void { _onProgressChange(data: Object): void {
@ -174,8 +169,8 @@ class NavigationAnimatedView extends React.Component {
let nextScenes = nextState.children.map((child, index) => { let nextScenes = nextState.children.map((child, index) => {
return { return {
index, index,
state: child,
isStale: false, isStale: false,
navigationState: child,
}; };
}); });
@ -184,8 +179,8 @@ class NavigationAnimatedView extends React.Component {
if (!NavigationStateUtils.get(nextState, child.key) && index !== nextState.index) { if (!NavigationStateUtils.get(nextState, child.key) && index !== nextState.index) {
nextScenes.push({ nextScenes.push({
index, index,
state: child,
isStale: true, isStale: true,
navigationState: child,
}); });
} }
}); });
@ -221,49 +216,57 @@ class NavigationAnimatedView extends React.Component {
initHeight: this._lastHeight, initHeight: this._lastHeight,
}; };
} }
_renderScene(scene: NavigationScene) { _renderScene(scene: NavigationScene) {
return this.props.renderScene({ const {
index: scene.index, navigationState,
onNavigate,
renderScene,
} = this.props;
const {
position,
scenes,
} = this.state;
return renderScene({
layout: this._getLayout(), layout: this._getLayout(),
navigationParentState: this.props.navigationState, navigationState,
navigationState: scene.state, onNavigate,
onNavigate: this.props.onNavigate, position,
position: this.state.position, scene,
scenes,
}); });
} }
_renderOverlay() { _renderOverlay() {
const { if (this.props.renderOverlay) {
onNavigate, const {
renderOverlay, navigationState,
navigationState, onNavigate,
} = this.props; renderOverlay,
if (renderOverlay) { } = this.props;
const {
position,
scenes,
} = this.state;
return renderOverlay({ return renderOverlay({
index: navigationState.index,
layout: this._getLayout(), layout: this._getLayout(),
navigationParentState: navigationState, navigationState,
navigationState: navigationState.children[navigationState.index], onNavigate,
onNavigate: onNavigate, position,
position: this.state.position, scene: scenes[navigationState.index],
scenes,
}); });
} }
return null; return null;
} }
} }
function setDefaultTiming(position, navigationState) { NavigationAnimatedView.propTypes = propTypes;
Animated.spring( NavigationAnimatedView.defaultProps = defaultProps;
position,
{
bounciness: 0,
toValue: navigationState.index,
}
).start();
}
NavigationAnimatedView.defaultProps = {
setTiming: setDefaultTiming,
};
NavigationAnimatedView = NavigationContainer.create(NavigationAnimatedView); NavigationAnimatedView = NavigationContainer.create(NavigationAnimatedView);

View File

@ -14,7 +14,9 @@
var React = require('React'); var React = require('React');
var NavigationRootContainer = require('NavigationRootContainer'); var NavigationRootContainer = require('NavigationRootContainer');
function createNavigationContainer(Component: React.Component): React.Component { function createNavigationContainer(
Component: ReactClass<any, any, any>,
): ReactClass {
class NavigationComponent extends React.Component { class NavigationComponent extends React.Component {
render() { render() {
return ( return (

View File

@ -12,6 +12,12 @@ const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder')
const clamp = require('clamp'); const clamp = require('clamp');
import {
NavigationActionCaller,
NavigationLayout,
NavigationPosition,
} from 'NavigationTypeDefinition';
/** /**
* The duration of the card animation in milliseconds. * The duration of the card animation in milliseconds.
*/ */
@ -42,6 +48,8 @@ const Directions = {
'VERTICAL': 'vertical', 'VERTICAL': 'vertical',
}; };
export type NavigationGestureDirection = $Enum<typeof Directions>;
/** /**
* Primitive gesture actions. * Primitive gesture actions.
*/ */
@ -52,25 +60,16 @@ const Actions = {
BACK: {type: 'back'}, BACK: {type: 'back'},
}; };
import type {
Layout,
Position,
} from 'NavigationAnimatedView';
export type OnNavigateHandler = (action: {type: string}) => void;
export type Direction = $Enum<typeof Directions>;
/** /**
* The type interface of the object that provides the information required by * The type interface of the object that provides the information required by
* NavigationLinearPanResponder. * NavigationLinearPanResponder.
*/ */
export type NavigationLinearPanResponderDelegate = { export type NavigationLinearPanResponderDelegate = {
getDirection: () => Direction; getDirection: () => NavigationGestureDirection;
getIndex: () => number, getIndex: () => number,
getLayout: () => Layout, getLayout: () => NavigationLayout,
getPosition: () => Position, getPosition: () => NavigationPosition,
onNavigate: OnNavigateHandler, onNavigate: NavigationActionCaller,
}; };
/** /**

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule NavigationPropTypes
* @flow
*/
'use strict';
/**
* React component PropTypes Definitions. Consider using this as a supplementary
* measure with `NavigationTypeDefinition`. This helps to capture the propType
* error at run-time, where as `NavigationTypeDefinition` capture the flow
* type check errors at build time.
*/
const Animated = require('Animated');
const React = require('react-native');
const {PropTypes} = React;
/* NavigationAction */
const action = PropTypes.shape({
type: PropTypes.string.isRequired,
});
/* NavigationAnimatedValue */
const animatedValue = PropTypes.instanceOf(Animated.Value);
/* NavigationState */
const navigationState = PropTypes.shape({
key: PropTypes.string.isRequired,
});
/* NavigationParentState */
const navigationParentState = PropTypes.shape({
index: PropTypes.number.isRequired,
key: PropTypes.string.isRequired,
children: PropTypes.arrayOf(navigationState),
});
/* NavigationLayout */
const layout = PropTypes.shape({
height: animatedValue,
initHeight: PropTypes.number.isRequired,
initWidth: PropTypes.number.isRequired,
width: animatedValue,
});
/* NavigationScene */
const scene = PropTypes.shape({
index: PropTypes.number.isRequired,
isStale: PropTypes.bool.isRequired,
navigationState,
});
/* NavigationSceneRendererProps */
const SceneRenderer = {
layout: layout.isRequired,
navigationState: navigationParentState.isRequired,
onNavigate: PropTypes.func.isRequired,
position: animatedValue.isRequired,
scene: scene.isRequired,
scenes: PropTypes.arrayOf(scene).isRequired,
};
module.exports = {
SceneRenderer,
action,
navigationParentState,
navigationState,
};

View File

@ -13,23 +13,18 @@
const AsyncStorage = require('AsyncStorage'); const AsyncStorage = require('AsyncStorage');
const Linking = require('Linking'); const Linking = require('Linking');
const React = require('React');
const BackAndroid = require('BackAndroid');
const Platform = require('Platform'); const Platform = require('Platform');
const React = require('React');
const NavigationPropTypes = require('NavigationPropTypes');
import type { import type {
NavigationAction, NavigationAction,
NavigationState, NavigationReducer,
NavigationReducer NavigationRenderer,
} from 'NavigationStateUtils'; } from 'NavigationTypeDefinition';
export type NavigationRenderer = (
navigationState: NavigationState,
onNavigate: Function
) => ReactElement;
export type BackAction = { export type BackAction = {
type: 'BackAction'; type: 'BackAction',
}; };
function getBackAction(): BackAction { function getBackAction(): BackAction {
@ -37,40 +32,60 @@ function getBackAction(): BackAction {
} }
type Props = { type Props = {
/*
* Set up the rendering of the app for a given navigation state
*/
renderNavigation: NavigationRenderer;
/*
* A function that will output the latest navigation state as a function of
* the (optional) previous state, and an action
*/
reducer: NavigationReducer;
/*
* Provide this key, and the container will store the navigation state in
* AsyncStorage through refreshes, with the provided key
*/
persistenceKey: ?string;
/* /*
* The default action to be passed into the reducer when getting the first * The default action to be passed into the reducer when getting the first
* state. Defaults to {type: 'RootContainerInitialAction'} * state. Defaults to {type: 'RootContainerInitialAction'}
*/ */
initialAction: NavigationAction; initialAction: NavigationAction,
/* /*
* Provide linkingActionMap to instruct the container to subscribe to linking * Provide linkingActionMap to instruct the container to subscribe to linking
* events, and use this mapper to convert URIs into actions that your app can * events, and use this mapper to convert URIs into actions that your app can
* handle * handle
*/ */
linkingActionMap: (uri: string) => NavigationAction; linkingActionMap: ?((uri: string) => NavigationAction),
/*
* Provide this key, and the container will store the navigation state in
* AsyncStorage through refreshes, with the provided key
*/
persistenceKey: ?string,
/*
* A function that will output the latest navigation state as a function of
* the (optional) previous state, and an action
*/
reducer: NavigationReducer,
/*
* Set up the rendering of the app for a given navigation state
*/
renderNavigation: NavigationRenderer,
};
const {PropTypes} = React;
const propTypes = {
initialAction: NavigationPropTypes.action.isRequired,
linkingActionMap: PropTypes.func,
persistenceKey: PropTypes.string,
reducer: PropTypes.func.isRequired,
renderNavigation: PropTypes.func.isRequired,
};
const defaultProps = {
initialAction: {
type: 'RootContainerInitialAction',
},
}; };
class NavigationRootContainer extends React.Component { class NavigationRootContainer extends React.Component {
_handleOpenURLEvent: Function; _handleOpenURLEvent: Function;
props: Props; props: Props;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.handleNavigation = this.handleNavigation.bind(this); this.handleNavigation = this.handleNavigation.bind(this);
@ -81,10 +96,11 @@ class NavigationRootContainer extends React.Component {
} }
this.state = { navState }; this.state = { navState };
} }
componentDidMount() { componentDidMount() {
if (this.props.LinkingActionMap) { if (this.props.LinkingActionMap) {
Linking.getInitialURL().then(this._handleOpenURL.bind(this)); Linking.getInitialURL().then(this._handleOpenURL.bind(this));
Platform.OS === 'ios' && Linking.addEventListener('url', this._handleOpenURLEvent); Platform.OS === 'ios' && Linking.addEventListener('url', this._handleOpenURLEvent);
} }
if (this.props.persistenceKey) { if (this.props.persistenceKey) {
AsyncStorage.getItem(this.props.persistenceKey, (err, storedString) => { AsyncStorage.getItem(this.props.persistenceKey, (err, storedString) => {
@ -100,12 +116,15 @@ class NavigationRootContainer extends React.Component {
}); });
} }
} }
componentWillUnmount() { componentWillUnmount() {
Platform.OS === 'ios' && Linking.removeEventListener('url', this._handleOpenURLEvent); Platform.OS === 'ios' && Linking.removeEventListener('url', this._handleOpenURLEvent);
} }
_handleOpenURLEvent(event: {url: string}) { _handleOpenURLEvent(event: {url: string}) {
this._handleOpenURL(event.url); this._handleOpenURL(event.url);
} }
_handleOpenURL(url: ?string) { _handleOpenURL(url: ?string) {
if (!this.props.LinkingActionMap) { if (!this.props.LinkingActionMap) {
return; return;
@ -115,11 +134,13 @@ class NavigationRootContainer extends React.Component {
this.handleNavigation(action); this.handleNavigation(action);
} }
} }
getChildContext(): Object { getChildContext(): Object {
return { return {
onNavigate: this.handleNavigation, onNavigate: this.handleNavigation,
}; };
} }
handleNavigation(action: Object): boolean { handleNavigation(action: Object): boolean {
const navState = this.props.reducer(this.state.navState, action); const navState = this.props.reducer(this.state.navState, action);
if (navState === this.state.navState) { if (navState === this.state.navState) {
@ -128,11 +149,14 @@ class NavigationRootContainer extends React.Component {
this.setState({ this.setState({
navState, navState,
}); });
if (this.props.persistenceKey) { if (this.props.persistenceKey) {
AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState)); AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
} }
return true; return true;
} }
render(): ReactElement { render(): ReactElement {
const navigation = this.props.renderNavigation( const navigation = this.props.renderNavigation(
this.state.navState, this.state.navState,
@ -143,15 +167,11 @@ class NavigationRootContainer extends React.Component {
} }
NavigationRootContainer.childContextTypes = { NavigationRootContainer.childContextTypes = {
onNavigate: React.PropTypes.func, onNavigate: PropTypes.func,
};
NavigationRootContainer.defaultProps = {
initialAction: {
type: 'RootContainerInitialAction',
},
}; };
NavigationRootContainer.propTypes = propTypes;
NavigationRootContainer.defaultProps = defaultProps;
NavigationRootContainer.getBackAction = getBackAction; NavigationRootContainer.getBackAction = getBackAction;
module.exports = NavigationRootContainer; module.exports = NavigationRootContainer;

View File

@ -13,24 +13,10 @@
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
export type NavigationState = { import type {
key: string; NavigationState,
}; NavigationParentState,
} from 'NavigationTypeDefinition';
export type NavigationParentState = {
key: string;
index: number;
children: Array<NavigationState>;
};
export type NavigationAction = {
type: string;
};
export type NavigationReducer = (
state: ?NavigationState,
action: ?NavigationAction
) => NavigationState;
function getParent(state: NavigationState): ?NavigationParentState { function getParent(state: NavigationState): ?NavigationParentState {
if ( if (

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule NavigationTypeDefinition
* @flow
*/
'use strict';
const Animated = require('Animated');
// Object Instances
export type NavigationAnimatedValue = Animated.Value;
// Value & Structs.
export type NavigationGestureDirection = 'horizontal' | 'vertical';
export type NavigationState = {
key: string,
};
export type NavigationParentState = {
index: number,
key: string,
children: Array<NavigationState>,
};
export type NavigationAction = any;
export type NavigationLayout = {
height: NavigationAnimatedValue,
initHeight: number,
initWidth: number,
width: NavigationAnimatedValue,
};
export type NavigationPosition = NavigationAnimatedValue;
export type NavigationScene = {
index: number,
isStale: boolean,
navigationState: NavigationState,
};
export type NavigationSceneRendererProps = {
// The layout of the containing view of the scenes.
layout: NavigationLayout,
// The navigation state of the containing view.
navigationState: NavigationParentState,
// Callback to navigation with an action.
onNavigate: NavigationActionCaller,
// The progressive index of the containing view's navigation state.
position: NavigationPosition,
// The scene to render.
scene: NavigationScene,
// All the scenes of the containing view's.
scenes: Array<NavigationScene>,
};
// Functions.
export type NavigationActionCaller = Function;
export type NavigationAnimationSetter = (
position: NavigationAnimatedValue,
newState: NavigationParentState,
lastState: NavigationParentState,
) => void;
export type NavigationRenderer = (
navigationState: NavigationState,
onNavigate: NavigationActionCaller,
) => ReactElement;
export type NavigationReducer = (
state: ?NavigationState,
action: ?NavigationAction,
) => NavigationState;
export type NavigationSceneRenderer = (
props: NavigationSceneRendererProps,
) => ?ReactElement;

View File

@ -20,9 +20,12 @@
import type { import type {
NavigationState, NavigationState,
NavigationReducer NavigationReducer
} from 'NavigationStateUtils'; } from 'NavigationTypeDefinition';
function NavigationFindReducer(reducers: Array<NavigationReducer>, defaultState: NavigationState): NavigationReducer { function NavigationFindReducer(
reducers: Array<NavigationReducer>,
defaultState: NavigationState,
): NavigationReducer {
return function(lastState: ?NavigationState, action: ?any): NavigationState { return function(lastState: ?NavigationState, action: ?any): NavigationState {
for (let i = 0; i < reducers.length; i++) { for (let i = 0; i < reducers.length; i++) {
let reducer = reducers[i]; let reducer = reducers[i];

View File

@ -17,7 +17,7 @@ import type {
NavigationState, NavigationState,
NavigationParentState, NavigationParentState,
NavigationReducer, NavigationReducer,
} from 'NavigationStateUtils'; } from 'NavigationTypeDefinition';
import type { import type {
BackAction, BackAction,

View File

@ -17,8 +17,7 @@ const NavigationStateUtils = require('NavigationStateUtils');
import type { import type {
NavigationReducer, NavigationReducer,
NavigationState, NavigationState,
NavigationParentState } from 'NavigationTypeDefinition';
} from 'NavigationStateUtils';
const ActionTypes = { const ActionTypes = {
JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo', JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo',

View File

@ -11,7 +11,6 @@
'use strict'; 'use strict';
jest jest
.dontMock('NavigationRootContainer')
.dontMock('NavigationStackReducer') .dontMock('NavigationStackReducer')
.dontMock('NavigationStateUtils'); .dontMock('NavigationStateUtils');

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
const NavigationRootContainer = {
getBackAction: () => {
return { type: 'BackAction' };
}
};
module.exports = NavigationRootContainer;