/** * 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 NavigatorBreadcrumbNavigationBar */ 'use strict'; var NavigatorBreadcrumbNavigationBarStyles = require('NavigatorBreadcrumbNavigationBarStyles'); var NavigatorNavigationBarStyles = require('NavigatorNavigationBarStyles'); var React = require('React'); var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); var View = require('View'); var invariant = require('invariant'); var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators; var PropTypes = React.PropTypes; /** * Reusable props objects. */ var CRUMB_PROPS = Interpolators.map(() => {return {style: {}};}); var ICON_PROPS = Interpolators.map(() => {return {style: {}};}); var SEPARATOR_PROPS = Interpolators.map(() => {return {style: {}};}); var TITLE_PROPS = Interpolators.map(() => {return {style: {}};}); var RIGHT_BUTTON_PROPS = Interpolators.map(() => {return {style: {}};}); var navStatePresentedIndex = function(navState) { if (navState.presentedIndex !== undefined) { return navState.presentedIndex; } // TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS` return navState.observedTopOfStack; }; /** * The first route is initially rendered using a different style than all * future routes. * * @param {number} index Index of breadcrumb. * @return {object} Style config for initial rendering of index. */ var initStyle = function(index, presentedIndex) { return index === presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Center[index] : index < presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Left[index] : NavigatorBreadcrumbNavigationBarStyles.Right[index]; }; var NavigatorBreadcrumbNavigationBar = React.createClass({ propTypes: { navigator: PropTypes.shape({ push: PropTypes.func, pop: PropTypes.func, replace: PropTypes.func, popToRoute: PropTypes.func, popToTop: PropTypes.func, }), routeMapper: PropTypes.shape({ rightContentForRoute: PropTypes.func, titleContentForRoute: PropTypes.func, iconForRoute: PropTypes.func, }), navState: React.PropTypes.shape({ routeStack: React.PropTypes.arrayOf(React.PropTypes.object), idStack: React.PropTypes.arrayOf(React.PropTypes.number), presentedIndex: React.PropTypes.number, }), style: View.propTypes.style, }, statics: { Styles: NavigatorBreadcrumbNavigationBarStyles, }, _updateIndexProgress: function(progress, index, fromIndex, toIndex) { var amount = toIndex > fromIndex ? progress : (1 - progress); var oldDistToCenter = index - fromIndex; var newDistToCenter = index - toIndex; var interpolate; invariant( Interpolators[index], 'Cannot find breadcrumb interpolators for ' + index ); if (oldDistToCenter > 0 && newDistToCenter === 0 || newDistToCenter > 0 && oldDistToCenter === 0) { interpolate = Interpolators[index].RightToCenter; } else if (oldDistToCenter < 0 && newDistToCenter === 0 || newDistToCenter < 0 && oldDistToCenter === 0) { interpolate = Interpolators[index].CenterToLeft; } else if (oldDistToCenter === newDistToCenter) { interpolate = Interpolators[index].RightToCenter; } else { interpolate = Interpolators[index].RightToLeft; } if (interpolate.Crumb(CRUMB_PROPS[index].style, amount)) { this.refs['crumb_' + index].setNativeProps(CRUMB_PROPS[index]); } if (interpolate.Icon(ICON_PROPS[index].style, amount)) { this.refs['icon_' + index].setNativeProps(ICON_PROPS[index]); } if (interpolate.Separator(SEPARATOR_PROPS[index].style, amount)) { this.refs['separator_' + index].setNativeProps(SEPARATOR_PROPS[index]); } if (interpolate.Title(TITLE_PROPS[index].style, amount)) { this.refs['title_' + index].setNativeProps(TITLE_PROPS[index]); } var right = this.refs['right_' + index]; if (right && interpolate.RightItem(RIGHT_BUTTON_PROPS[index].style, amount)) { right.setNativeProps(RIGHT_BUTTON_PROPS[index]); } }, updateProgress: function(progress, fromIndex, 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); } }, onAnimationStart: function(fromIndex, toIndex) { var max = Math.max(fromIndex, toIndex); var min = Math.min(fromIndex, toIndex); for (var index = min; index <= max; index++) { this._setRenderViewsToHardwareTextureAndroid(index, true); } }, onAnimationEnd: function() { var max = this.props.navState.routeStack.length - 1; for (var index = 0; index <= max; index++) { this._setRenderViewsToHardwareTextureAndroid(index, false); } }, _setRenderViewsToHardwareTextureAndroid: function(index, renderToHardwareTexture) { var props = { renderToHardwareTextureAndroid: renderToHardwareTexture, }; this.refs['icon_' + index].setNativeProps(props); this.refs['separator_' + index].setNativeProps(props); this.refs['title_' + index].setNativeProps(props); var right = this.refs['right_' + index]; if (right) { right.setNativeProps(props); } }, render: function() { var navState = this.props.navState; var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb); var titles = navState.routeStack.map(this._renderOrReturnTitle); var buttons = navState.routeStack.map(this._renderOrReturnRightButton); return ( {titles} {icons} {buttons} ); }, _renderOrReturnBreadcrumb: function(route, index) { var uid = this.props.navState.idStack[index]; var navBarRouteMapper = this.props.routeMapper; var navOps = this.props.navigator; var alreadyRendered = this.refs['crumbContainer' + uid]; if (alreadyRendered) { // Don't bother re-calculating the children return ( ); } var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); return ( {navBarRouteMapper.iconForRoute(route, navOps)} {navBarRouteMapper.separatorForRoute(route, navOps)} ); }, _renderOrReturnTitle: function(route, index) { var navState = this.props.navState; var uid = navState.idStack[index]; var alreadyRendered = this.refs['titleContainer' + uid]; if (alreadyRendered) { // Don't bother re-calculating the children return ( ); } var navBarRouteMapper = this.props.routeMapper; var titleContent = navBarRouteMapper.titleContentForRoute( navState.routeStack[index], this.props.navigator ); var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); return ( {titleContent} ); }, _renderOrReturnRightButton: function(route, index) { var navState = this.props.navState; var navBarRouteMapper = this.props.routeMapper; var uid = navState.idStack[index]; var alreadyRendered = this.refs['rightContainer' + uid]; if (alreadyRendered) { // Don't bother re-calculating the children return ( ); } var rightContent = navBarRouteMapper.rightContentForRoute( navState.routeStack[index], this.props.navigator ); if (!rightContent) { return null; } var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); return ( {rightContent} ); }, }); var styles = StyleSheet.create({ breadCrumbContainer: { overflow: 'hidden', position: 'absolute', height: NavigatorNavigationBarStyles.General.TotalNavHeight, top: 0, left: 0, right: 0, }, }); module.exports = NavigatorBreadcrumbNavigationBar;