/**
* 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 NavigatorBreadcrumbNavigationBar
*/
'use strict';
const NavigatorBreadcrumbNavigationBarStyles = require('NavigatorBreadcrumbNavigationBarStyles');
const NavigatorNavigationBarStylesAndroid = require('NavigatorNavigationBarStylesAndroid');
const NavigatorNavigationBarStylesIOS = require('NavigatorNavigationBarStylesIOS');
const Platform = require('Platform');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');
const guid = require('guid');
const invariant = require('fbjs/lib/invariant');
const { Map } = require('immutable');
const Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
const NavigatorNavigationBarStyles = Platform.OS === 'android' ?
NavigatorNavigationBarStylesAndroid : NavigatorNavigationBarStylesIOS;
const PropTypes = React.PropTypes;
/**
* Reusable props objects.
*/
const CRUMB_PROPS = Interpolators.map(() => ({style: {}}));
const ICON_PROPS = Interpolators.map(() => ({style: {}}));
const SEPARATOR_PROPS = Interpolators.map(() => ({style: {}}));
const TITLE_PROPS = Interpolators.map(() => ({style: {}}));
const RIGHT_BUTTON_PROPS = Interpolators.map(() => ({style: {}}));
function navStatePresentedIndex(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.
*/
function initStyle(index, presentedIndex) {
return index === presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Center[index] :
index < presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Left[index] :
NavigatorBreadcrumbNavigationBarStyles.Right[index];
}
class NavigatorBreadcrumbNavigationBar extends React.Component {
static 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),
presentedIndex: React.PropTypes.number,
}),
style: View.propTypes.style,
};
static Styles = NavigatorBreadcrumbNavigationBarStyles;
_updateIndexProgress(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._setPropsIfExists('crumb_' + index, CRUMB_PROPS[index]);
}
if (interpolate.Icon(ICON_PROPS[index].style, amount)) {
this._setPropsIfExists('icon_' + index, ICON_PROPS[index]);
}
if (interpolate.Separator(SEPARATOR_PROPS[index].style, amount)) {
this._setPropsIfExists('separator_' + index, SEPARATOR_PROPS[index]);
}
if (interpolate.Title(TITLE_PROPS[index].style, amount)) {
this._setPropsIfExists('title_' + index, TITLE_PROPS[index]);
}
var right = this.refs['right_' + index];
const rightButtonStyle = RIGHT_BUTTON_PROPS[index].style;
if (right && interpolate.RightItem(rightButtonStyle, amount)) {
right.setNativeProps({
style: rightButtonStyle,
pointerEvents: rightButtonStyle.opacity === 0 ? 'none' : 'auto',
});
}
}
updateProgress(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(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() {
var max = this.props.navState.routeStack.length - 1;
for (var index = 0; index <= max; index++) {
this._setRenderViewsToHardwareTextureAndroid(index, false);
}
}
_setRenderViewsToHardwareTextureAndroid(index, renderToHardwareTexture) {
var props = {
renderToHardwareTextureAndroid: renderToHardwareTexture,
};
this._setPropsIfExists('icon_' + index, props);
this._setPropsIfExists('separator_' + index, props);
this._setPropsIfExists('title_' + index, props);
this._setPropsIfExists('right_' + index, props);
}
componentWillMount() {
this._reset();
}
render() {
var navState = this.props.navState;
var icons = navState && navState.routeStack.map(this._getBreadcrumb);
var titles = navState.routeStack.map(this._getTitle);
var buttons = navState.routeStack.map(this._getRightButton);
return (
{titles}
{icons}
{buttons}
);
}
immediatelyRefresh() {
this._reset();
this.forceUpdate();
}
_reset() {
this._key = guid();
this._descriptors = {
title: new Map(),
right: new Map(),
};
}
_getBreadcrumb = (route, index) => {
/**
* To prevent the case where titles on an empty navigation stack covers the first icon and
* becomes partially unpressable, we set the first breadcrumb to be unpressable by default, and
* make it pressable when there are multiple items in the stack.
*/
const pointerEvents = (
(this.props.navState.routeStack.length <= 1 && index === 0) ?
'none' :
'auto'
);
const navBarRouteMapper = this.props.routeMapper;
const firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
var breadcrumbDescriptor = (
{navBarRouteMapper.iconForRoute(route, this.props.navigator)}
{navBarRouteMapper.separatorForRoute(route, this.props.navigator)}
);
return breadcrumbDescriptor;
};
_getTitle = (route, index) => {
if (this._descriptors.title.has(route)) {
return this._descriptors.title.get(route);
}
var titleContent = this.props.routeMapper.titleContentForRoute(
this.props.navState.routeStack[index],
this.props.navigator
);
var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
var titleDescriptor = (
{titleContent}
);
this._descriptors.title = this._descriptors.title.set(route, titleDescriptor);
return titleDescriptor;
};
_getRightButton = (route, index) => {
if (this._descriptors.right.has(route)) {
return this._descriptors.right.get(route);
}
var rightContent = this.props.routeMapper.rightContentForRoute(
this.props.navState.routeStack[index],
this.props.navigator
);
if (!rightContent) {
this._descriptors.right = this._descriptors.right.set(route, null);
return null;
}
var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
var rightButtonDescriptor = (
{rightContent}
);
this._descriptors.right = this._descriptors.right.set(route, rightButtonDescriptor);
return rightButtonDescriptor;
};
_setPropsIfExists(ref, props) {
var ref = this.refs[ref];
ref && ref.setNativeProps(props);
}
}
const styles = StyleSheet.create({
breadCrumbContainer: {
overflow: 'hidden',
position: 'absolute',
height: NavigatorNavigationBarStyles.General.TotalNavHeight,
top: 0,
left: 0,
right: 0,
},
});
module.exports = NavigatorBreadcrumbNavigationBar;