238 lines
7.3 KiB
JavaScript
238 lines
7.3 KiB
JavaScript
/**
|
|
* 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 NavigatorNavigationBar
|
|
*/
|
|
'use strict';
|
|
|
|
var React = require('React');
|
|
var NavigatorNavigationBarStylesAndroid = require('NavigatorNavigationBarStylesAndroid');
|
|
var NavigatorNavigationBarStylesIOS = require('NavigatorNavigationBarStylesIOS');
|
|
var Platform = require('Platform');
|
|
var StyleSheet = require('StyleSheet');
|
|
var View = require('View');
|
|
|
|
var guid = require('guid');
|
|
|
|
var { Map } = require('immutable');
|
|
|
|
var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
|
|
|
|
var NavigatorNavigationBarStyles = Platform.OS === 'android' ?
|
|
NavigatorNavigationBarStylesAndroid : NavigatorNavigationBarStylesIOS;
|
|
|
|
var navStatePresentedIndex = function(navState) {
|
|
if (navState.presentedIndex !== undefined) {
|
|
return navState.presentedIndex;
|
|
}
|
|
// TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS`
|
|
return navState.observedTopOfStack;
|
|
};
|
|
|
|
var NavigatorNavigationBar = React.createClass({
|
|
|
|
propTypes: {
|
|
navigator: React.PropTypes.object,
|
|
routeMapper: React.PropTypes.shape({
|
|
Title: React.PropTypes.func.isRequired,
|
|
LeftButton: React.PropTypes.func.isRequired,
|
|
RightButton: React.PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
navState: React.PropTypes.shape({
|
|
routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
|
|
presentedIndex: React.PropTypes.number,
|
|
}),
|
|
navigationStyles: React.PropTypes.object,
|
|
style: View.propTypes.style,
|
|
},
|
|
|
|
statics: {
|
|
Styles: NavigatorNavigationBarStyles,
|
|
StylesAndroid: NavigatorNavigationBarStylesAndroid,
|
|
StylesIOS: NavigatorNavigationBarStylesIOS,
|
|
},
|
|
|
|
getDefaultProps() {
|
|
return {
|
|
navigationStyles: NavigatorNavigationBarStyles,
|
|
};
|
|
},
|
|
|
|
componentWillMount: function() {
|
|
this._reset();
|
|
},
|
|
|
|
/**
|
|
* Stop transtion, immediately resets the cached state and re-render the
|
|
* whole view.
|
|
*/
|
|
immediatelyRefresh() {
|
|
this._reset();
|
|
this.forceUpdate();
|
|
},
|
|
|
|
_reset() {
|
|
this._key = guid();
|
|
this._reusableProps = {};
|
|
this._components = {};
|
|
this._descriptors = {};
|
|
|
|
COMPONENT_NAMES.forEach(componentName => {
|
|
this._components[componentName] = new Map();
|
|
this._descriptors[componentName] = new Map();
|
|
});
|
|
},
|
|
|
|
_getReusableProps: function(
|
|
/*string*/componentName,
|
|
/*number*/index
|
|
) /*object*/ {
|
|
var propStack = this._reusableProps[componentName];
|
|
if (!propStack) {
|
|
propStack = this._reusableProps[componentName] = [];
|
|
}
|
|
var props = propStack[index];
|
|
if (!props) {
|
|
props = propStack[index] = {style:{}};
|
|
}
|
|
return props;
|
|
},
|
|
|
|
_updateIndexProgress: function(
|
|
/*number*/progress,
|
|
/*number*/index,
|
|
/*number*/fromIndex,
|
|
/*number*/toIndex
|
|
) {
|
|
var amount = toIndex > fromIndex ? progress : (1 - progress);
|
|
var oldDistToCenter = index - fromIndex;
|
|
var newDistToCenter = index - toIndex;
|
|
var interpolate;
|
|
if (oldDistToCenter > 0 && newDistToCenter === 0 ||
|
|
newDistToCenter > 0 && oldDistToCenter === 0) {
|
|
interpolate = this.props.navigationStyles.Interpolators.RightToCenter;
|
|
} else if (oldDistToCenter < 0 && newDistToCenter === 0 ||
|
|
newDistToCenter < 0 && oldDistToCenter === 0) {
|
|
interpolate = this.props.navigationStyles.Interpolators.CenterToLeft;
|
|
} else if (oldDistToCenter === newDistToCenter) {
|
|
interpolate = this.props.navigationStyles.Interpolators.RightToCenter;
|
|
} else {
|
|
interpolate = this.props.navigationStyles.Interpolators.RightToLeft;
|
|
}
|
|
|
|
COMPONENT_NAMES.forEach(function (componentName) {
|
|
var component = this._components[componentName].get(this.props.navState.routeStack[index]);
|
|
var props = this._getReusableProps(componentName, index);
|
|
if (component && interpolate[componentName](props.style, amount)) {
|
|
component.setNativeProps(props);
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
updateProgress: function(
|
|
/*number*/progress,
|
|
/*number*/fromIndex,
|
|
/*number*/toIndex
|
|
) {
|
|
var max = Math.max(fromIndex, toIndex);
|
|
var min = Math.min(fromIndex, toIndex);
|
|
for (var index = min; index <= max; index++) {
|
|
this._updateIndexProgress(progress, index, fromIndex, toIndex);
|
|
}
|
|
},
|
|
|
|
render: function() {
|
|
var navBarStyle = {
|
|
height: this.props.navigationStyles.General.TotalNavHeight,
|
|
};
|
|
var navState = this.props.navState;
|
|
var components = navState.routeStack.map((route, index) =>
|
|
COMPONENT_NAMES.map(componentName =>
|
|
this._getComponent(componentName, route, index)
|
|
)
|
|
);
|
|
|
|
return (
|
|
<View
|
|
key={this._key}
|
|
style={[styles.navBarContainer, navBarStyle, this.props.style]}>
|
|
{components}
|
|
</View>
|
|
);
|
|
},
|
|
|
|
_getComponent: function(
|
|
/*string*/componentName,
|
|
/*object*/route,
|
|
/*number*/index
|
|
) /*?Object*/ {
|
|
if (this._descriptors[componentName].includes(route)) {
|
|
return this._descriptors[componentName].get(route);
|
|
}
|
|
|
|
var rendered = null;
|
|
|
|
var content = this.props.routeMapper[componentName](
|
|
this.props.navState.routeStack[index],
|
|
this.props.navigator,
|
|
index,
|
|
this.props.navState
|
|
);
|
|
if (!content) {
|
|
return null;
|
|
}
|
|
|
|
var initialStage = index === navStatePresentedIndex(this.props.navState) ?
|
|
this.props.navigationStyles.Stages.Center :
|
|
this.props.navigationStyles.Stages.Left;
|
|
rendered = (
|
|
<View
|
|
ref={(ref) => {
|
|
this._components[componentName] = this._components[componentName].set(route, ref);
|
|
}}
|
|
pointerEvents="box-none"
|
|
style={initialStage[componentName]}>
|
|
{content}
|
|
</View>
|
|
);
|
|
|
|
this._descriptors[componentName] = this._descriptors[componentName].set(route, rendered);
|
|
return rendered;
|
|
},
|
|
|
|
});
|
|
|
|
|
|
var styles = StyleSheet.create({
|
|
navBarContainer: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
backgroundColor: 'transparent',
|
|
},
|
|
});
|
|
|
|
module.exports = NavigatorNavigationBar;
|