mirror of
https://github.com/status-im/react-native.git
synced 2025-02-14 10:26:33 +00:00
Summary: Adds the API that enables the navigation events capturing and bubbling which is the feature that is enabled if the nested navigation contexts is created by the navigator. This would allow developer to observe or reconcile navigation events within the navigation tree. public ./Libraries/FBReactKit/jest Reviewed By: zjj010104 Differential Revision: D2546451 fb-gh-sync-id: dfc9d16defaa563b9e80fd751a20570f6e524b74
224 lines
6.5 KiB
JavaScript
224 lines
6.5 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 NavigationContext
|
|
*/
|
|
'use strict';
|
|
|
|
var NavigationEvent = require('NavigationEvent');
|
|
var NavigationEventEmitter = require('NavigationEventEmitter');
|
|
var NavigationTreeNode = require('NavigationTreeNode');
|
|
|
|
var emptyFunction = require('emptyFunction');
|
|
var invariant = require('invariant');
|
|
|
|
import type * as EventSubscription from 'EventSubscription';
|
|
|
|
var {
|
|
AT_TARGET,
|
|
BUBBLING_PHASE,
|
|
CAPTURING_PHASE,
|
|
} = NavigationEvent;
|
|
|
|
/**
|
|
* Class that contains the info and methods for app navigation.
|
|
*/
|
|
class NavigationContext {
|
|
__node: NavigationTreeNode;
|
|
_bubbleEventEmitter: ?NavigationEventEmitter;
|
|
_captureEventEmitter: ?NavigationEventEmitter;
|
|
_currentRoute: any;
|
|
_emitCounter: number;
|
|
_emitQueue: Array<any>;
|
|
|
|
constructor() {
|
|
this._bubbleEventEmitter = new NavigationEventEmitter(this);
|
|
this._captureEventEmitter = new NavigationEventEmitter(this);
|
|
this._currentRoute = null;
|
|
|
|
// Sets the protected property `__node`.
|
|
this.__node = new NavigationTreeNode(this);
|
|
|
|
this._emitCounter = 0;
|
|
this._emitQueue = [];
|
|
|
|
this.addListener('willfocus', this._onFocus, this);
|
|
this.addListener('didfocus', this._onFocus, this);
|
|
}
|
|
|
|
/* $FlowFixMe - get/set properties not yet supported */
|
|
get parent(): ?NavigationContext {
|
|
var parent = this.__node.getParent();
|
|
return parent ? parent.getValue() : null;
|
|
}
|
|
|
|
/* $FlowFixMe - get/set properties not yet supported */
|
|
get currentRoute(): any {
|
|
return this._currentRoute;
|
|
}
|
|
|
|
appendChild(childContext: NavigationContext): void {
|
|
this.__node.appendChild(childContext.__node);
|
|
}
|
|
|
|
addListener(
|
|
eventType: string,
|
|
listener: Function,
|
|
context: ?Object,
|
|
useCapture: ?boolean
|
|
): EventSubscription {
|
|
var emitter = useCapture ?
|
|
this._captureEventEmitter :
|
|
this._bubbleEventEmitter;
|
|
if (emitter) {
|
|
return emitter.addListener(eventType, listener, context);
|
|
} else {
|
|
return {remove: emptyFunction};
|
|
}
|
|
}
|
|
|
|
emit(eventType: String, data: any, didEmitCallback: ?Function): void {
|
|
if (this._emitCounter > 0) {
|
|
// An event cycle that was previously created hasn't finished yet.
|
|
// Put this event cycle into the queue and will finish them later.
|
|
var args: any = Array.prototype.slice.call(arguments);
|
|
this._emitQueue.push(args);
|
|
return;
|
|
}
|
|
|
|
this._emitCounter++;
|
|
|
|
var targets = [this];
|
|
var parentTarget = this.parent;
|
|
while (parentTarget) {
|
|
targets.unshift(parentTarget);
|
|
parentTarget = parentTarget.parent;
|
|
}
|
|
|
|
var propagationStopped = false;
|
|
var defaultPrevented = false;
|
|
var callback = (event) => {
|
|
propagationStopped = propagationStopped || event.isPropagationStopped();
|
|
defaultPrevented = defaultPrevented || event.defaultPrevented;
|
|
};
|
|
|
|
// capture phase
|
|
targets.some((currentTarget) => {
|
|
if (propagationStopped) {
|
|
return true;
|
|
}
|
|
|
|
var extraInfo = {
|
|
defaultPrevented,
|
|
eventPhase: CAPTURING_PHASE,
|
|
propagationStopped,
|
|
target: this,
|
|
};
|
|
|
|
currentTarget.__emit(eventType, data, callback, extraInfo);
|
|
}, this);
|
|
|
|
// bubble phase
|
|
targets.reverse().some((currentTarget) => {
|
|
if (propagationStopped) {
|
|
return true;
|
|
}
|
|
var extraInfo = {
|
|
defaultPrevented,
|
|
eventPhase: BUBBLING_PHASE,
|
|
propagationStopped,
|
|
target: this,
|
|
};
|
|
currentTarget.__emit(eventType, data, callback, extraInfo);
|
|
}, this);
|
|
|
|
if (didEmitCallback) {
|
|
var event = NavigationEvent.pool(eventType, this, data);
|
|
propagationStopped && event.stopPropagation();
|
|
defaultPrevented && event.preventDefault();
|
|
didEmitCallback.call(this, event);
|
|
event.dispose();
|
|
}
|
|
|
|
this._emitCounter--;
|
|
while (this._emitQueue.length) {
|
|
var args: any = this._emitQueue.shift();
|
|
this.emit.apply(this, args);
|
|
}
|
|
}
|
|
|
|
dispose(): void {
|
|
// clean up everything.
|
|
this._bubbleEventEmitter && this._bubbleEventEmitter.removeAllListeners();
|
|
this._captureEventEmitter && this._captureEventEmitter.removeAllListeners();
|
|
this._bubbleEventEmitter = null;
|
|
this._captureEventEmitter = null;
|
|
this._currentRoute = null;
|
|
}
|
|
|
|
// This method `__method` is protected.
|
|
__emit(
|
|
eventType: String,
|
|
data: any,
|
|
didEmitCallback: ?Function,
|
|
extraInfo: Object,
|
|
): void {
|
|
var emitter;
|
|
switch (extraInfo.eventPhase) {
|
|
case CAPTURING_PHASE: // phase = 1
|
|
emitter = this._captureEventEmitter;
|
|
break;
|
|
case BUBBLING_PHASE: // phase = 3
|
|
emitter = this._bubbleEventEmitter;
|
|
break;
|
|
default:
|
|
invariant(false, 'invalid event phase %s', extraInfo.eventPhase);
|
|
}
|
|
|
|
if (extraInfo.target === this) {
|
|
// phase = 2
|
|
extraInfo.eventPhase = AT_TARGET;
|
|
}
|
|
|
|
if (emitter) {
|
|
emitter.emit(
|
|
eventType,
|
|
data,
|
|
didEmitCallback,
|
|
extraInfo
|
|
);
|
|
}
|
|
}
|
|
|
|
_onFocus(event: NavigationEvent): void {
|
|
invariant(
|
|
event.data && event.data.hasOwnProperty('route'),
|
|
'didfocus event should provide route'
|
|
);
|
|
this._currentRoute = event.data.route;
|
|
}
|
|
}
|
|
|
|
module.exports = NavigationContext;
|