Remove Deprecated NavigationExperimental
Summary: Now that there are a number of good navigation solutions provided by the community, we are ready to remove NavigationExperimental from the RN core. The latest navigation doc explains the available options pretty well: http://facebook.github.io/react-native/docs/navigation.html . We should also add a mention to Airbnb's new native-navigation. For anybody who continues to rely on it, it is recommended to migrate to React Navigation, which will be maintained over the long-term. For those who cannot migrate yet, it is possible to copy this code into your app. Closes https://github.com/facebook/react-native/pull/13066 Differential Revision: D4757539 Pulled By: ericvicenti fbshipit-source-id: 949d9b33f188584fb095155fa67d3ce24beba29f
|
@ -1,48 +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 NavigationAnimatedValueSubscription
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type {
|
||||
NavigationAnimatedValue
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
class NavigationAnimatedValueSubscription {
|
||||
_value: NavigationAnimatedValue;
|
||||
_token: string;
|
||||
|
||||
constructor(value: NavigationAnimatedValue, callback: Function) {
|
||||
this._value = value;
|
||||
this._token = value.addListener(callback);
|
||||
}
|
||||
|
||||
remove(): void {
|
||||
this._value.removeListener(this._token);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NavigationAnimatedValueSubscription;
|
|
@ -1,132 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationCard
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
||||
const NavigationPagerPanResponder = require('NavigationPagerPanResponder');
|
||||
const NavigationPagerStyleInterpolator = require('NavigationPagerStyleInterpolator');
|
||||
const NavigationPointerEventsContainer = require('NavigationPointerEventsContainer');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
|
||||
import type {
|
||||
NavigationPanPanHandlers,
|
||||
NavigationSceneRenderer,
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type Props = NavigationSceneRendererProps & {
|
||||
onComponentRef: (ref: any) => void,
|
||||
onNavigateBack: ?Function,
|
||||
panHandlers: ?NavigationPanPanHandlers,
|
||||
pointerEvents: string,
|
||||
renderScene: NavigationSceneRenderer,
|
||||
style: any,
|
||||
};
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
/**
|
||||
* Component that renders the scene as card for the <NavigationCardStack />.
|
||||
*/
|
||||
class NavigationCard extends React.Component<any, Props, any> {
|
||||
props: Props;
|
||||
|
||||
static propTypes = {
|
||||
...NavigationPropTypes.SceneRendererProps,
|
||||
onComponentRef: PropTypes.func.isRequired,
|
||||
onNavigateBack: PropTypes.func,
|
||||
panHandlers: NavigationPropTypes.panHandlers,
|
||||
pointerEvents: PropTypes.string.isRequired,
|
||||
renderScene: PropTypes.func.isRequired,
|
||||
style: PropTypes.any,
|
||||
};
|
||||
|
||||
render(): React.Element<any> {
|
||||
const {
|
||||
panHandlers,
|
||||
pointerEvents,
|
||||
renderScene,
|
||||
style,
|
||||
...props /* NavigationSceneRendererProps */
|
||||
} = this.props;
|
||||
|
||||
const viewStyle = style === undefined ?
|
||||
NavigationCardStackStyleInterpolator.forHorizontal(props) :
|
||||
style;
|
||||
|
||||
const viewPanHandlers = panHandlers === undefined ?
|
||||
NavigationCardStackPanResponder.forHorizontal({
|
||||
...props,
|
||||
onNavigateBack: this.props.onNavigateBack,
|
||||
}) :
|
||||
panHandlers;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
{...viewPanHandlers}
|
||||
pointerEvents={pointerEvents}
|
||||
ref={this.props.onComponentRef}
|
||||
style={[styles.main, viewStyle]}>
|
||||
{renderScene(props)}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
NavigationCard = NavigationPointerEventsContainer.create(NavigationCard);
|
||||
|
||||
NavigationCard.CardStackPanResponder = NavigationCardStackPanResponder;
|
||||
NavigationCard.CardStackStyleInterpolator = NavigationCardStackStyleInterpolator;
|
||||
NavigationCard.PagerPanResponder = NavigationPagerPanResponder;
|
||||
NavigationCard.PagerStyleInterpolator = NavigationPagerStyleInterpolator;
|
||||
|
||||
module.exports = NavigationCard;
|
|
@ -1,329 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationCardStack
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
|
||||
const NavigationCard = require('NavigationCard');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const NavigationTransitioner = require('NavigationTransitioner');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const {PropTypes} = React;
|
||||
const {Directions} = NavigationCardStackPanResponder;
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationSceneRenderer,
|
||||
NavigationSceneRendererProps,
|
||||
NavigationTransitionProps,
|
||||
NavigationStyleInterpolator,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
import type {
|
||||
NavigationGestureDirection,
|
||||
} from 'NavigationCardStackPanResponder';
|
||||
|
||||
type Props = {
|
||||
direction: NavigationGestureDirection,
|
||||
navigationState: NavigationState,
|
||||
onNavigateBack?: Function,
|
||||
renderHeader: ?NavigationSceneRenderer,
|
||||
renderScene: NavigationSceneRenderer,
|
||||
cardStyle?: any,
|
||||
style: any,
|
||||
gestureResponseDistance?: ?number,
|
||||
enableGestures: ?boolean,
|
||||
cardStyleInterpolator?: ?NavigationStyleInterpolator,
|
||||
scenesStyle?: any,
|
||||
};
|
||||
|
||||
type DefaultProps = {
|
||||
direction: NavigationGestureDirection,
|
||||
enableGestures: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* A controlled navigation view that renders a stack of cards.
|
||||
*
|
||||
* ```html
|
||||
* +------------+
|
||||
* +-| Header |
|
||||
* +-+ |------------|
|
||||
* | | | |
|
||||
* | | | Focused |
|
||||
* | | | Card |
|
||||
* | | | |
|
||||
* +-+ | |
|
||||
* +-+ |
|
||||
* +------------+
|
||||
* ```
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```js
|
||||
*
|
||||
* class App extends React.Component {
|
||||
* constructor(props, context) {
|
||||
* this.state = {
|
||||
* navigation: {
|
||||
* index: 0,
|
||||
* routes: [
|
||||
* {key: 'page 1'},
|
||||
* },
|
||||
* },
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <NavigationCardStack
|
||||
* navigationState={this.state.navigation}
|
||||
* renderScene={this._renderScene}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* _renderScene: (props) => {
|
||||
* return (
|
||||
* <View>
|
||||
* <Text>{props.scene.route.key}</Text>
|
||||
* </View>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class NavigationCardStack extends React.Component<DefaultProps, Props, void> {
|
||||
_render : NavigationSceneRenderer;
|
||||
_renderScene : NavigationSceneRenderer;
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Custom style applied to the card.
|
||||
*/
|
||||
cardStyle: PropTypes.any,
|
||||
|
||||
/**
|
||||
* Direction of the cards movement. Value could be `horizontal` or
|
||||
* `vertical`. Default value is `horizontal`.
|
||||
*/
|
||||
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
|
||||
|
||||
/**
|
||||
* The distance from the edge of the card which gesture response can start
|
||||
* for. Default value is `30`.
|
||||
*/
|
||||
gestureResponseDistance: PropTypes.number,
|
||||
|
||||
/**
|
||||
* An interpolator function that is passed an object parameter of type
|
||||
* NavigationSceneRendererProps and should return a style object to apply to
|
||||
* the transitioning navigation card.
|
||||
*
|
||||
* Default interpolator transitions translateX, scale, and opacity.
|
||||
*/
|
||||
cardStyleInterpolator: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Enable gestures. Default value is true.
|
||||
*
|
||||
* When disabled, transition animations will be handled natively, which
|
||||
* improves performance of the animation. In future iterations, gestures
|
||||
* will also work with native-driven animation.
|
||||
*/
|
||||
enableGestures: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The controlled navigation state. Typically, the navigation state
|
||||
* look like this:
|
||||
*
|
||||
* ```js
|
||||
* const navigationState = {
|
||||
* index: 0, // the index of the selected route.
|
||||
* routes: [ // A list of routes.
|
||||
* {key: 'page 1'}, // The 1st route.
|
||||
* {key: 'page 2'}, // The second route.
|
||||
* ],
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
navigationState: NavigationPropTypes.navigationState.isRequired,
|
||||
|
||||
/**
|
||||
* Callback that is called when the "back" action is performed.
|
||||
* This happens when the back button is pressed or the back gesture is
|
||||
* performed.
|
||||
*/
|
||||
onNavigateBack: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Function that renders the header.
|
||||
*/
|
||||
renderHeader: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Function that renders the a scene for a route.
|
||||
*/
|
||||
renderScene: PropTypes.func.isRequired,
|
||||
|
||||
/**
|
||||
* Custom style applied to the cards stack.
|
||||
*/
|
||||
style: View.propTypes.style,
|
||||
|
||||
/**
|
||||
* Custom style applied to the scenes stack.
|
||||
*/
|
||||
scenesStyle: View.propTypes.style,
|
||||
};
|
||||
|
||||
static defaultProps: DefaultProps = {
|
||||
direction: Directions.HORIZONTAL,
|
||||
enableGestures: true,
|
||||
};
|
||||
|
||||
constructor(props: Props, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
this._render = this._render.bind(this);
|
||||
this._renderScene = this._renderScene.bind(this);
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
return (
|
||||
<NavigationTransitioner
|
||||
configureTransition={this._configureTransition}
|
||||
navigationState={this.props.navigationState}
|
||||
render={this._render}
|
||||
style={this.props.style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_configureTransition = () => {
|
||||
const isVertical = this.props.direction === 'vertical';
|
||||
const animationConfig = {};
|
||||
if (
|
||||
!!NativeAnimatedModule
|
||||
|
||||
// Gestures do not work with the current iteration of native animation
|
||||
// driving. When gestures are disabled, we can drive natively.
|
||||
&& !this.props.enableGestures
|
||||
|
||||
// Native animation support also depends on the transforms used:
|
||||
&& NavigationCardStackStyleInterpolator.canUseNativeDriver(isVertical)
|
||||
) {
|
||||
animationConfig.useNativeDriver = true;
|
||||
}
|
||||
return animationConfig;
|
||||
}
|
||||
|
||||
_render(props: NavigationTransitionProps): React.Element<any> {
|
||||
const {
|
||||
renderHeader,
|
||||
} = this.props;
|
||||
|
||||
const header = renderHeader ? <View>{renderHeader(props)}</View> : null;
|
||||
|
||||
const scenes = props.scenes.map(
|
||||
scene => this._renderScene({
|
||||
...props,
|
||||
scene,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View
|
||||
style={[styles.scenes, this.props.scenesStyle]}>
|
||||
{scenes}
|
||||
</View>
|
||||
{header}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderScene(props: NavigationSceneRendererProps): React.Element<any> {
|
||||
const isVertical = this.props.direction === 'vertical';
|
||||
|
||||
const interpolator = this.props.cardStyleInterpolator || (isVertical ?
|
||||
NavigationCardStackStyleInterpolator.forVertical :
|
||||
NavigationCardStackStyleInterpolator.forHorizontal);
|
||||
|
||||
const style = interpolator(props);
|
||||
|
||||
let panHandlers = null;
|
||||
|
||||
if (this.props.enableGestures) {
|
||||
const panHandlersProps = {
|
||||
...props,
|
||||
onNavigateBack: this.props.onNavigateBack,
|
||||
gestureResponseDistance: this.props.gestureResponseDistance,
|
||||
};
|
||||
panHandlers = isVertical ?
|
||||
NavigationCardStackPanResponder.forVertical(panHandlersProps) :
|
||||
NavigationCardStackPanResponder.forHorizontal(panHandlersProps);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationCard
|
||||
{...props}
|
||||
key={'card_' + props.scene.key}
|
||||
panHandlers={panHandlers}
|
||||
renderScene={this.props.renderScene}
|
||||
style={[style, this.props.cardStyle]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
// Header is physically rendered after scenes so that Header won't be
|
||||
// covered by the shadows of the scenes.
|
||||
// That said, we'd have use `flexDirection: 'column-reverse'` to move
|
||||
// Header above the scenes.
|
||||
flexDirection: 'column-reverse',
|
||||
},
|
||||
scenes: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationCardStack;
|
|
@ -1,271 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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 NavigationCardStackPanResponder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const I18nManager = require('I18nManager');
|
||||
const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder');
|
||||
|
||||
const clamp = require('clamp');
|
||||
|
||||
import type {
|
||||
NavigationPanPanHandlers,
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
const emptyFunction = () => {};
|
||||
|
||||
/**
|
||||
* The duration of the card animation in milliseconds.
|
||||
*/
|
||||
const ANIMATION_DURATION = 250;
|
||||
|
||||
/**
|
||||
* The threshold to invoke the `onNavigateBack` action.
|
||||
* For instance, `1 / 3` means that moving greater than 1 / 3 of the width of
|
||||
* the view will navigate.
|
||||
*/
|
||||
const POSITION_THRESHOLD = 1 / 3;
|
||||
|
||||
/**
|
||||
* The threshold (in pixels) to start the gesture action.
|
||||
*/
|
||||
const RESPOND_THRESHOLD = 15;
|
||||
|
||||
/**
|
||||
* The threshold (in pixels) to finish the gesture action.
|
||||
*/
|
||||
const DISTANCE_THRESHOLD = 100;
|
||||
|
||||
/**
|
||||
* Primitive gesture directions.
|
||||
*/
|
||||
const Directions = {
|
||||
'HORIZONTAL': 'horizontal',
|
||||
'VERTICAL': 'vertical',
|
||||
};
|
||||
|
||||
export type NavigationGestureDirection = 'horizontal' | 'vertical';
|
||||
|
||||
type Props = NavigationSceneRendererProps & {
|
||||
onNavigateBack: ?Function,
|
||||
/**
|
||||
* The distance from the edge of the navigator which gesture response can start for.
|
||||
**/
|
||||
gestureResponseDistance: ?number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Pan responder that handles gesture for a card in the cards stack.
|
||||
*
|
||||
* +------------+
|
||||
* +-+ |
|
||||
* +-+ | |
|
||||
* | | | |
|
||||
* | | | Focused |
|
||||
* | | | Card |
|
||||
* | | | |
|
||||
* +-+ | |
|
||||
* +-+ |
|
||||
* +------------+
|
||||
*/
|
||||
class NavigationCardStackPanResponder extends NavigationAbstractPanResponder {
|
||||
|
||||
_isResponding: boolean;
|
||||
_isVertical: boolean;
|
||||
_props: Props;
|
||||
_startValue: number;
|
||||
|
||||
constructor(
|
||||
direction: NavigationGestureDirection,
|
||||
props: Props,
|
||||
) {
|
||||
super();
|
||||
this._isResponding = false;
|
||||
this._isVertical = direction === Directions.VERTICAL;
|
||||
this._props = props;
|
||||
this._startValue = 0;
|
||||
|
||||
// Hack to make this work with native driven animations. We add a single listener
|
||||
// so the JS value of the following animated values gets updated. We rely on
|
||||
// some Animated private APIs and not doing so would require using a bunch of
|
||||
// value listeners but we'd have to remove them to not leak and I'm not sure
|
||||
// when we'd do that with the current structure we have. `stopAnimation` callback
|
||||
// is also broken with native animated values that have no listeners so if we
|
||||
// want to remove this we have to fix this too.
|
||||
this._addNativeListener(this._props.layout.width);
|
||||
this._addNativeListener(this._props.layout.height);
|
||||
this._addNativeListener(this._props.position);
|
||||
}
|
||||
|
||||
onMoveShouldSetPanResponder(event: any, gesture: any): boolean {
|
||||
const props = this._props;
|
||||
|
||||
if (props.navigationState.index !== props.scene.index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const layout = props.layout;
|
||||
const isVertical = this._isVertical;
|
||||
const index = props.navigationState.index;
|
||||
const currentDragDistance = gesture[isVertical ? 'dy' : 'dx'];
|
||||
const currentDragPosition = gesture[isVertical ? 'moveY' : 'moveX'];
|
||||
const maxDragDistance = isVertical ?
|
||||
layout.height.__getValue() :
|
||||
layout.width.__getValue();
|
||||
|
||||
const positionMax = isVertical ?
|
||||
props.gestureResponseDistance :
|
||||
/**
|
||||
* For horizontal scroll views, a distance of 30 from the left of the screen is the
|
||||
* standard maximum position to start touch responsiveness.
|
||||
*/
|
||||
props.gestureResponseDistance || 30;
|
||||
|
||||
if (positionMax != null && currentDragPosition > positionMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
Math.abs(currentDragDistance) > RESPOND_THRESHOLD &&
|
||||
maxDragDistance > 0 &&
|
||||
index > 0
|
||||
);
|
||||
}
|
||||
|
||||
onPanResponderGrant(): void {
|
||||
this._isResponding = false;
|
||||
this._props.position.stopAnimation((value: number) => {
|
||||
this._isResponding = true;
|
||||
this._startValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
onPanResponderMove(event: any, gesture: any): void {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
|
||||
const props = this._props;
|
||||
const layout = props.layout;
|
||||
const isVertical = this._isVertical;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const index = props.navigationState.index;
|
||||
const distance = isVertical ?
|
||||
layout.height.__getValue() :
|
||||
layout.width.__getValue();
|
||||
const currentValue = I18nManager.isRTL && axis === 'dx' ?
|
||||
this._startValue + (gesture[axis] / distance) :
|
||||
this._startValue - (gesture[axis] / distance);
|
||||
|
||||
const value = clamp(
|
||||
index - 1,
|
||||
currentValue,
|
||||
index
|
||||
);
|
||||
|
||||
props.position.setValue(value);
|
||||
}
|
||||
|
||||
onPanResponderRelease(event: any, gesture: any): void {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isResponding = false;
|
||||
|
||||
const props = this._props;
|
||||
const isVertical = this._isVertical;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const index = props.navigationState.index;
|
||||
const distance = I18nManager.isRTL && axis === 'dx' ?
|
||||
-gesture[axis] :
|
||||
gesture[axis];
|
||||
|
||||
props.position.stopAnimation((value: number) => {
|
||||
this._reset();
|
||||
|
||||
if (!props.onNavigateBack) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
distance > DISTANCE_THRESHOLD ||
|
||||
value <= index - POSITION_THRESHOLD
|
||||
) {
|
||||
props.onNavigateBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onPanResponderTerminate(): void {
|
||||
this._isResponding = false;
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_reset(): void {
|
||||
const props = this._props;
|
||||
Animated.timing(
|
||||
props.position,
|
||||
{
|
||||
toValue: props.navigationState.index,
|
||||
duration: ANIMATION_DURATION,
|
||||
useNativeDriver: props.position.__isNative,
|
||||
}
|
||||
).start();
|
||||
}
|
||||
|
||||
_addNativeListener(animatedValue) {
|
||||
if (!animatedValue.__isNative) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(animatedValue._listeners).length === 0) {
|
||||
animatedValue.addListener(emptyFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createPanHandlers(
|
||||
direction: NavigationGestureDirection,
|
||||
props: Props,
|
||||
): NavigationPanPanHandlers {
|
||||
const responder = new NavigationCardStackPanResponder(direction, props);
|
||||
return responder.panHandlers;
|
||||
}
|
||||
|
||||
function forHorizontal(
|
||||
props: Props,
|
||||
): NavigationPanPanHandlers {
|
||||
return createPanHandlers(Directions.HORIZONTAL, props);
|
||||
}
|
||||
|
||||
function forVertical(
|
||||
props: Props,
|
||||
): NavigationPanPanHandlers {
|
||||
return createPanHandlers(Directions.VERTICAL, props);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// constants
|
||||
ANIMATION_DURATION,
|
||||
DISTANCE_THRESHOLD,
|
||||
POSITION_THRESHOLD,
|
||||
RESPOND_THRESHOLD,
|
||||
|
||||
// enums
|
||||
Directions,
|
||||
|
||||
// methods.
|
||||
forHorizontal,
|
||||
forVertical,
|
||||
};
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationCardStackStyleInterpolator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const I18nManager = require('I18nManager');
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* Utility that builds the style for the card in the cards stack.
|
||||
*
|
||||
* +------------+
|
||||
* +-+ |
|
||||
* +-+ | |
|
||||
* | | | |
|
||||
* | | | Focused |
|
||||
* | | | Card |
|
||||
* | | | |
|
||||
* +-+ | |
|
||||
* +-+ |
|
||||
* +------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render the initial style when the initial layout isn't measured yet.
|
||||
*/
|
||||
function forInitial(props: NavigationSceneRendererProps): Object {
|
||||
const {
|
||||
navigationState,
|
||||
scene,
|
||||
} = props;
|
||||
|
||||
const focused = navigationState.index === scene.index;
|
||||
const opacity = focused ? 1 : 0;
|
||||
// If not focused, move the scene to the far away.
|
||||
const translate = focused ? 0 : 1000000;
|
||||
return {
|
||||
opacity,
|
||||
transform: [
|
||||
{ translateX: translate },
|
||||
{ translateY: translate },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function forHorizontal(props: NavigationSceneRendererProps): Object {
|
||||
const {
|
||||
layout,
|
||||
position,
|
||||
scene,
|
||||
} = props;
|
||||
|
||||
if (!layout.isMeasured) {
|
||||
return forInitial(props);
|
||||
}
|
||||
|
||||
const index = scene.index;
|
||||
const inputRange = [index - 1, index, index + 0.99, index + 1];
|
||||
const width = layout.initWidth;
|
||||
const outputRange = I18nManager.isRTL ?
|
||||
([-width, 0, 10, 10]: Array<number>) :
|
||||
([width, 0, -10, -10]: Array<number>);
|
||||
|
||||
|
||||
const opacity = position.interpolate({
|
||||
inputRange,
|
||||
outputRange: ([1, 1, 0.3, 0]: Array<number>),
|
||||
});
|
||||
|
||||
const scale = position.interpolate({
|
||||
inputRange,
|
||||
outputRange: ([1, 1, 0.95, 0.95]: Array<number>),
|
||||
});
|
||||
|
||||
const translateY = 0;
|
||||
const translateX = position.interpolate({
|
||||
inputRange,
|
||||
outputRange,
|
||||
});
|
||||
|
||||
return {
|
||||
opacity,
|
||||
transform: [
|
||||
{ scale },
|
||||
{ translateX },
|
||||
{ translateY },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function forVertical(props: NavigationSceneRendererProps): Object {
|
||||
const {
|
||||
layout,
|
||||
position,
|
||||
scene,
|
||||
} = props;
|
||||
|
||||
if (!layout.isMeasured) {
|
||||
return forInitial(props);
|
||||
}
|
||||
|
||||
const index = scene.index;
|
||||
const inputRange = [index - 1, index, index + 0.99, index + 1];
|
||||
const height = layout.initHeight;
|
||||
|
||||
const opacity = position.interpolate({
|
||||
inputRange,
|
||||
outputRange: ([1, 1, 0.3, 0]: Array<number>),
|
||||
});
|
||||
|
||||
const scale = position.interpolate({
|
||||
inputRange,
|
||||
outputRange: ([1, 1, 0.95, 0.95]: Array<number>),
|
||||
});
|
||||
|
||||
const translateX = 0;
|
||||
const translateY = position.interpolate({
|
||||
inputRange,
|
||||
outputRange: ([height, 0, -10, -10]: Array<number>),
|
||||
});
|
||||
|
||||
return {
|
||||
opacity,
|
||||
transform: [
|
||||
{ scale },
|
||||
{ translateX },
|
||||
{ translateY },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function canUseNativeDriver(isVertical: boolean): boolean {
|
||||
// The native driver can be enabled for this interpolator because the scale,
|
||||
// translateX, and translateY transforms are supported with the native
|
||||
// animation driver.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forHorizontal,
|
||||
forVertical,
|
||||
canUseNativeDriver,
|
||||
};
|
|
@ -1,282 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationHeader
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NavigationHeaderBackButton = require('NavigationHeaderBackButton');
|
||||
const NavigationHeaderStyleInterpolator = require('NavigationHeaderStyleInterpolator');
|
||||
const NavigationHeaderTitle = require('NavigationHeaderTitle');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const React = require('React');
|
||||
const ReactNative = require('react-native');
|
||||
const TVEventHandler = require('TVEventHandler');
|
||||
|
||||
const {
|
||||
Animated,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
NavigationStyleInterpolator,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type SubViewProps = NavigationSceneRendererProps & {
|
||||
onNavigateBack: ?Function,
|
||||
};
|
||||
|
||||
type SubViewRenderer = (subViewProps: SubViewProps) => ?React.Element<any>;
|
||||
|
||||
type DefaultProps = {
|
||||
renderLeftComponent: SubViewRenderer,
|
||||
renderRightComponent: SubViewRenderer,
|
||||
renderTitleComponent: SubViewRenderer,
|
||||
statusBarHeight: number | Animated.Value,
|
||||
};
|
||||
|
||||
type Props = NavigationSceneRendererProps & {
|
||||
onNavigateBack: ?Function,
|
||||
renderLeftComponent: SubViewRenderer,
|
||||
renderRightComponent: SubViewRenderer,
|
||||
renderTitleComponent: SubViewRenderer,
|
||||
style?: any,
|
||||
viewProps?: any,
|
||||
statusBarHeight: number | Animated.Value,
|
||||
};
|
||||
|
||||
type SubViewName = 'left' | 'title' | 'right';
|
||||
|
||||
const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
|
||||
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;
|
||||
const {PropTypes} = React;
|
||||
|
||||
class NavigationHeader extends React.PureComponent<DefaultProps, Props, any> {
|
||||
props: Props;
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
renderTitleComponent: (props: SubViewProps) => {
|
||||
const title = String(props.scene.route.title || '');
|
||||
return <NavigationHeaderTitle>{title}</NavigationHeaderTitle>;
|
||||
},
|
||||
|
||||
renderLeftComponent: (props: SubViewProps) => {
|
||||
if (props.scene.index === 0 || !props.onNavigateBack) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavigationHeaderBackButton
|
||||
onPress={props.onNavigateBack}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
renderRightComponent: (props: SubViewProps) => {
|
||||
return null;
|
||||
},
|
||||
|
||||
statusBarHeight: STATUSBAR_HEIGHT,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...NavigationPropTypes.SceneRendererProps,
|
||||
onNavigateBack: PropTypes.func,
|
||||
renderLeftComponent: PropTypes.func,
|
||||
renderRightComponent: PropTypes.func,
|
||||
renderTitleComponent: PropTypes.func,
|
||||
style: View.propTypes.style,
|
||||
statusBarHeight: PropTypes.number,
|
||||
viewProps: PropTypes.shape(View.propTypes),
|
||||
};
|
||||
|
||||
_tvEventHandler: TVEventHandler;
|
||||
|
||||
componentDidMount(): void {
|
||||
this._tvEventHandler = new TVEventHandler();
|
||||
this._tvEventHandler.enable(this, function(cmp, evt) {
|
||||
if (evt && evt.eventType === 'menu') {
|
||||
cmp.props.onNavigateBack && cmp.props.onNavigateBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this._tvEventHandler) {
|
||||
this._tvEventHandler.disable();
|
||||
delete this._tvEventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
const { scenes, style, viewProps } = this.props;
|
||||
|
||||
const scenesProps = scenes.map(scene => {
|
||||
const props = NavigationPropTypes.extractSceneRendererProps(this.props);
|
||||
props.scene = scene;
|
||||
return props;
|
||||
});
|
||||
|
||||
const barHeight = (this.props.statusBarHeight instanceof Animated.Value)
|
||||
? Animated.add(this.props.statusBarHeight, new Animated.Value(APPBAR_HEIGHT))
|
||||
: APPBAR_HEIGHT + this.props.statusBarHeight;
|
||||
|
||||
return (
|
||||
<Animated.View style={[
|
||||
styles.appbar,
|
||||
{ height: barHeight },
|
||||
style
|
||||
]}
|
||||
{...viewProps}
|
||||
>
|
||||
{scenesProps.map(this._renderLeft, this)}
|
||||
{scenesProps.map(this._renderTitle, this)}
|
||||
{scenesProps.map(this._renderRight, this)}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderLeft = (props: NavigationSceneRendererProps): ?React.Element<any> => {
|
||||
return this._renderSubView(
|
||||
props,
|
||||
'left',
|
||||
this.props.renderLeftComponent,
|
||||
NavigationHeaderStyleInterpolator.forLeft,
|
||||
);
|
||||
};
|
||||
|
||||
_renderTitle = (props: NavigationSceneRendererProps): ?React.Element<any> => {
|
||||
return this._renderSubView(
|
||||
props,
|
||||
'title',
|
||||
this.props.renderTitleComponent,
|
||||
NavigationHeaderStyleInterpolator.forCenter,
|
||||
);
|
||||
};
|
||||
|
||||
_renderRight = (props: NavigationSceneRendererProps): ?React.Element<any> => {
|
||||
return this._renderSubView(
|
||||
props,
|
||||
'right',
|
||||
this.props.renderRightComponent,
|
||||
NavigationHeaderStyleInterpolator.forRight,
|
||||
);
|
||||
};
|
||||
|
||||
_renderSubView(
|
||||
props: NavigationSceneRendererProps,
|
||||
name: SubViewName,
|
||||
renderer: SubViewRenderer,
|
||||
styleInterpolator: NavigationStyleInterpolator,
|
||||
): ?React.Element<any> {
|
||||
const {
|
||||
scene,
|
||||
navigationState,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
index,
|
||||
isStale,
|
||||
key,
|
||||
} = scene;
|
||||
|
||||
const offset = navigationState.index - index;
|
||||
|
||||
if (Math.abs(offset) > 2) {
|
||||
// Scene is far away from the active scene. Hides it to avoid unnecessary
|
||||
// rendering.
|
||||
return null;
|
||||
}
|
||||
|
||||
const subViewProps = {...props, onNavigateBack: this.props.onNavigateBack};
|
||||
const subView = renderer(subViewProps);
|
||||
if (subView === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pointerEvents = offset !== 0 || isStale ? 'none' : 'box-none';
|
||||
return (
|
||||
<Animated.View
|
||||
pointerEvents={pointerEvents}
|
||||
key={name + '_' + key}
|
||||
style={[
|
||||
styles[name],
|
||||
{ marginTop: this.props.statusBarHeight },
|
||||
styleInterpolator(props),
|
||||
]}>
|
||||
{subView}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
static HEIGHT = APPBAR_HEIGHT + STATUSBAR_HEIGHT;
|
||||
static Title = NavigationHeaderTitle;
|
||||
static BackButton = NavigationHeaderBackButton;
|
||||
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
appbar: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: Platform.OS === 'ios' ? '#EFEFF2' : '#FFF',
|
||||
borderBottomColor: 'rgba(0, 0, 0, .15)',
|
||||
borderBottomWidth: Platform.OS === 'ios' ? StyleSheet.hairlineWidth : 0,
|
||||
elevation: 4,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
|
||||
title: {
|
||||
bottom: 0,
|
||||
left: APPBAR_HEIGHT,
|
||||
position: 'absolute',
|
||||
right: APPBAR_HEIGHT,
|
||||
top: 0,
|
||||
},
|
||||
|
||||
left: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
},
|
||||
|
||||
right: {
|
||||
bottom: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationHeader;
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule NavigationHeaderBackButton
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const {
|
||||
I18nManager,
|
||||
Image,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} = ReactNative;
|
||||
|
||||
type Props = {
|
||||
imageStyle?: any,
|
||||
onPress: Function,
|
||||
style?: any,
|
||||
};
|
||||
|
||||
const NavigationHeaderBackButton = (props: Props) => (
|
||||
<TouchableOpacity style={[styles.buttonContainer, props.style]} onPress={props.onPress}>
|
||||
<Image style={[styles.button, props.imageStyle]} source={require('./assets/back-icon.png')} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
NavigationHeaderBackButton.propTypes = {
|
||||
onPress: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
button: {
|
||||
height: 24,
|
||||
width: 24,
|
||||
margin: Platform.OS === 'ios' ? 10 : 16,
|
||||
resizeMode: 'contain',
|
||||
transform: [{scaleX: I18nManager.isRTL ? -1 : 1}],
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NavigationHeaderBackButton;
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationHeaderStyleInterpolator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const I18nManager = require('I18nManager');
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* Utility that builds the style for the navigation header.
|
||||
*
|
||||
* +-------------+-------------+-------------+
|
||||
* | | | |
|
||||
* | Left | Title | Right |
|
||||
* | Component | Component | Component |
|
||||
* | | | |
|
||||
* +-------------+-------------+-------------+
|
||||
*/
|
||||
|
||||
function forLeft(props: NavigationSceneRendererProps): Object {
|
||||
const {position, scene} = props;
|
||||
const {index} = scene;
|
||||
return {
|
||||
opacity: position.interpolate({
|
||||
inputRange: [ index - 1, index, index + 1 ],
|
||||
outputRange: ([ 0, 1, 0 ]: Array<number>),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function forCenter(props: NavigationSceneRendererProps): Object {
|
||||
const {position, scene} = props;
|
||||
const {index} = scene;
|
||||
return {
|
||||
opacity:position.interpolate({
|
||||
inputRange: [ index - 1, index, index + 1 ],
|
||||
outputRange: ([ 0, 1, 0 ]: Array<number>),
|
||||
}),
|
||||
transform: [
|
||||
{
|
||||
translateX: position.interpolate({
|
||||
inputRange: [ index - 1, index + 1 ],
|
||||
outputRange: I18nManager.isRTL ?
|
||||
([ -200, 200 ]: Array<number>) :
|
||||
([ 200, -200 ]: Array<number>),
|
||||
}),
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function forRight(props: NavigationSceneRendererProps): Object {
|
||||
const {position, scene} = props;
|
||||
const {index} = scene;
|
||||
return {
|
||||
opacity: position.interpolate({
|
||||
inputRange: [ index - 1, index, index + 1 ],
|
||||
outputRange: ([ 0, 1, 0 ]: Array<number>),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forCenter,
|
||||
forLeft,
|
||||
forRight,
|
||||
};
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationHeaderTitle
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const {
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
} = ReactNative;
|
||||
|
||||
type Props = {
|
||||
children?: React.Element<any>,
|
||||
style?: any,
|
||||
textStyle?: any,
|
||||
viewProps?: any,
|
||||
}
|
||||
|
||||
const NavigationHeaderTitle = ({ children, style, textStyle, viewProps }: Props) => (
|
||||
<View style={[ styles.title, style ]} {...viewProps}>
|
||||
<Text style={[ styles.titleText, textStyle ]}>{children}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 16
|
||||
},
|
||||
|
||||
titleText: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
color: 'rgba(0, 0, 0, .9)',
|
||||
textAlign: Platform.OS === 'ios' ? 'center' : 'left'
|
||||
}
|
||||
});
|
||||
|
||||
NavigationHeaderTitle.propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
style: View.propTypes.style,
|
||||
textStyle: Text.propTypes.style
|
||||
};
|
||||
|
||||
module.exports = NavigationHeaderTitle;
|
|
@ -1,238 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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 NavigationPagerPanResponder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder');
|
||||
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
||||
const I18nManager = require('I18nManager');
|
||||
|
||||
const clamp = require('clamp');
|
||||
|
||||
import type {
|
||||
NavigationPanPanHandlers,
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
import type {
|
||||
NavigationGestureDirection,
|
||||
} from 'NavigationCardStackPanResponder';
|
||||
|
||||
type Props = NavigationSceneRendererProps & {
|
||||
onNavigateBack: ?Function,
|
||||
onNavigateForward: ?Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Primitive gesture directions.
|
||||
*/
|
||||
const {
|
||||
ANIMATION_DURATION,
|
||||
POSITION_THRESHOLD,
|
||||
RESPOND_THRESHOLD,
|
||||
Directions,
|
||||
} = NavigationCardStackPanResponder;
|
||||
|
||||
/**
|
||||
* The threshold (in pixels) to finish the gesture action.
|
||||
*/
|
||||
const DISTANCE_THRESHOLD = 50;
|
||||
|
||||
/**
|
||||
* The threshold to trigger the gesture action. This determines the rate of the
|
||||
* flick when the action will be triggered
|
||||
*/
|
||||
const VELOCITY_THRESHOLD = 1.5;
|
||||
|
||||
/**
|
||||
* Pan responder that handles gesture for a card in the cards list.
|
||||
*
|
||||
* +-------------+-------------+-------------+
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | Next | Focused | Previous |
|
||||
* | Card | Card | Card |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* +-------------+-------------+-------------+
|
||||
*/
|
||||
class NavigationPagerPanResponder extends NavigationAbstractPanResponder {
|
||||
|
||||
_isResponding: boolean;
|
||||
_isVertical: boolean;
|
||||
_props: Props;
|
||||
_startValue: number;
|
||||
|
||||
constructor(
|
||||
direction: NavigationGestureDirection,
|
||||
props: Props,
|
||||
) {
|
||||
super();
|
||||
this._isResponding = false;
|
||||
this._isVertical = direction === Directions.VERTICAL;
|
||||
this._props = props;
|
||||
this._startValue = 0;
|
||||
}
|
||||
|
||||
onMoveShouldSetPanResponder(event: any, gesture: any): boolean {
|
||||
const props = this._props;
|
||||
|
||||
if (props.navigationState.index !== props.scene.index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const layout = props.layout;
|
||||
const isVertical = this._isVertical;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const index = props.navigationState.index;
|
||||
const distance = isVertical ?
|
||||
layout.height.__getValue() :
|
||||
layout.width.__getValue();
|
||||
|
||||
return (
|
||||
Math.abs(gesture[axis]) > RESPOND_THRESHOLD &&
|
||||
distance > 0 &&
|
||||
index >= 0
|
||||
);
|
||||
}
|
||||
|
||||
onPanResponderGrant(): void {
|
||||
this._isResponding = false;
|
||||
this._props.position.stopAnimation((value: number) => {
|
||||
this._isResponding = true;
|
||||
this._startValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
onPanResponderMove(event: any, gesture: any): void {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
layout,
|
||||
navigationState,
|
||||
position,
|
||||
scenes,
|
||||
} = this._props;
|
||||
|
||||
const isVertical = this._isVertical;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const index = navigationState.index;
|
||||
const distance = isVertical ?
|
||||
layout.height.__getValue() :
|
||||
layout.width.__getValue();
|
||||
const currentValue = I18nManager.isRTL && axis === 'dx' ?
|
||||
this._startValue + (gesture[axis] / distance) :
|
||||
this._startValue - (gesture[axis] / distance);
|
||||
|
||||
const prevIndex = Math.max(
|
||||
0,
|
||||
index - 1,
|
||||
);
|
||||
|
||||
const nextIndex = Math.min(
|
||||
index + 1,
|
||||
scenes.length - 1,
|
||||
);
|
||||
|
||||
const value = clamp(
|
||||
prevIndex,
|
||||
currentValue,
|
||||
nextIndex,
|
||||
);
|
||||
|
||||
position.setValue(value);
|
||||
}
|
||||
|
||||
onPanResponderRelease(event: any, gesture: any): void {
|
||||
if (!this._isResponding) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isResponding = false;
|
||||
|
||||
const {
|
||||
navigationState,
|
||||
onNavigateBack,
|
||||
onNavigateForward,
|
||||
position,
|
||||
} = this._props;
|
||||
|
||||
const isVertical = this._isVertical;
|
||||
const axis = isVertical ? 'dy' : 'dx';
|
||||
const velocityAxis = isVertical ? 'vy' : 'vx';
|
||||
const index = navigationState.index;
|
||||
const distance = I18nManager.isRTL && axis === 'dx' ?
|
||||
-gesture[axis] :
|
||||
gesture[axis];
|
||||
const moveSpeed = I18nManager.isRTL && velocityAxis === 'vx' ?
|
||||
-gesture[velocityAxis] :
|
||||
gesture[velocityAxis];
|
||||
|
||||
position.stopAnimation((value: number) => {
|
||||
this._reset();
|
||||
if (
|
||||
distance > DISTANCE_THRESHOLD ||
|
||||
value <= index - POSITION_THRESHOLD ||
|
||||
moveSpeed > VELOCITY_THRESHOLD
|
||||
) {
|
||||
onNavigateBack && onNavigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
distance < -DISTANCE_THRESHOLD ||
|
||||
value >= index + POSITION_THRESHOLD ||
|
||||
moveSpeed < -VELOCITY_THRESHOLD
|
||||
) {
|
||||
onNavigateForward && onNavigateForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onPanResponderTerminate(): void {
|
||||
this._isResponding = false;
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_reset(): void {
|
||||
const props = this._props;
|
||||
Animated.timing(
|
||||
props.position,
|
||||
{
|
||||
toValue: props.navigationState.index,
|
||||
duration: ANIMATION_DURATION,
|
||||
}
|
||||
).start();
|
||||
}
|
||||
}
|
||||
|
||||
function createPanHandlers(
|
||||
direction: NavigationGestureDirection,
|
||||
props: Props,
|
||||
): NavigationPanPanHandlers {
|
||||
const responder = new NavigationPagerPanResponder(direction, props);
|
||||
return responder.panHandlers;
|
||||
}
|
||||
|
||||
function forHorizontal(
|
||||
props: Props,
|
||||
): NavigationPanPanHandlers {
|
||||
return createPanHandlers(Directions.HORIZONTAL, props);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forHorizontal,
|
||||
};
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationPagerStyleInterpolator
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const I18nManager = require('I18nManager');
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* Utility that builds the style for the card in the cards list.
|
||||
*
|
||||
* +-------------+-------------+-------------+
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | Next | Focused | Previous |
|
||||
* | Card | Card | Card |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* | | | |
|
||||
* +-------------+-------------+-------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render the initial style when the initial layout isn't measured yet.
|
||||
*/
|
||||
function forInitial(props: NavigationSceneRendererProps): Object {
|
||||
const {
|
||||
navigationState,
|
||||
scene,
|
||||
} = props;
|
||||
|
||||
const focused = navigationState.index === scene.index;
|
||||
const opacity = focused ? 1 : 0;
|
||||
// If not focused, move the scene to the far away.
|
||||
const dir = scene.index > navigationState.index ? 1 : -1;
|
||||
const translate = focused ? 0 : (1000000 * dir);
|
||||
return {
|
||||
opacity,
|
||||
transform: [
|
||||
{ translateX: translate },
|
||||
{ translateY: translate },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function forHorizontal(props: NavigationSceneRendererProps): Object {
|
||||
const {
|
||||
layout,
|
||||
position,
|
||||
scene,
|
||||
} = props;
|
||||
|
||||
if (!layout.isMeasured) {
|
||||
return forInitial(props);
|
||||
}
|
||||
|
||||
const index = scene.index;
|
||||
const inputRange = [index - 1, index, index + 1];
|
||||
const width = layout.initWidth;
|
||||
const outputRange = I18nManager.isRTL ?
|
||||
([-width, 0, width]: Array<number>) :
|
||||
([width, 0, -width]: Array<number>);
|
||||
|
||||
const translateX = position.interpolate({
|
||||
inputRange,
|
||||
outputRange,
|
||||
});
|
||||
|
||||
return {
|
||||
opacity : 1,
|
||||
shadowColor: 'transparent',
|
||||
shadowRadius: 0,
|
||||
transform: [
|
||||
{ scale: 1 },
|
||||
{ translateX },
|
||||
{ translateY: 0 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forHorizontal,
|
||||
};
|
|
@ -1,158 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* 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 NavigationPointerEventsContainer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('React');
|
||||
const NavigationAnimatedValueSubscription = require('NavigationAnimatedValueSubscription');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type Props = NavigationSceneRendererProps;
|
||||
|
||||
const MIN_POSITION_OFFSET = 0.01;
|
||||
|
||||
/**
|
||||
* Create a higher-order component that automatically computes the
|
||||
* `pointerEvents` property for a component whenever navigation position
|
||||
* changes.
|
||||
*/
|
||||
function create(
|
||||
Component: ReactClass<any>,
|
||||
): ReactClass<any> {
|
||||
|
||||
class Container extends React.Component<any, Props, any> {
|
||||
|
||||
_component: any;
|
||||
_onComponentRef: (view: any) => void;
|
||||
_onPositionChange: (data: {value: number}) => void;
|
||||
_pointerEvents: string;
|
||||
_positionListener: ?NavigationAnimatedValueSubscription;
|
||||
|
||||
props: Props;
|
||||
|
||||
constructor(props: Props, context: any) {
|
||||
super(props, context);
|
||||
this._pointerEvents = this._computePointerEvents();
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
this._onPositionChange = this._onPositionChange.bind(this);
|
||||
this._onComponentRef = this._onComponentRef.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this._bindPosition(this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this._positionListener && this._positionListener.remove();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props): void {
|
||||
this._bindPosition(nextProps);
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
this._pointerEvents = this._computePointerEvents();
|
||||
return (
|
||||
<Component
|
||||
{...this.props}
|
||||
pointerEvents={this._pointerEvents}
|
||||
onComponentRef={this._onComponentRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_onComponentRef(component: any): void {
|
||||
this._component = component;
|
||||
if (component) {
|
||||
invariant(
|
||||
typeof component.setNativeProps === 'function',
|
||||
'component must implement method `setNativeProps`',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_bindPosition(props: NavigationSceneRendererProps): void {
|
||||
this._positionListener && this._positionListener.remove();
|
||||
this._positionListener = new NavigationAnimatedValueSubscription(
|
||||
props.position,
|
||||
this._onPositionChange,
|
||||
);
|
||||
}
|
||||
|
||||
_onPositionChange(): void {
|
||||
if (this._component) {
|
||||
const pointerEvents = this._computePointerEvents();
|
||||
if (this._pointerEvents !== pointerEvents) {
|
||||
this._pointerEvents = pointerEvents;
|
||||
this._component.setNativeProps({pointerEvents});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_computePointerEvents(): string {
|
||||
const {
|
||||
navigationState,
|
||||
position,
|
||||
scene,
|
||||
} = this.props;
|
||||
|
||||
if (scene.isStale || navigationState.index !== scene.index) {
|
||||
// The scene isn't focused.
|
||||
return scene.index > navigationState.index ?
|
||||
'box-only' :
|
||||
'none';
|
||||
}
|
||||
|
||||
const offset = position.__getAnimatedValue() - navigationState.index;
|
||||
if (Math.abs(offset) > MIN_POSITION_OFFSET) {
|
||||
// The positon is still away from scene's index.
|
||||
// Scene's children should not receive touches until the position
|
||||
// is close enough to scene's index.
|
||||
return 'box-only';
|
||||
}
|
||||
|
||||
return 'auto';
|
||||
}
|
||||
}
|
||||
return Container;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
};
|
Before Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 100 B |
Before Width: | Height: | Size: 177 B |
Before Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 303 B |
Before Width: | Height: | Size: 167 B |
Before Width: | Height: | Size: 439 B |
Before Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 586 B |
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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 NavigationAbstractPanResponder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const PanResponder = require('PanResponder');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationPanPanHandlers,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
const EmptyPanHandlers = {
|
||||
onMoveShouldSetPanResponder: null,
|
||||
onPanResponderGrant: null,
|
||||
onPanResponderMove: null,
|
||||
onPanResponderRelease: null,
|
||||
onPanResponderTerminate: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class that defines the common interface of PanResponder that handles
|
||||
* the gesture actions.
|
||||
*/
|
||||
class NavigationAbstractPanResponder {
|
||||
|
||||
panHandlers: NavigationPanPanHandlers;
|
||||
|
||||
constructor() {
|
||||
const config = {};
|
||||
Object.keys(EmptyPanHandlers).forEach(name => {
|
||||
const fn: any = (this: any)[name];
|
||||
|
||||
invariant(
|
||||
typeof fn === 'function',
|
||||
'subclass of `NavigationAbstractPanResponder` must implement method %s',
|
||||
name
|
||||
);
|
||||
|
||||
config[name] = fn.bind(this);
|
||||
}, this);
|
||||
|
||||
this.panHandlers = PanResponder.create(config).panHandlers;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NavigationAbstractPanResponder;
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* 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 NavigationExperimental
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NavigationCard = require('NavigationCard');
|
||||
const NavigationCardStack = require('NavigationCardStack');
|
||||
const NavigationHeader = require('NavigationHeader');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const NavigationStateUtils = require('NavigationStateUtils');
|
||||
const NavigationTransitioner = require('NavigationTransitioner');
|
||||
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
// This warning will only be reached if the user has required the module
|
||||
warning(
|
||||
false,
|
||||
'NavigationExperimental is deprecated and will be removed in a future ' +
|
||||
'version of React Native. The NavigationExperimental views live on in ' +
|
||||
'the React-Navigation project, which also makes it easy to declare ' +
|
||||
'navigation logic for your app. Learn more at https://reactnavigation.org/'
|
||||
);
|
||||
|
||||
|
||||
const NavigationExperimental = {
|
||||
// Core
|
||||
StateUtils: NavigationStateUtils,
|
||||
|
||||
// Views
|
||||
Transitioner: NavigationTransitioner,
|
||||
|
||||
// CustomComponents:
|
||||
Card: NavigationCard,
|
||||
CardStack: NavigationCardStack,
|
||||
Header: NavigationHeader,
|
||||
|
||||
PropTypes: NavigationPropTypes,
|
||||
};
|
||||
|
||||
module.exports = NavigationExperimental;
|
|
@ -1,124 +0,0 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
import type {
|
||||
NavigationSceneRendererProps,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
/* NavigationAction */
|
||||
const action = PropTypes.shape({
|
||||
type: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
/* NavigationAnimatedValue */
|
||||
const animatedValue = PropTypes.instanceOf(Animated.Value);
|
||||
|
||||
/* NavigationRoute */
|
||||
const navigationRoute = PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
/* NavigationState */
|
||||
const navigationState = PropTypes.shape({
|
||||
index: PropTypes.number.isRequired,
|
||||
routes: PropTypes.arrayOf(navigationRoute),
|
||||
});
|
||||
|
||||
/* NavigationLayout */
|
||||
const layout = PropTypes.shape({
|
||||
height: animatedValue,
|
||||
initHeight: PropTypes.number.isRequired,
|
||||
initWidth: PropTypes.number.isRequired,
|
||||
isMeasured: PropTypes.bool.isRequired,
|
||||
width: animatedValue,
|
||||
});
|
||||
|
||||
/* NavigationScene */
|
||||
const scene = PropTypes.shape({
|
||||
index: PropTypes.number.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
isStale: PropTypes.bool.isRequired,
|
||||
key: PropTypes.string.isRequired,
|
||||
route: navigationRoute.isRequired,
|
||||
});
|
||||
|
||||
/* NavigationSceneRendererProps */
|
||||
const SceneRendererProps = {
|
||||
layout: layout.isRequired,
|
||||
navigationState: navigationState.isRequired,
|
||||
position: animatedValue.isRequired,
|
||||
progress: animatedValue.isRequired,
|
||||
scene: scene.isRequired,
|
||||
scenes: PropTypes.arrayOf(scene).isRequired,
|
||||
};
|
||||
|
||||
const SceneRenderer = PropTypes.shape(SceneRendererProps);
|
||||
|
||||
/* NavigationPanPanHandlers */
|
||||
const panHandlers = PropTypes.shape({
|
||||
onMoveShouldSetResponder: PropTypes.func.isRequired,
|
||||
onMoveShouldSetResponderCapture: PropTypes.func.isRequired,
|
||||
onResponderEnd: PropTypes.func.isRequired,
|
||||
onResponderGrant: PropTypes.func.isRequired,
|
||||
onResponderMove: PropTypes.func.isRequired,
|
||||
onResponderReject: PropTypes.func.isRequired,
|
||||
onResponderRelease: PropTypes.func.isRequired,
|
||||
onResponderStart: PropTypes.func.isRequired,
|
||||
onResponderTerminate: PropTypes.func.isRequired,
|
||||
onResponderTerminationRequest: PropTypes.func.isRequired,
|
||||
onStartShouldSetResponder: PropTypes.func.isRequired,
|
||||
onStartShouldSetResponderCapture: PropTypes.func.isRequired,
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function that extracts the props needed for scene renderer.
|
||||
*/
|
||||
function extractSceneRendererProps(
|
||||
props: NavigationSceneRendererProps,
|
||||
): NavigationSceneRendererProps {
|
||||
return {
|
||||
layout: props.layout,
|
||||
navigationState: props.navigationState,
|
||||
position: props.position,
|
||||
progress: props.progress,
|
||||
scene: props.scene,
|
||||
scenes: props.scenes,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// helpers
|
||||
extractSceneRendererProps,
|
||||
|
||||
// Bundled propTypes.
|
||||
SceneRendererProps,
|
||||
|
||||
// propTypes
|
||||
SceneRenderer,
|
||||
action,
|
||||
navigationState,
|
||||
navigationRoute,
|
||||
panHandlers,
|
||||
};
|
|
@ -1,215 +0,0 @@
|
|||
/**
|
||||
* 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 NavigationStateUtils
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationRoute,
|
||||
NavigationState
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* Utilities to perform atomic operation with navigate state and routes.
|
||||
*
|
||||
* ```javascript
|
||||
* const state1 = {key: 'page 1'};
|
||||
* const state2 = NavigationStateUtils.push(state1, {key: 'page 2'});
|
||||
* ```
|
||||
*/
|
||||
const NavigationStateUtils = {
|
||||
|
||||
/**
|
||||
* Gets a route by key. If the route isn't found, returns `null`.
|
||||
*/
|
||||
get(state: NavigationState, key: string): ?NavigationRoute {
|
||||
return state.routes.find(route => route.key === key) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the first index at which a given route's key can be found in the
|
||||
* routes of the navigation state, or -1 if it is not present.
|
||||
*/
|
||||
indexOf(state: NavigationState, key: string): number {
|
||||
return state.routes.map(route => route.key).indexOf(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns `true` at which a given route's key can be found in the
|
||||
* routes of the navigation state.
|
||||
*/
|
||||
has(state: NavigationState, key: string): boolean {
|
||||
return !!state.routes.some(route => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pushes a new route into the navigation state.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* stack is at.
|
||||
*/
|
||||
push(state: NavigationState, route: NavigationRoute): NavigationState {
|
||||
invariant(
|
||||
NavigationStateUtils.indexOf(state, route.key) === -1,
|
||||
'should not push route with duplicated key %s',
|
||||
route.key,
|
||||
);
|
||||
|
||||
const routes = state.routes.slice();
|
||||
routes.push(route);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Pops out a route from the navigation state.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* stack is at.
|
||||
*/
|
||||
pop(state: NavigationState): NavigationState {
|
||||
if (state.index <= 0) {
|
||||
// [Note]: Over-popping does not throw error. Instead, it will be no-op.
|
||||
return state;
|
||||
}
|
||||
const routes = state.routes.slice(0, -1);
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route of the navigation state by index.
|
||||
*/
|
||||
jumpToIndex(state: NavigationState, index: number): NavigationState {
|
||||
if (index === state.index) {
|
||||
return state;
|
||||
}
|
||||
|
||||
invariant(!!state.routes[index], 'invalid index %s to jump to', index);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route of the navigation state by key.
|
||||
*/
|
||||
jumpTo(state: NavigationState, key: string): NavigationState {
|
||||
const index = NavigationStateUtils.indexOf(state, key);
|
||||
return NavigationStateUtils.jumpToIndex(state, index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route to the previous route.
|
||||
*/
|
||||
back(state: NavigationState): NavigationState {
|
||||
const index = state.index - 1;
|
||||
const route = state.routes[index];
|
||||
return route ? NavigationStateUtils.jumpToIndex(state, index) : state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focused route to the next route.
|
||||
*/
|
||||
forward(state: NavigationState): NavigationState {
|
||||
const index = state.index + 1;
|
||||
const route = state.routes[index];
|
||||
return route ? NavigationStateUtils.jumpToIndex(state, index) : state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a route by a key.
|
||||
* Note that this moves the index to the positon to where the new route in the
|
||||
* stack is at.
|
||||
*/
|
||||
replaceAt(
|
||||
state: NavigationState,
|
||||
key: string,
|
||||
route: NavigationRoute,
|
||||
): NavigationState {
|
||||
const index = NavigationStateUtils.indexOf(state, key);
|
||||
return NavigationStateUtils.replaceAtIndex(state, index, route);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a route by a index.
|
||||
* Note that this moves the index to the positon to where the new route in the
|
||||
* stack is at.
|
||||
*/
|
||||
replaceAtIndex(
|
||||
state: NavigationState,
|
||||
index: number,
|
||||
route: NavigationRoute,
|
||||
): NavigationState {
|
||||
invariant(
|
||||
!!state.routes[index],
|
||||
'invalid index %s for replacing route %s',
|
||||
index,
|
||||
route.key,
|
||||
);
|
||||
|
||||
if (state.routes[index] === route) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const routes = state.routes.slice();
|
||||
routes[index] = route;
|
||||
|
||||
return {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets all routes.
|
||||
* Note that this moves the index to the positon to where the last route in the
|
||||
* stack is at if the param `index` isn't provided.
|
||||
*/
|
||||
reset(
|
||||
state: NavigationState,
|
||||
routes: Array<NavigationRoute>,
|
||||
index?: number,
|
||||
): NavigationState {
|
||||
invariant(
|
||||
routes.length && Array.isArray(routes),
|
||||
'invalid routes to replace',
|
||||
);
|
||||
|
||||
const nextIndex: number = index === undefined ? routes.length - 1 : index;
|
||||
|
||||
if (state.routes.length === routes.length && state.index === nextIndex) {
|
||||
const compare = (route, ii) => routes[ii] === route;
|
||||
if (state.routes.every(compare)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
invariant(!!routes[nextIndex], 'invalid index %s to reset', nextIndex);
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: nextIndex,
|
||||
routes,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = NavigationStateUtils;
|
|
@ -1,292 +0,0 @@
|
|||
/**
|
||||
* 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 NavigationTransitioner
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animated = require('Animated');
|
||||
const Easing = require('Easing');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const NavigationScenesReducer = require('NavigationScenesReducer');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationAnimatedValue,
|
||||
NavigationLayout,
|
||||
NavigationScene,
|
||||
NavigationState,
|
||||
NavigationTransitionProps,
|
||||
NavigationTransitionSpec,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
type Props = {
|
||||
configureTransition: (
|
||||
a: NavigationTransitionProps,
|
||||
b: ?NavigationTransitionProps,
|
||||
) => NavigationTransitionSpec,
|
||||
navigationState: NavigationState,
|
||||
onTransitionEnd: () => void,
|
||||
onTransitionStart: () => void,
|
||||
render: (a: NavigationTransitionProps, b: ?NavigationTransitionProps) => any,
|
||||
style: any,
|
||||
};
|
||||
|
||||
type State = {
|
||||
layout: NavigationLayout,
|
||||
position: NavigationAnimatedValue,
|
||||
progress: NavigationAnimatedValue,
|
||||
scenes: Array<NavigationScene>,
|
||||
};
|
||||
|
||||
const {PropTypes} = React;
|
||||
|
||||
const DefaultTransitionSpec = {
|
||||
duration: 250,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
timing: Animated.timing,
|
||||
};
|
||||
|
||||
class NavigationTransitioner extends React.Component<any, Props, State> {
|
||||
_onLayout: (event: any) => void;
|
||||
_onTransitionEnd: () => void;
|
||||
_prevTransitionProps: ?NavigationTransitionProps;
|
||||
_transitionProps: NavigationTransitionProps;
|
||||
_isMounted: boolean;
|
||||
|
||||
props: Props;
|
||||
state: State;
|
||||
|
||||
static propTypes = {
|
||||
configureTransition: PropTypes.func,
|
||||
navigationState: NavigationPropTypes.navigationState.isRequired,
|
||||
onTransitionEnd: PropTypes.func,
|
||||
onTransitionStart: PropTypes.func,
|
||||
render: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props: Props, context: any) {
|
||||
super(props, context);
|
||||
|
||||
// The initial layout isn't measured. Measured layout will be only available
|
||||
// when the component is mounted.
|
||||
const layout = {
|
||||
height: new Animated.Value(0),
|
||||
initHeight: 0,
|
||||
initWidth: 0,
|
||||
isMeasured: false,
|
||||
width: new Animated.Value(0),
|
||||
};
|
||||
|
||||
this.state = {
|
||||
layout,
|
||||
position: new Animated.Value(this.props.navigationState.index),
|
||||
progress: new Animated.Value(1),
|
||||
scenes: NavigationScenesReducer([], this.props.navigationState),
|
||||
};
|
||||
|
||||
this._prevTransitionProps = null;
|
||||
this._transitionProps = buildTransitionProps(props, this.state);
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
this._onLayout = this._onLayout.bind(this);
|
||||
this._onTransitionEnd = this._onTransitionEnd.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props): void {
|
||||
const nextScenes = NavigationScenesReducer(
|
||||
this.state.scenes,
|
||||
nextProps.navigationState,
|
||||
this.props.navigationState
|
||||
);
|
||||
|
||||
if (nextScenes === this.state.scenes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = {
|
||||
...this.state,
|
||||
scenes: nextScenes,
|
||||
};
|
||||
|
||||
const {
|
||||
position,
|
||||
progress,
|
||||
} = nextState;
|
||||
|
||||
progress.setValue(0);
|
||||
|
||||
this._prevTransitionProps = this._transitionProps;
|
||||
this._transitionProps = buildTransitionProps(nextProps, nextState);
|
||||
|
||||
// get the transition spec.
|
||||
const transitionUserSpec = nextProps.configureTransition ?
|
||||
nextProps.configureTransition(
|
||||
this._transitionProps,
|
||||
this._prevTransitionProps,
|
||||
) :
|
||||
null;
|
||||
|
||||
const transitionSpec = {
|
||||
...DefaultTransitionSpec,
|
||||
...transitionUserSpec,
|
||||
};
|
||||
|
||||
const {timing} = transitionSpec;
|
||||
delete transitionSpec.timing;
|
||||
|
||||
const animations = [
|
||||
timing(
|
||||
progress,
|
||||
{
|
||||
...transitionSpec,
|
||||
toValue: 1,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
if (nextProps.navigationState.index !== this.props.navigationState.index) {
|
||||
animations.push(
|
||||
timing(
|
||||
position,
|
||||
{
|
||||
...transitionSpec,
|
||||
toValue: nextProps.navigationState.index,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// update scenes and play the transition
|
||||
this.setState(nextState, () => {
|
||||
nextProps.onTransitionStart && nextProps.onTransitionStart(
|
||||
this._transitionProps,
|
||||
this._prevTransitionProps,
|
||||
);
|
||||
Animated.parallel(animations).start(this._onTransitionEnd);
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
return (
|
||||
<View
|
||||
onLayout={this._onLayout}
|
||||
style={[styles.main, this.props.style]}>
|
||||
{this.props.render(this._transitionProps, this._prevTransitionProps)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_onLayout(event: any): void {
|
||||
const {height, width} = event.nativeEvent.layout;
|
||||
if (this.state.layout.initWidth === width &&
|
||||
this.state.layout.initHeight === height) {
|
||||
return;
|
||||
}
|
||||
const layout = {
|
||||
...this.state.layout,
|
||||
initHeight: height,
|
||||
initWidth: width,
|
||||
isMeasured: true,
|
||||
};
|
||||
|
||||
layout.height.setValue(height);
|
||||
layout.width.setValue(width);
|
||||
|
||||
const nextState = {
|
||||
...this.state,
|
||||
layout,
|
||||
};
|
||||
|
||||
this._transitionProps = buildTransitionProps(this.props, nextState);
|
||||
this.setState(nextState);
|
||||
}
|
||||
|
||||
_onTransitionEnd(): void {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevTransitionProps = this._prevTransitionProps;
|
||||
this._prevTransitionProps = null;
|
||||
|
||||
const nextState = {
|
||||
...this.state,
|
||||
scenes: this.state.scenes.filter(isSceneNotStale),
|
||||
};
|
||||
|
||||
this._transitionProps = buildTransitionProps(this.props, nextState);
|
||||
|
||||
this.setState(nextState, () => {
|
||||
this.props.onTransitionEnd && this.props.onTransitionEnd(
|
||||
this._transitionProps,
|
||||
prevTransitionProps,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildTransitionProps(
|
||||
props: Props,
|
||||
state: State,
|
||||
): NavigationTransitionProps {
|
||||
const {
|
||||
navigationState,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
layout,
|
||||
position,
|
||||
progress,
|
||||
scenes,
|
||||
} = state;
|
||||
|
||||
const scene = scenes.find(isSceneActive);
|
||||
|
||||
invariant(scene, 'No active scene when building navigation transition props.');
|
||||
|
||||
return {
|
||||
layout,
|
||||
navigationState,
|
||||
position,
|
||||
progress,
|
||||
scenes,
|
||||
scene
|
||||
};
|
||||
}
|
||||
|
||||
function isSceneNotStale(scene: NavigationScene): boolean {
|
||||
return !scene.isStale;
|
||||
}
|
||||
|
||||
function isSceneActive(scene: NavigationScene): boolean {
|
||||
return scene.isActive;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationTransitioner;
|
|
@ -1,121 +0,0 @@
|
|||
/**
|
||||
* 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');
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
// Object Instances
|
||||
|
||||
export type NavigationAnimatedValue = Animated.Value;
|
||||
|
||||
// Value & Structs.
|
||||
|
||||
export type NavigationGestureDirection = 'horizontal' | 'vertical';
|
||||
|
||||
export type NavigationRoute = {
|
||||
key: string,
|
||||
title?: string
|
||||
};
|
||||
|
||||
export type NavigationState = {
|
||||
index: number,
|
||||
routes: Array<NavigationRoute>,
|
||||
};
|
||||
|
||||
export type NavigationLayout = {
|
||||
height: NavigationAnimatedValue,
|
||||
initHeight: number,
|
||||
initWidth: number,
|
||||
isMeasured: boolean,
|
||||
width: NavigationAnimatedValue,
|
||||
};
|
||||
|
||||
export type NavigationScene = {
|
||||
index: number,
|
||||
isActive: boolean,
|
||||
isStale: boolean,
|
||||
key: string,
|
||||
route: NavigationRoute,
|
||||
};
|
||||
|
||||
export type NavigationTransitionProps = {
|
||||
// The layout of the transitioner of the scenes.
|
||||
layout: NavigationLayout,
|
||||
|
||||
// The navigation state of the transitioner.
|
||||
navigationState: NavigationState,
|
||||
|
||||
// The progressive index of the transitioner's navigation state.
|
||||
position: NavigationAnimatedValue,
|
||||
|
||||
// The value that represents the progress of the transition when navigation
|
||||
// state changes from one to another. Its numberic value will range from 0
|
||||
// to 1.
|
||||
// progress.__getAnimatedValue() < 1 : transtion is happening.
|
||||
// progress.__getAnimatedValue() == 1 : transtion completes.
|
||||
progress: NavigationAnimatedValue,
|
||||
|
||||
// All the scenes of the transitioner.
|
||||
scenes: Array<NavigationScene>,
|
||||
|
||||
// The active scene, corresponding to the route at
|
||||
// `navigationState.routes[navigationState.index]`.
|
||||
scene: NavigationScene,
|
||||
|
||||
// The gesture distance for `horizontal` and `vertical` transitions
|
||||
gestureResponseDistance?: ?number,
|
||||
};
|
||||
|
||||
// Similar to `NavigationTransitionProps`, except that the prop `scene`
|
||||
// represents the scene for the renderer to render.
|
||||
export type NavigationSceneRendererProps = NavigationTransitionProps;
|
||||
|
||||
export type NavigationPanPanHandlers = {
|
||||
onMoveShouldSetResponder: Function,
|
||||
onMoveShouldSetResponderCapture: Function,
|
||||
onResponderEnd: Function,
|
||||
onResponderGrant: Function,
|
||||
onResponderMove: Function,
|
||||
onResponderReject: Function,
|
||||
onResponderRelease: Function,
|
||||
onResponderStart: Function,
|
||||
onResponderTerminate: Function,
|
||||
onResponderTerminationRequest: Function,
|
||||
onStartShouldSetResponder: Function,
|
||||
onStartShouldSetResponderCapture: Function,
|
||||
};
|
||||
|
||||
export type NavigationTransitionSpec = {
|
||||
duration?: number,
|
||||
// An easing function from `Easing`.
|
||||
easing?: () => any,
|
||||
// A timing function such as `Animated.timing`.
|
||||
timing?: (value: NavigationAnimatedValue, config: any) => any,
|
||||
};
|
||||
|
||||
// Functions.
|
||||
|
||||
export type NavigationAnimationSetter = (
|
||||
position: NavigationAnimatedValue,
|
||||
newState: NavigationState,
|
||||
lastState: NavigationState,
|
||||
) => void;
|
||||
|
||||
export type NavigationSceneRenderer = (
|
||||
props: NavigationSceneRendererProps,
|
||||
) => ?React.Element<any>;
|
||||
|
||||
export type NavigationStyleInterpolator = (
|
||||
props: NavigationSceneRendererProps,
|
||||
) => Object;
|
|
@ -1,210 +0,0 @@
|
|||
/**
|
||||
* 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 NavigationScenesReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const shallowEqual = require('fbjs/lib/shallowEqual');
|
||||
|
||||
import type {
|
||||
NavigationRoute,
|
||||
NavigationScene,
|
||||
NavigationState,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
const SCENE_KEY_PREFIX = 'scene_';
|
||||
|
||||
/**
|
||||
* Helper function to compare route keys (e.g. "9", "11").
|
||||
*/
|
||||
function compareKey(one: string, two: string): number {
|
||||
const delta = one.length - two.length;
|
||||
if (delta > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (delta < 0) {
|
||||
return -1;
|
||||
}
|
||||
return one > two ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort scenes based on their index and view key.
|
||||
*/
|
||||
function compareScenes(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene,
|
||||
): number {
|
||||
if (one.index > two.index) {
|
||||
return 1;
|
||||
}
|
||||
if (one.index < two.index) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compareKey(
|
||||
one.key,
|
||||
two.key,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether two routes are the same.
|
||||
*/
|
||||
function areScenesShallowEqual(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene,
|
||||
): boolean {
|
||||
return (
|
||||
one.key === two.key &&
|
||||
one.index === two.index &&
|
||||
one.isStale === two.isStale &&
|
||||
one.isActive === two.isActive &&
|
||||
areRoutesShallowEqual(one.route, two.route)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether two routes are the same.
|
||||
*/
|
||||
function areRoutesShallowEqual(
|
||||
one: ?NavigationRoute,
|
||||
two: ?NavigationRoute,
|
||||
): boolean {
|
||||
if (!one || !two) {
|
||||
return one === two;
|
||||
}
|
||||
|
||||
if (one.key !== two.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shallowEqual(one, two);
|
||||
}
|
||||
|
||||
function NavigationScenesReducer(
|
||||
scenes: Array<NavigationScene>,
|
||||
nextState: NavigationState,
|
||||
prevState: ?NavigationState,
|
||||
): Array<NavigationScene> {
|
||||
if (prevState === nextState) {
|
||||
return scenes;
|
||||
}
|
||||
|
||||
const prevScenes: Map<string, NavigationScene> = new Map();
|
||||
const freshScenes: Map<string, NavigationScene> = new Map();
|
||||
const staleScenes: Map<string, NavigationScene> = new Map();
|
||||
|
||||
// Populate stale scenes from previous scenes marked as stale.
|
||||
scenes.forEach(scene => {
|
||||
const {key} = scene;
|
||||
if (scene.isStale) {
|
||||
staleScenes.set(key, scene);
|
||||
}
|
||||
prevScenes.set(key, scene);
|
||||
});
|
||||
|
||||
const nextKeys = new Set();
|
||||
nextState.routes.forEach((route, index) => {
|
||||
const key = SCENE_KEY_PREFIX + route.key;
|
||||
const scene = {
|
||||
index,
|
||||
isActive: false,
|
||||
isStale: false,
|
||||
key,
|
||||
route,
|
||||
};
|
||||
invariant(
|
||||
!nextKeys.has(key),
|
||||
`navigationState.routes[${index}].key "${key}" conflicts with ` +
|
||||
'another route!'
|
||||
);
|
||||
nextKeys.add(key);
|
||||
|
||||
if (staleScenes.has(key)) {
|
||||
// A previously `stale` scene is now part of the nextState, so we
|
||||
// revive it by removing it from the stale scene map.
|
||||
staleScenes.delete(key);
|
||||
}
|
||||
freshScenes.set(key, scene);
|
||||
});
|
||||
|
||||
if (prevState) {
|
||||
// Look at the previous routes and classify any removed scenes as `stale`.
|
||||
prevState.routes.forEach((route: NavigationRoute, index) => {
|
||||
const key = SCENE_KEY_PREFIX + route.key;
|
||||
if (freshScenes.has(key)) {
|
||||
return;
|
||||
}
|
||||
staleScenes.set(key, {
|
||||
index,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key,
|
||||
route,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const nextScenes = [];
|
||||
|
||||
const mergeScene = (nextScene => {
|
||||
const {key} = nextScene;
|
||||
const prevScene = prevScenes.has(key) ? prevScenes.get(key) : null;
|
||||
if (prevScene && areScenesShallowEqual(prevScene, nextScene)) {
|
||||
// Reuse `prevScene` as `scene` so view can avoid unnecessary re-render.
|
||||
// This assumes that the scene's navigation state is immutable.
|
||||
nextScenes.push(prevScene);
|
||||
} else {
|
||||
nextScenes.push(nextScene);
|
||||
}
|
||||
});
|
||||
|
||||
staleScenes.forEach(mergeScene);
|
||||
freshScenes.forEach(mergeScene);
|
||||
|
||||
nextScenes.sort(compareScenes);
|
||||
|
||||
let activeScenesCount = 0;
|
||||
nextScenes.forEach((scene, ii) => {
|
||||
const isActive = !scene.isStale && scene.index === nextState.index;
|
||||
if (isActive !== scene.isActive) {
|
||||
nextScenes[ii] = {
|
||||
...scene,
|
||||
isActive,
|
||||
};
|
||||
}
|
||||
if (isActive) {
|
||||
activeScenesCount++;
|
||||
}
|
||||
});
|
||||
|
||||
invariant(
|
||||
activeScenesCount === 1,
|
||||
'there should always be only one scene active, not %s.',
|
||||
activeScenesCount,
|
||||
);
|
||||
|
||||
if (nextScenes.length !== scenes.length) {
|
||||
return nextScenes;
|
||||
}
|
||||
|
||||
if (nextScenes.some(
|
||||
(scene, index) => !areScenesShallowEqual(scenes[index], scene)
|
||||
)) {
|
||||
return nextScenes;
|
||||
}
|
||||
|
||||
// scenes haven't changed.
|
||||
return scenes;
|
||||
}
|
||||
|
||||
module.exports = NavigationScenesReducer;
|
|
@ -1,300 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('NavigationScenesReducer');
|
||||
|
||||
const NavigationScenesReducer = require('NavigationScenesReducer');
|
||||
|
||||
/**
|
||||
* Simulate scenes transtion with changes of navigation states.
|
||||
*/
|
||||
function testTransition(states) {
|
||||
const routes = states.map(keys => {
|
||||
return {
|
||||
index: 0,
|
||||
routes: keys.map(key => {
|
||||
return { key };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let scenes = [];
|
||||
let prevState = null;
|
||||
routes.forEach((nextState) => {
|
||||
scenes = NavigationScenesReducer(scenes, nextState, prevState);
|
||||
prevState = nextState;
|
||||
});
|
||||
|
||||
return scenes;
|
||||
}
|
||||
|
||||
describe('NavigationScenesReducer', () => {
|
||||
|
||||
it('gets initial scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
index: 0,
|
||||
isActive: true,
|
||||
isStale: false,
|
||||
key: 'scene_1',
|
||||
route: {
|
||||
key: '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
isActive: false,
|
||||
isStale: false,
|
||||
key: 'scene_2',
|
||||
route: {
|
||||
key: '2'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('pushes new scenes', () => {
|
||||
// Transition from ['1', '2'] to ['1', '2', '3'].
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['1', '2', '3'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
index: 0,
|
||||
isActive: true,
|
||||
isStale: false,
|
||||
key: 'scene_1',
|
||||
route: {
|
||||
key: '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
isActive: false,
|
||||
isStale: false,
|
||||
key: 'scene_2',
|
||||
route: {
|
||||
key: '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
isActive: false,
|
||||
isStale: false,
|
||||
key: 'scene_3',
|
||||
route: {
|
||||
key: '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('gets active scene when index changes', () => {
|
||||
const state1 = {
|
||||
index: 0,
|
||||
routes: [{key: '1'}, {key: '2'}],
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
index: 1,
|
||||
routes: [{key: '1'}, {key: '2'}],
|
||||
};
|
||||
|
||||
const scenes1 = NavigationScenesReducer([], state1, null);
|
||||
const scenes2 = NavigationScenesReducer(scenes1, state2, state1);
|
||||
const route = scenes2.find((scene) => scene.isActive).route;
|
||||
expect(route).toEqual({key: '2'});
|
||||
});
|
||||
|
||||
it('gets same scenes', () => {
|
||||
const state1 = {
|
||||
index: 0,
|
||||
routes: [{key: '1'}, {key: '2'}],
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
index: 0,
|
||||
routes: [{key: '1'}, {key: '2'}],
|
||||
};
|
||||
|
||||
const scenes1 = NavigationScenesReducer([], state1, null);
|
||||
const scenes2 = NavigationScenesReducer(scenes1, state2, state1);
|
||||
expect(scenes1).toBe(scenes2);
|
||||
});
|
||||
|
||||
it('gets different scenes when keys are different', () => {
|
||||
const state1 = {
|
||||
index: 0,
|
||||
routes: [{key: '1'}, {key: '2'}],
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
index: 0,
|
||||
routes: [{key: '2'}, {key: '1'}],
|
||||
};
|
||||
|
||||
const scenes1 = NavigationScenesReducer([], state1, null);
|
||||
const scenes2 = NavigationScenesReducer(scenes1, state2, state1);
|
||||
expect(scenes1).not.toBe(scenes2);
|
||||
});
|
||||
|
||||
it('gets different scenes when routes are different', () => {
|
||||
const state1 = {
|
||||
index: 0,
|
||||
routes: [{key: '1', x: 1}, {key: '2', x: 2}],
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
index: 0,
|
||||
routes: [{key: '1', x: 3}, {key: '2', x: 4}],
|
||||
};
|
||||
|
||||
const scenes1 = NavigationScenesReducer([], state1, null);
|
||||
const scenes2 = NavigationScenesReducer(scenes1, state2, state1);
|
||||
expect(scenes1).not.toBe(scenes2);
|
||||
});
|
||||
|
||||
|
||||
it('gets different scenes when state index changes', () => {
|
||||
const state1 = {
|
||||
index: 0,
|
||||
routes: [{key: '1', x: 1}, {key: '2', x: 2}],
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
index: 1,
|
||||
routes: [{key: '1', x: 1}, {key: '2', x: 2}],
|
||||
};
|
||||
|
||||
const scenes1 = NavigationScenesReducer([], state1, null);
|
||||
const scenes2 = NavigationScenesReducer(scenes1, state2, state1);
|
||||
expect(scenes1).not.toBe(scenes2);
|
||||
});
|
||||
|
||||
it('pops scenes', () => {
|
||||
// Transition from ['1', '2', '3'] to ['1', '2'].
|
||||
const scenes = testTransition([
|
||||
['1', '2', '3'],
|
||||
['1', '2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
index: 0,
|
||||
isActive: true,
|
||||
isStale: false,
|
||||
key: 'scene_1',
|
||||
route: {
|
||||
key: '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
isActive: false,
|
||||
isStale: false,
|
||||
key: 'scene_2',
|
||||
route: {
|
||||
key: '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key: 'scene_3',
|
||||
route: {
|
||||
key: '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('replaces scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['3'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
index: 0,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key: 'scene_1',
|
||||
route: {
|
||||
key: '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 0,
|
||||
isActive: true,
|
||||
isStale: false,
|
||||
key: 'scene_3',
|
||||
route: {
|
||||
key: '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key: 'scene_2',
|
||||
route: {
|
||||
key: '2'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('revives scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['3'],
|
||||
['2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
index: 0,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key: 'scene_1',
|
||||
route: {
|
||||
key: '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 0,
|
||||
isActive: true,
|
||||
isStale: false,
|
||||
key: 'scene_2',
|
||||
route: {
|
||||
key: '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 0,
|
||||
isActive: false,
|
||||
isStale: true,
|
||||
key: 'scene_3',
|
||||
route: {
|
||||
key: '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,156 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('NavigationStateUtils');
|
||||
|
||||
const NavigationStateUtils = require('NavigationStateUtils');
|
||||
|
||||
describe('NavigationStateUtils', () => {
|
||||
|
||||
// Getters
|
||||
it('gets route', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}]};
|
||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({key: 'a'});
|
||||
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
|
||||
});
|
||||
|
||||
it('gets route index', () => {
|
||||
const state = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
|
||||
});
|
||||
|
||||
it('has a route', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
||||
});
|
||||
|
||||
// Push
|
||||
it('pushes a route', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.push(state, {key: 'b'})).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not push duplicated route', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}]};
|
||||
expect(() => NavigationStateUtils.push(state, {key: 'a'})).toThrow();
|
||||
});
|
||||
|
||||
// Pop
|
||||
it('pops route', () => {
|
||||
const state = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 0, routes: [{key: 'a'}]};
|
||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not pop route if not applicable', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}]};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
|
||||
// Jump
|
||||
it('jumps to new index', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid index', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
||||
});
|
||||
|
||||
it('jumps to new key', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid key', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
||||
});
|
||||
|
||||
it('move backwards', () => {
|
||||
const state = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
it('move forwards', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
// Replace
|
||||
it('Replaces by key', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'c'}]};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAt(
|
||||
state,
|
||||
'b',
|
||||
{key: 'c'},
|
||||
)
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Replaces by index', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'a'}, {key: 'c'}]};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(
|
||||
state,
|
||||
1,
|
||||
{key: 'c'},
|
||||
)
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
// Reset
|
||||
it('Resets routes', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 1, routes: [{key: 'x'}, {key: 'y'}]};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[{key: 'x'}, {key: 'y'}],
|
||||
)
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(state, []);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('Resets routes with index', () => {
|
||||
const state = {index: 0, routes: [{key: 'a'}, {key: 'b'}]};
|
||||
const newState = {index: 0, routes: [{key: 'x'}, {key: 'y'}]};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[{key: 'x'}, {key: 'y'}],
|
||||
0,
|
||||
)
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(state, [{key: 'x'}, {key: 'y'}], 100);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
|
@ -95,7 +95,6 @@ const ReactNative = {
|
|||
get LayoutAnimation() { return require('LayoutAnimation'); },
|
||||
get Linking() { return require('Linking'); },
|
||||
get NativeEventEmitter() { return require('NativeEventEmitter'); },
|
||||
get NavigationExperimental() { return require('NavigationExperimental'); },
|
||||
get NetInfo() { return require('NetInfo'); },
|
||||
get PanResponder() { return require('PanResponder'); },
|
||||
get PermissionsAndroid() { return require('PermissionsAndroid'); },
|
||||
|
|