diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index a15e38bc1..0ad3c4269 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -27,11 +27,14 @@
'use strict';
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
+var BackAndroid = require('BackAndroid');
var Dimensions = require('Dimensions');
var InteractionMixin = require('InteractionMixin');
-var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
-var NavigatorNavigationBar = require('NavigatorNavigationBar');
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
+var NavigatorInterceptor = require('NavigatorInterceptor');
+var NavigatorNavigationBar = require('NavigatorNavigationBar');
+var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
+var NavigatorStaticContextContainer = require('NavigatorStaticContextContainer');
var PanResponder = require('PanResponder');
var React = require('React');
var StaticContainer = require('StaticContainer.react');
@@ -40,6 +43,7 @@ var Subscribable = require('Subscribable');
var TimerMixin = require('react-timer-mixin');
var View = require('View');
+var getNavigatorContext = require('getNavigatorContext');
var clamp = require('clamp');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
@@ -182,12 +186,11 @@ var Navigator = React.createClass({
/**
* Required function which renders the scene for a given route. Will be
- * invoked with the route, the navigator object, and a ref handler that
- * will allow a ref to your scene to be provided by props.onItemRef
+ * invoked with the route and the navigator object
*
* ```
- * (route, navigator, onRef) =>
- *
+ * (route, navigator) =>
+ *
* ```
*/
renderScene: PropTypes.func.isRequired,
@@ -241,10 +244,16 @@ var Navigator = React.createClass({
sceneStyle: View.propTypes.style,
},
+ contextTypes: {
+ navigator: PropTypes.object,
+ },
+
statics: {
BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar,
NavigationBar: NavigatorNavigationBar,
SceneConfigs: NavigatorSceneConfigs,
+ Interceptor: NavigatorInterceptor,
+ getContext: getNavigatorContext,
},
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
@@ -293,7 +302,20 @@ var Navigator = React.createClass({
},
componentWillMount: function() {
- this.navigatorActions = {
+ this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
+ this.navigatorContext = {
+ setHandlerForRoute: this.setHandlerForRoute,
+ request: this.request,
+
+ parentNavigator: this.parentNavigator,
+ getCurrentRoutes: this.getCurrentRoutes,
+ // We want to bubble focused routes to the top navigation stack. If we
+ // are a child navigator, this allows us to call props.navigator.on*Focus
+ // of the topmost Navigator
+ onWillFocus: this.props.onWillFocus,
+ onDidFocus: this.props.onDidFocus,
+
+ // Legacy, imperitive nav actions. Use request when possible.
jumpBack: this.jumpBack,
jumpForward: this.jumpForward,
jumpTo: this.jumpTo,
@@ -307,14 +329,8 @@ var Navigator = React.createClass({
resetTo: this.resetTo,
popToRoute: this.popToRoute,
popToTop: this.popToTop,
- parentNavigator: this.props.navigator,
- getCurrentRoutes: this.getCurrentRoutes,
- // We want to bubble focused routes to the top navigation stack. If we
- // are a child navigator, this allows us to call props.navigator.on*Focus
- // of the topmost Navigator
- onWillFocus: this.props.onWillFocus,
- onDidFocus: this.props.onDidFocus,
};
+ this._handlers = {};
this.panGesture = PanResponder.create({
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
@@ -330,6 +346,46 @@ var Navigator = React.createClass({
this._emitWillFocus(this.state.presentedIndex);
},
+ request: function(action, arg1, arg2) {
+ if (this.parentNavigator) {
+ return this.parentNavigator.request.apply(null, arguments);
+ }
+ return this._handleRequest.apply(null, arguments);
+ },
+
+ _handleRequest: function(action, arg1, arg2) {
+ var childHandler = this._handlers[this.state.presentedIndex];
+ if (childHandler && childHandler(action, arg1, arg2)) {
+ return true;
+ }
+ switch (action) {
+ case 'pop':
+ return this._handlePop();
+ case 'push':
+ return this._handlePush(arg1);
+ default:
+ invariant(false, 'Unsupported request type ' + action);
+ return false;
+ }
+ },
+
+ _handlePop: function() {
+ if (this.state.presentedIndex === 0) {
+ return false;
+ }
+ this.pop();
+ return true;
+ },
+
+ _handlePush: function(route) {
+ this.push(route);
+ return true;
+ },
+
+ setHandlerForRoute: function(route, handler) {
+ this._handlers[this.state.routeStack.indexOf(route)] = handler;
+ },
+
_configureSpring: function(animationConfig) {
var config = this.spring.getSpringConfig();
config.friction = animationConfig.springFriction;
@@ -345,6 +401,28 @@ var Navigator = React.createClass({
this.spring.addListener(this);
this.onSpringUpdate();
this._emitDidFocus(this.state.presentedIndex);
+ if (this.parentNavigator) {
+ this.parentNavigator.setHandler(this._handleRequest);
+ } else {
+ // There is no navigator in our props or context, so this is the
+ // top-level navigator. We will handle back button presses here
+ BackAndroid.addEventListener('hardwareBackPress', this._handleBackPress);
+ }
+ },
+
+ componentWillUnmount: function() {
+ if (this.parentNavigator) {
+ this.parentNavigator.setHandler(null);
+ } else {
+ BackAndroid.removeEventListener('hardwareBackPress', this._handleBackPress);
+ }
+ },
+
+ _handleBackPress: function() {
+ var didPop = this.request('pop');
+ if (!didPop) {
+ BackAndroid.exitApp();
+ }
},
/**
@@ -791,8 +869,9 @@ var Navigator = React.createClass({
},
pop: function() {
- if (this.props.navigator && this.state.routeStack.length === 1) {
- return this.props.navigator.pop();
+ // TODO (t6707686): remove this parentNavigator call after transitioning call sites to `.request('pop')`
+ if (this.parentNavigator && this.state.routeStack.length === 1) {
+ return this.parentNavigator.pop();
}
this.popN(1);
},
@@ -889,7 +968,7 @@ var Navigator = React.createClass({
return this.state.routeStack;
},
- _onItemRef: function(itemId, ref) {
+ _handleItemRef: function(itemId, ref) {
this._itemRefs[itemId] = ref;
var itemIndex = this.state.idStack.indexOf(itemId);
if (itemIndex === -1) {
@@ -922,23 +1001,33 @@ var Navigator = React.createClass({
this.state.updatingRangeLength !== 0 &&
i >= this.state.updatingRangeStart &&
i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
+ var sceneNavigatorContext = {
+ ...this.navigatorContext,
+ route,
+ setHandler: (handler) => {
+ this.navigatorContext.setHandlerForRoute(route, handler);
+ },
+ };
var child = this.props.renderScene(
route,
- this.navigatorActions,
- this._onItemRef.bind(null, this.state.idStack[i])
+ sceneNavigatorContext
);
-
var initialSceneStyle =
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
return (
-
+
- {child}
+ {React.cloneElement(child, {
+ ref: this._handleItemRef.bind(null, this.state.idStack[i]),
+ })}
-
+
);
},
@@ -967,7 +1056,7 @@ var Navigator = React.createClass({
}
return React.cloneElement(this.props.navigationBar, {
ref: (navBar) => { this._navBar = navBar; },
- navigator: this.navigatorActions,
+ navigator: this.navigatorContext,
navState: this.state,
});
},
diff --git a/Libraries/CustomComponents/Navigator/NavigatorInterceptor.js b/Libraries/CustomComponents/Navigator/NavigatorInterceptor.js
new file mode 100644
index 000000000..dcc5d43ef
--- /dev/null
+++ b/Libraries/CustomComponents/Navigator/NavigatorInterceptor.js
@@ -0,0 +1,95 @@
+/**
+ * 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 NavigatorInterceptor
+ */
+'use strict';
+
+var React = require('React');
+
+var getNavigatorContext = require('getNavigatorContext');
+
+var NavigatorInterceptor = React.createClass({
+
+ contextTypes: {
+ navigator: React.PropTypes.object,
+ },
+
+ componentWillMount: function() {
+ this.navigator = getNavigatorContext(this);
+ },
+
+ componentDidMount: function() {
+ this.navigator.setHandler(this._navigatorHandleRequest);
+ },
+
+ childContextTypes: {
+ navigator: React.PropTypes.object,
+ },
+
+ getChildContext: function() {
+ return {
+ navigator: {
+ ...this.navigator,
+ setHandler: (handler) => {
+ this._childNavigationHandler = handler;
+ },
+ }
+ };
+ },
+
+ componentWillUnmount: function() {
+ this.navigator.setHandler(null);
+ },
+
+ _navigatorHandleRequest: function(action, arg1, arg2) {
+ if (this._interceptorHandle(action, arg1, arg2)) {
+ return true;
+ }
+ if (this._childNavigationHandler && this._childNavigationHandler(action, arg1, arg2)) {
+ return true;
+ }
+ },
+
+ _interceptorHandle: function(action, arg1, arg2) {
+ if (this.props.onRequest && this.props.onRequest(action, arg1, arg2)) {
+ return true;
+ }
+ switch (action) {
+ case 'pop':
+ return this.props.onPopRequest && this.props.onPopRequest(action, arg1, arg2);
+ case 'push':
+ return this.props.onPushRequest && this.props.onPushRequest(action, arg1, arg2);
+ default:
+ return false;
+ }
+ },
+
+ render: function() {
+ return this.props.children;
+ },
+
+});
+
+module.exports = NavigatorInterceptor;
diff --git a/Libraries/CustomComponents/Navigator/NavigatorStaticContextContainer.js b/Libraries/CustomComponents/Navigator/NavigatorStaticContextContainer.js
new file mode 100644
index 000000000..ded8048e4
--- /dev/null
+++ b/Libraries/CustomComponents/Navigator/NavigatorStaticContextContainer.js
@@ -0,0 +1,53 @@
+/**
+ * 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 NavigatorStaticContextContainer
+ */
+'use strict';
+
+var React = require('React');
+var StaticContainer = require('StaticContainer.react');
+
+var PropTypes = React.PropTypes;
+
+var NavigatorStaticContextContainer = React.createClass({
+
+ childContextTypes: {
+ navigator: PropTypes.object,
+ },
+
+ getChildContext: function() {
+ return {
+ navigator: this.props.navigatorContext,
+ };
+ },
+
+ render: function() {
+ return (
+
+ );
+ },
+});
+
+module.exports = NavigatorStaticContextContainer;
diff --git a/Libraries/CustomComponents/Navigator/getNavigatorContext.js b/Libraries/CustomComponents/Navigator/getNavigatorContext.js
new file mode 100644
index 000000000..0c169fe0f
--- /dev/null
+++ b/Libraries/CustomComponents/Navigator/getNavigatorContext.js
@@ -0,0 +1,37 @@
+/**
+ * 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 getNavigatorContext
+ */
+'use strict';
+
+
+var ReactInstanceMap = require('ReactInstanceMap');
+
+function getNavigatorContext(el) {
+ // TODO (t6707746): replace with `el.context.navigator` when parent context is supported
+ return ReactInstanceMap.get(el)._context.navigator;
+}
+
+module.exports = getNavigatorContext;