mirror of
https://github.com/facebook/react.git
synced 2026-02-22 20:01:52 +00:00
Simplify Event Core
Summary: This makes a few changes to React Core, most notably `ReactEventEmitter` and `ReactEventTopLevelCallback`. - Changed `ReactEventEmitter` to use `EventListener` (instead of `NormalizedEventListener`). - Deleted `NormalizedEventListener` (which was previously broken). - Created `getEventTarget` which is used to get a normalized `target` from a native event. - Changed `ReactEventTopLevelCallback` to use `getEventTarget`. - Renamed `abstractEventType` to `reactEventType` in `AbstractEvent`. - Reanmed `abstractTargetID` to `reactTargetID` in `AbstractEvent`. - Removed `originatingTopLevelEventType` from `AbstractEvent` (unused and violates encapsulation). - Removed `nativeEvent.target === window` check when refreshing authoritative scroll values (unnecessary). This actually fixes React because `NormalizedEventListener` does not currently do what it promises to do (which is normalizing `target` on the native event). The `target` event is read-only on native events. This also revises documentation and adds `@typechecks` to a few modules. NOTE: Most importantly, this sets the stage for replacing `AbstractEvent` with `ReactEvent` and subclasses, piecemeal.
This commit is contained in:
committed by
Paul O’Shannessy
parent
36d8ce8fab
commit
ba6fea1bf5
@@ -14,33 +14,44 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactEventEmitter
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var BrowserEnv = require('BrowserEnv');
|
||||
var EventConstants = require('EventConstants');
|
||||
var EventListener = require('EventListener');
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var NormalizedEventListener = require('NormalizedEventListener');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var isEventSupported = require('isEventSupported');
|
||||
|
||||
var registrationNames = EventPluginHub.registrationNames;
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
var listen = NormalizedEventListener.listen;
|
||||
var capture = NormalizedEventListener.capture;
|
||||
|
||||
/**
|
||||
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
|
||||
* Summary of `ReactEventEmitter` event handling:
|
||||
*
|
||||
* ReactEventEmitter.putListener('myID', 'onClick', myFunction);
|
||||
* - We trap low level 'top-level' events.
|
||||
*
|
||||
* - We dedupe cross-browser event names into these 'top-level types' so that
|
||||
* `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
|
||||
*
|
||||
* - At this point we have native browser events with the top-level type that
|
||||
* was used to catch it at the top-level.
|
||||
*
|
||||
* - We continuously stream these native events (and their respective top-level
|
||||
* types) to the event plugin system `EventPluginHub` and ask the plugin
|
||||
* system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
|
||||
* objects are the events that applications actually deal with - they are not
|
||||
* native browser events but cross-browser wrappers.
|
||||
*
|
||||
* - When returning the `AbstractEvent` objects, `EventPluginHub` will make
|
||||
* sure each abstract event is annotated with "dispatches", which are the
|
||||
* sequence of listeners (and IDs) that care about the event.
|
||||
*
|
||||
* - These `AbstractEvent` objects are fed back into the event plugin system,
|
||||
* which in turn executes these dispatches.
|
||||
*
|
||||
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Overview of React and the event system:
|
||||
*
|
||||
* .
|
||||
@@ -71,6 +82,64 @@ var capture = NormalizedEventListener.capture;
|
||||
* React Core . General Purpose Event Plugin System
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether or not `ensureListening` has been invoked.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
var _isListening = false;
|
||||
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {DOMEventTarget} element Element on which to attach listener.
|
||||
* @internal
|
||||
*/
|
||||
function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
EventListener.listen(
|
||||
element,
|
||||
handlerBaseName,
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traps a top-level event by using event capturing.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {DOMEventTarget} element Element on which to attach listener.
|
||||
* @internal
|
||||
*/
|
||||
function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
EventListener.capture(
|
||||
element,
|
||||
handlerBaseName,
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to window scroll and resize events. We cache scroll values so that
|
||||
* application code can access them without triggering reflows.
|
||||
*
|
||||
* NOTE: Scroll events do not bubble.
|
||||
*
|
||||
* @private
|
||||
* @see http://www.quirksmode.org/dom/events/scroll.html
|
||||
*/
|
||||
function registerScrollValueMonitoring() {
|
||||
var refresh = BrowserEnv.refreshAuthoritativeScrollValues;
|
||||
EventListener.listen(window, 'scroll', refresh);
|
||||
EventListener.listen(window, 'resize', refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* We listen for bubbled touch events on the document object.
|
||||
*
|
||||
@@ -89,96 +158,19 @@ var capture = NormalizedEventListener.capture;
|
||||
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
|
||||
* they bubble to document.
|
||||
*
|
||||
* @see http://www.quirksmode.org/dom/events/keys.html.
|
||||
*/
|
||||
|
||||
var _isListening = false;
|
||||
|
||||
/**
|
||||
* Traps top-level events that bubble. Delegates to the main dispatcher
|
||||
* `handleTopLevel` after performing some basic normalization via
|
||||
* `TopLevelCallbackCreator.createTopLevelCallback`.
|
||||
*/
|
||||
function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
|
||||
listen(
|
||||
onWhat,
|
||||
handlerBaseName,
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traps a top-level event by using event capturing.
|
||||
*/
|
||||
function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
|
||||
capture(
|
||||
onWhat,
|
||||
handlerBaseName,
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to document scroll and window resize events that may change the
|
||||
* document scroll values. We store those results so as to discourage
|
||||
* application code from asking the DOM itself which could trigger additional
|
||||
* reflows.
|
||||
*/
|
||||
function registerDocumentScrollListener() {
|
||||
listen(window, 'scroll', function(nativeEvent) {
|
||||
if (nativeEvent.target === window) {
|
||||
BrowserEnv.refreshAuthoritativeScrollValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerDocumentResizeListener() {
|
||||
listen(window, 'resize', function(nativeEvent) {
|
||||
if (nativeEvent.target === window) {
|
||||
BrowserEnv.refreshAuthoritativeScrollValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of `ReactEventEmitter` event handling:
|
||||
*
|
||||
* - We trap low level 'top-level' events.
|
||||
*
|
||||
* - We dedupe cross-browser event names into these 'top-level types' so that
|
||||
* `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
|
||||
*
|
||||
* - At this point we have native browser events with the top-level type that
|
||||
* was used to catch it at the top-level.
|
||||
*
|
||||
* - We continuously stream these native events (and their respective top-level
|
||||
* types) to the event plugin system `EventPluginHub` and ask the plugin
|
||||
* system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
|
||||
* objects are the events that applications actually deal with - they are not
|
||||
* native browser events but cross-browser wrappers.
|
||||
*
|
||||
* - When returning the `AbstractEvent` objects, `EventPluginHub` will make
|
||||
* sure each abstract event is annotated with "dispatches", which are the
|
||||
* sequence of listeners (and IDs) that care about the event.
|
||||
*
|
||||
* - These `AbstractEvent` objects are fed back into the event plugin system,
|
||||
* which in turn executes these dispatches.
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @private
|
||||
* @see http://www.quirksmode.org/dom/events/keys.html.
|
||||
*/
|
||||
function listenAtTopLevel(touchNotMouse) {
|
||||
invariant(
|
||||
!_isListening,
|
||||
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
|
||||
);
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
var mountAt = document;
|
||||
|
||||
registerDocumentScrollListener();
|
||||
registerDocumentResizeListener();
|
||||
registerScrollValueMonitoring();
|
||||
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
|
||||
@@ -226,82 +218,111 @@ function listenAtTopLevel(touchNotMouse) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the heart of `ReactEventEmitter`. It simply streams the top-level
|
||||
* native events to `EventPluginHub`.
|
||||
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
|
||||
*
|
||||
* ReactEventEmitter.putListener('myID', 'onClick', myFunction);
|
||||
*
|
||||
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
|
||||
*
|
||||
* @param {object} topLevelType Record from `EventConstants`.
|
||||
* @param {Event} nativeEvent A Standard Event with fixed `target` property.
|
||||
* @param {DOMElement} renderedTarget Element of interest to the framework.
|
||||
* @param {string} renderedTargetID string ID of `renderedTarget`.
|
||||
* @internal
|
||||
*/
|
||||
function handleTopLevel(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget) {
|
||||
var abstractEvents = EventPluginHub.extractAbstractEvents(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget
|
||||
);
|
||||
|
||||
// The event queue being processed in the same cycle allows preventDefault.
|
||||
EventPluginHub.enqueueAbstractEvents(abstractEvents);
|
||||
EventPluginHub.processAbstractEventQueue();
|
||||
}
|
||||
|
||||
function setEnabled(enabled) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'setEnabled(...): Cannot toggle event listening in a Worker thread. This ' +
|
||||
'is likely a bug in the framework. Please report immediately.'
|
||||
);
|
||||
ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
|
||||
}
|
||||
|
||||
function isEnabled() {
|
||||
return ReactEventEmitter.TopLevelCallbackCreator.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that top-level event delegation listeners are listening at `mountAt`.
|
||||
* There are issues with listening to both touch events and mouse events on the
|
||||
* top-level, so we make the caller choose which one to listen to. (If there's a
|
||||
* touch top-level listeners, anchors don't receive clicks for some reason, and
|
||||
* only in some cases).
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @param {object} TopLevelCallbackCreator Module that can create top-level
|
||||
* callback handlers.
|
||||
* @internal
|
||||
*/
|
||||
function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'ensureListening(...): Cannot toggle event listening in a Worker thread. ' +
|
||||
'This is likely a bug in the framework. Please report immediately.'
|
||||
);
|
||||
if (!_isListening) {
|
||||
ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
|
||||
listenAtTopLevel(touchNotMouse);
|
||||
_isListening = true;
|
||||
}
|
||||
}
|
||||
|
||||
var ReactEventEmitter = {
|
||||
TopLevelCallbackCreator: null, // Injectable callback creator.
|
||||
handleTopLevel: handleTopLevel,
|
||||
setEnabled: setEnabled,
|
||||
isEnabled: isEnabled,
|
||||
ensureListening: ensureListening,
|
||||
registrationNames: registrationNames,
|
||||
|
||||
/**
|
||||
* React references `ReactEventTopLevelCallback` using this property in order
|
||||
* to allow dependency injection via `ensureListening`.
|
||||
*/
|
||||
TopLevelCallbackCreator: null,
|
||||
|
||||
/**
|
||||
* Ensures that top-level event delegation listeners are installed.
|
||||
*
|
||||
* There are issues with listening to both touch events and mouse events on
|
||||
* the top-level, so we make the caller choose which one to listen to. (If
|
||||
* there's a touch top-level listeners, anchors don't receive clicks for some
|
||||
* reason, and only in some cases).
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @param {object} TopLevelCallbackCreator
|
||||
*/
|
||||
ensureListening: function(touchNotMouse, TopLevelCallbackCreator) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'ensureListening(...): Cannot toggle event listening in a Worker ' +
|
||||
'thread. This is likely a bug in the framework. Please report ' +
|
||||
'immediately.'
|
||||
);
|
||||
if (!_isListening) {
|
||||
ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
|
||||
listenAtTopLevel(touchNotMouse);
|
||||
_isListening = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets whether or not any created callbacks should be enabled.
|
||||
*
|
||||
* @param {boolean} enabled True if callbacks should be enabled.
|
||||
*/
|
||||
setEnabled: function(enabled) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
|
||||
'This is likely a bug in the framework. Please report immediately.'
|
||||
);
|
||||
if (ReactEventEmitter.TopLevelCallbackCreator) {
|
||||
ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean} True if callbacks are enabled.
|
||||
*/
|
||||
isEnabled: function() {
|
||||
return !!(
|
||||
ReactEventEmitter.TopLevelCallbackCreator &&
|
||||
ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Streams a fired top-level event to `EventPluginHub` where plugins have the
|
||||
* opportunity to create `ReactEvent`s to be dispatched.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
*/
|
||||
handleTopLevel: function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
var abstractEvents = EventPluginHub.extractAbstractEvents(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
|
||||
// Event queue being processed in the same cycle allows `preventDefault`.
|
||||
EventPluginHub.enqueueAbstractEvents(abstractEvents);
|
||||
EventPluginHub.processAbstractEventQueue();
|
||||
},
|
||||
|
||||
registrationNames: EventPluginHub.registrationNames,
|
||||
|
||||
putListener: EventPluginHub.putListener,
|
||||
|
||||
getListener: EventPluginHub.getListener,
|
||||
|
||||
deleteAllListeners: EventPluginHub.deleteAllListeners,
|
||||
|
||||
trapBubbledEvent: trapBubbledEvent,
|
||||
|
||||
trapCapturedEvent: trapCapturedEvent
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactEventEmitter;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactEventTopLevelCallback
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -23,51 +24,59 @@ var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
|
||||
var getDOMNodeID = require('getDOMNodeID');
|
||||
var getEventTarget = require('getEventTarget');
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
var _topLevelListenersEnabled = true;
|
||||
|
||||
/**
|
||||
* Top-level callback creator used to implement event handling using delegation.
|
||||
* This is used via dependency injection in `ReactEventEmitter.ensureListening`.
|
||||
*/
|
||||
var ReactEventTopLevelCallback = {
|
||||
|
||||
/**
|
||||
* @param {boolean} enabled Whether or not all callbacks that have ever been
|
||||
* created with this module should be enabled.
|
||||
* Sets whether or not any created callbacks should be enabled.
|
||||
*
|
||||
* @param {boolean} enabled True if callbacks should be enabled.
|
||||
*/
|
||||
setEnabled: function(enabled) {
|
||||
_topLevelListenersEnabled = !!enabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean} True if callbacks are enabled.
|
||||
*/
|
||||
isEnabled: function() {
|
||||
return _topLevelListenersEnabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* For a given `topLevelType`, creates a callback that could be added as a
|
||||
* listener to the document. That top level callback will simply fix the
|
||||
* native events before invoking `handleTopLevel`.
|
||||
* Creates a callback for the supplied `topLevelType` that could be added as
|
||||
* a listener to the document. The callback computes a `topLevelTarget` which
|
||||
* should be the root node of a mounted React component where the listener
|
||||
* is attached.
|
||||
*
|
||||
* - Raw native events cannot be trusted to describe their targets correctly
|
||||
* so we expect that the argument to the nested function has already been
|
||||
* fixed. But the `target` property may not be something of interest to
|
||||
* React, so we find the most suitable target. But even at that point, DOM
|
||||
* Elements (the target ) can't be trusted to describe their IDs correctly
|
||||
* so we obtain the ID in a reliable manner and pass it to
|
||||
* `handleTopLevel`. The target/id that we found to be relevant to our
|
||||
* framework are called `renderedTarget`/`renderedTargetID` respectively.
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @return {function} Callback for handling top-level events.
|
||||
*/
|
||||
createTopLevelCallback: function(topLevelType) {
|
||||
return function(fixedNativeEvent) {
|
||||
return function(nativeEvent) {
|
||||
if (!_topLevelListenersEnabled) {
|
||||
return;
|
||||
}
|
||||
var renderedTarget = ReactInstanceHandles.getFirstReactDOM(
|
||||
fixedNativeEvent.target
|
||||
var topLevelTarget = ReactInstanceHandles.getFirstReactDOM(
|
||||
getEventTarget(nativeEvent)
|
||||
) || ExecutionEnvironment.global;
|
||||
var renderedTargetID = getDOMNodeID(renderedTarget);
|
||||
var topLevelTargetID = getDOMNodeID(topLevelTarget) || '';
|
||||
ReactEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
fixedNativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactInstanceHandles
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -57,13 +58,13 @@ function isValidID(id) {
|
||||
/**
|
||||
* True if the supplied `node` is rendered by React.
|
||||
*
|
||||
* @param {DOMElement} node DOM Element to check.
|
||||
* @param {DOMEventTarget} node DOM Element to check.
|
||||
* @return {boolean} True if the DOM Element appears to be rendered by React.
|
||||
* @private
|
||||
*/
|
||||
function isRenderedByReact(node) {
|
||||
var id = getDOMNodeID(node);
|
||||
return id && id.charAt(0) === SEPARATOR;
|
||||
return id ? id.charAt(0) === SEPARATOR : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,8 +141,8 @@ var ReactInstanceHandles = {
|
||||
* Traverses up the ancestors of the supplied node to find a node that is a
|
||||
* DOM representation of a React component.
|
||||
*
|
||||
* @param {DOMElement} node
|
||||
* @return {?DOMElement}
|
||||
* @param {?DOMEventTarget} node
|
||||
* @return {?DOMEventTarget}
|
||||
* @internal
|
||||
*/
|
||||
getFirstReactDOM: function(node) {
|
||||
@@ -159,9 +160,9 @@ var ReactInstanceHandles = {
|
||||
* Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
|
||||
* Exploits the ID naming scheme to perform the search quickly.
|
||||
*
|
||||
* @param {DOMElement} ancestorNode Search from this root.
|
||||
* @param {DOMEventTarget} ancestorNode Search from this root.
|
||||
* @pararm {string} id ID of the DOM representation of the component.
|
||||
* @return {?DOMElement} DOM element with the supplied `id`, if one exists.
|
||||
* @return {?DOMEventTarget} DOM node with the supplied `id`, if one exists.
|
||||
* @internal
|
||||
*/
|
||||
findComponentRoot: function(ancestorNode, id) {
|
||||
@@ -228,7 +229,7 @@ var ReactInstanceHandles = {
|
||||
* contains the React component with the supplied DOM ID.
|
||||
*
|
||||
* @param {string} id DOM ID of a React component.
|
||||
* @return {string} DOM ID of the React component that is the root.
|
||||
* @return {?string} DOM ID of the React component that is the root.
|
||||
* @internal
|
||||
*/
|
||||
getReactRootIDFromNodeID: function(id) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule getDOMNodeID
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -23,8 +24,8 @@
|
||||
* control whose name or ID is "id". However, not all DOM nodes support
|
||||
* `getAttributeNode` (document - which is not a form) so that is checked first.
|
||||
*
|
||||
* @param {Element} domNode DOM node element to return ID of.
|
||||
* @returns {string} The ID of `domNode`.
|
||||
* @param {DOMElement|DOMWindow|DOMDocument} domNode DOM node.
|
||||
* @returns {string} ID of the supplied `domNode`.
|
||||
*/
|
||||
function getDOMNodeID(domNode) {
|
||||
if (domNode.getAttributeNode) {
|
||||
|
||||
39
src/dom/getEventTarget.js
Normal file
39
src/dom/getEventTarget.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule getEventTarget
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
/**
|
||||
* Gets the target node from a native browser event by accounting for
|
||||
* inconsistencies in browser DOM APIs.
|
||||
*
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {DOMEventTarget} Target node.
|
||||
*/
|
||||
function getEventTarget(nativeEvent) {
|
||||
var target =
|
||||
nativeEvent.target ||
|
||||
nativeEvent.srcElement ||
|
||||
ExecutionEnvironment.global;
|
||||
// Safari may fire events on text nodes (Node.TEXT_NODE is 3).
|
||||
// @see http://www.quirksmode.org/js/events_properties.html
|
||||
return target.nodeType === 3 ? target.parentNode : target;
|
||||
}
|
||||
|
||||
module.exports = getEventTarget;
|
||||
@@ -44,14 +44,12 @@ var MAX_POOL_SIZE = 20;
|
||||
* unreliable native event.
|
||||
*/
|
||||
function AbstractEvent(
|
||||
abstractEventType,
|
||||
abstractTargetID, // Allows the abstract target to differ from native.
|
||||
originatingTopLevelEventType,
|
||||
reactEventType,
|
||||
reactTargetID, // Allows the abstract target to differ from native.
|
||||
nativeEvent,
|
||||
data) {
|
||||
this.type = abstractEventType;
|
||||
this.abstractTargetID = abstractTargetID || '';
|
||||
this.originatingTopLevelEventType = originatingTopLevelEventType;
|
||||
this.reactEventType = reactEventType;
|
||||
this.reactTargetID = reactTargetID || '';
|
||||
this.nativeEvent = nativeEvent;
|
||||
this.data = data;
|
||||
// TODO: Deprecate storing target - doesn't always make sense for some types
|
||||
@@ -263,12 +261,10 @@ AbstractEvent.persistentCloneOf = function(abstractEvent) {
|
||||
throwIf(!(abstractEvent instanceof AbstractEvent), CLONE_TYPE_ERR);
|
||||
}
|
||||
return new AbstractEvent(
|
||||
abstractEvent.type,
|
||||
abstractEvent.abstractTargetID,
|
||||
abstractEvent.originatingTopLevelEventType,
|
||||
abstractEvent.reactEventType,
|
||||
abstractEvent.reactTargetID,
|
||||
abstractEvent.nativeEvent,
|
||||
abstractEvent.data,
|
||||
abstractEvent.target
|
||||
abstractEvent.data
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -194,15 +194,16 @@ function recordAllRegistrationNames(eventType, PluginModule) {
|
||||
* @param {AbstractEvent} abstractEvent to look at
|
||||
*/
|
||||
function getPluginModuleForAbstractEvent(abstractEvent) {
|
||||
if (abstractEvent.type.registrationName) {
|
||||
return registrationNames[abstractEvent.type.registrationName];
|
||||
var reactEventType = abstractEvent.reactEventType;
|
||||
if (reactEventType.registrationName) {
|
||||
return registrationNames[reactEventType.registrationName];
|
||||
} else {
|
||||
for (var phase in abstractEvent.type.phasedRegistrationNames) {
|
||||
if (!abstractEvent.type.phasedRegistrationNames.hasOwnProperty(phase)) {
|
||||
for (var phase in reactEventType.phasedRegistrationNames) {
|
||||
if (!reactEventType.phasedRegistrationNames.hasOwnProperty(phase)) {
|
||||
continue;
|
||||
}
|
||||
var PluginModule = registrationNames[
|
||||
abstractEvent.type.phasedRegistrationNames[phase]
|
||||
reactEventType.phasedRegistrationNames[phase]
|
||||
];
|
||||
if (PluginModule) {
|
||||
return PluginModule;
|
||||
@@ -223,36 +224,36 @@ var deleteAllListeners = function(domID) {
|
||||
* Accepts the stream of top level native events, and gives every registered
|
||||
* plugin an opportunity to extract `AbstractEvent`s with annotated dispatches.
|
||||
*
|
||||
* @param {Enum} topLevelType Record from `EventConstants`.
|
||||
* @param {Event} nativeEvent A Standard Event with fixed `target` property.
|
||||
* @param {Element} renderedTarget Element of interest to the framework, usually
|
||||
* the same as `nativeEvent.target` but occasionally an element immediately
|
||||
* above `nativeEvent.target` (the first DOM node recognized as one "rendered"
|
||||
* by the framework at hand.)
|
||||
* @param {string} renderedTargetID string ID of `renderedTarget`.
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
*/
|
||||
var extractAbstractEvents =
|
||||
function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
|
||||
var abstractEvents;
|
||||
var plugins = injection.plugins;
|
||||
var len = plugins.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
// Not every plugin in the ordering may be loaded at runtime.
|
||||
var possiblePlugin = plugins[i];
|
||||
var extractedAbstractEvents =
|
||||
possiblePlugin &&
|
||||
possiblePlugin.extractAbstractEvents(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget
|
||||
);
|
||||
var extractAbstractEvents = function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
var abstractEvents;
|
||||
var plugins = injection.plugins;
|
||||
for (var i = 0, l = plugins.length; i < l; i++) {
|
||||
// Not every plugin in the ordering may be loaded at runtime.
|
||||
var possiblePlugin = plugins[i];
|
||||
if (possiblePlugin) {
|
||||
var extractedAbstractEvents = possiblePlugin.extractAbstractEvents(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
if (extractedAbstractEvents) {
|
||||
abstractEvents = accumulate(abstractEvents, extractedAbstractEvents);
|
||||
}
|
||||
}
|
||||
return abstractEvents;
|
||||
};
|
||||
}
|
||||
return abstractEvents;
|
||||
};
|
||||
|
||||
var enqueueAbstractEvents = function(abstractEvents) {
|
||||
if (abstractEvents) {
|
||||
|
||||
@@ -58,7 +58,7 @@ var injection = {
|
||||
*/
|
||||
function listenerAtPhase(id, abstractEvent, propagationPhase) {
|
||||
var registrationName =
|
||||
abstractEvent.type.phasedRegistrationNames[propagationPhase];
|
||||
abstractEvent.reactEventType.phasedRegistrationNames[propagationPhase];
|
||||
return getListener(id, registrationName);
|
||||
}
|
||||
|
||||
@@ -92,9 +92,9 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
|
||||
* have a different target.
|
||||
*/
|
||||
function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
|
||||
if (abstractEvent && abstractEvent.type.phasedRegistrationNames) {
|
||||
if (abstractEvent && abstractEvent.reactEventType.phasedRegistrationNames) {
|
||||
injection.InstanceHandle.traverseTwoPhase(
|
||||
abstractEvent.abstractTargetID,
|
||||
abstractEvent.reactTargetID,
|
||||
accumulateDirectionalDispatches,
|
||||
abstractEvent
|
||||
);
|
||||
@@ -105,11 +105,12 @@ function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `abstractTargetID` be the same as the dispatched ID.
|
||||
* requiring that the `reactTargetID` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(id, ignoredDirection, abstractEvent) {
|
||||
if (abstractEvent && abstractEvent.type.registrationName) {
|
||||
var listener = getListener(id, abstractEvent.type.registrationName);
|
||||
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
|
||||
var registrationName = abstractEvent.reactEventType.registrationName;
|
||||
var listener = getListener(id, registrationName);
|
||||
if (listener) {
|
||||
abstractEvent._dispatchListeners =
|
||||
accumulate(abstractEvent._dispatchListeners, listener);
|
||||
@@ -120,12 +121,12 @@ function accumulateDispatches(id, ignoredDirection, abstractEvent) {
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `AbstractEvent`, but only for the
|
||||
* `abstractTargetID`.
|
||||
* `reactTargetID`.
|
||||
* @param {AbstractEvent} abstractEvent
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(abstractEvent) {
|
||||
if (abstractEvent && abstractEvent.type.registrationName) {
|
||||
accumulateDispatches(abstractEvent.abstractTargetID, null, abstractEvent);
|
||||
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
|
||||
accumulateDispatches(abstractEvent.reactTargetID, null, abstractEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule NormalizedEventListener
|
||||
*/
|
||||
|
||||
var EventListener = require('EventListener');
|
||||
|
||||
/**
|
||||
* @param {?Event} eventParam Event parameter from an attached listener.
|
||||
* @return {Event} Normalized event object.
|
||||
* @private
|
||||
*/
|
||||
function normalizeEvent(eventParam) {
|
||||
var nativeEvent = eventParam || window.event;
|
||||
// In some browsers (OLD FF), setting the target throws an error. A good way
|
||||
// to tell if setting the target will throw an error, is to check if the event
|
||||
// has a `target` property. Safari events have a `target` but it's not always
|
||||
// normalized. Even if a `target` property exists, it's good to only set the
|
||||
// target property if we realize that a change will actually take place.
|
||||
var hasTargetProperty = 'target' in nativeEvent;
|
||||
var eventTarget = nativeEvent.target || nativeEvent.srcElement || window;
|
||||
// Safari may fire events on text nodes (Node.TEXT_NODE is 3)
|
||||
// @see http://www.quirksmode.org/js/events_properties.html
|
||||
var textNodeNormalizedTarget =
|
||||
(eventTarget.nodeType === 3) ? eventTarget.parentNode : eventTarget;
|
||||
if (!hasTargetProperty || nativeEvent.target !== textNodeNormalizedTarget) {
|
||||
// TODO: Normalize the object via `merge()` to work with strict mode.
|
||||
nativeEvent.target = textNodeNormalizedTarget;
|
||||
}
|
||||
return nativeEvent;
|
||||
}
|
||||
|
||||
function createNormalizedCallback(cb) {
|
||||
return function(unfixedNativeEvent) {
|
||||
cb(normalizeEvent(unfixedNativeEvent));
|
||||
};
|
||||
}
|
||||
|
||||
var NormalizedEventListener = {
|
||||
|
||||
/**
|
||||
* Listens to bubbled events on a DOM node.
|
||||
*
|
||||
* NOTE: The listener will be invoked with a normalized event object.
|
||||
*
|
||||
* @param {DOMElement} el DOM element to register listener on.
|
||||
* @param {string} handlerBaseName Event name, e.g. "click".
|
||||
* @param {function} cb Callback function.
|
||||
* @public
|
||||
*/
|
||||
listen: function(el, handlerBaseName, cb) {
|
||||
EventListener.listen(el, handlerBaseName, createNormalizedCallback(cb));
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens to captured events on a DOM node.
|
||||
*
|
||||
* NOTE: The listener will be invoked with a normalized event object.
|
||||
*
|
||||
* @param {DOMElement} el DOM element to register listener on.
|
||||
* @param {string} handlerBaseName Event name, e.g. "click".
|
||||
* @param {function} cb Callback function.
|
||||
* @public
|
||||
*/
|
||||
capture: function(el, handlerBaseName, cb) {
|
||||
EventListener.capture(el, handlerBaseName, createNormalizedCallback(cb));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = NormalizedEventListener;
|
||||
@@ -136,20 +136,25 @@ if (__DEV__) {
|
||||
* This plugin does not really extract any abstract events. Rather it just looks
|
||||
* at the top level event and bumps up counters as appropriate
|
||||
*
|
||||
* @see EventPluginHub.extractAbstractEvents
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
* @see {EventPluginHub.extractAbstractEvents}
|
||||
*/
|
||||
function extractAbstractEvents(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget) {
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
var currentEvent = topLevelTypesToAnalyticsEvent[topLevelType];
|
||||
if (!currentEvent || !renderedTarget || !renderedTarget.attributes) {
|
||||
if (!currentEvent || !topLevelTarget || !topLevelTarget.attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var analyticsIDAttribute = renderedTarget.attributes[ANALYTICS_ID];
|
||||
var analyticsEventsAttribute = renderedTarget.attributes[ANALYTICS_EVENTS];
|
||||
var analyticsIDAttribute = topLevelTarget.attributes[ANALYTICS_ID];
|
||||
var analyticsEventsAttribute = topLevelTarget.attributes[ANALYTICS_EVENTS];
|
||||
if(!analyticsIDAttribute || !analyticsEventsAttribute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule EnterLeaveEventPlugin
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -36,28 +37,32 @@ var abstractEventTypes = {
|
||||
};
|
||||
|
||||
/**
|
||||
* For almost every interaction we care about, there will be a top level
|
||||
* `mouseOver` and `mouseOut` event that occur so we can usually only pay
|
||||
* attention to one of the two (we'll pay attention to the `mouseOut` event) to
|
||||
* avoid extracting a duplicate event. However, there's one interaction where
|
||||
* there will be no `mouseOut` event to rely on - mousing from outside the
|
||||
* browser *into* the chrome. We detect this scenario and only in that case, we
|
||||
* use the `mouseOver` event.
|
||||
* For almost every interaction we care about, there will be a top-level
|
||||
* `mouseover` and `mouseout` event that occurs so only pay attention to one of
|
||||
* the two (to avoid duplicate events). We use the `mouseout` event.
|
||||
*
|
||||
* @see EventPluginHub.extractAbstractEvents
|
||||
* However, there's one interaction where there will be no `mouseout` event to
|
||||
* rely on - mousing from outside the browser *into* the chrome. We detect this
|
||||
* scenario and only in that case, we use the `mouseover` event.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
* @see {EventPluginHub.extractAbstractEvents}
|
||||
*/
|
||||
var extractAbstractEvents = function(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget) {
|
||||
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
if (topLevelType === topLevelTypes.topMouseOver &&
|
||||
(nativeEvent.relatedTarget || nativeEvent.fromElement)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (topLevelType !== topLevelTypes.topMouseOut &&
|
||||
topLevelType !== topLevelTypes.topMouseOver){
|
||||
topLevelType !== topLevelTypes.topMouseOver) {
|
||||
return null; // Must not be a mouse in or mouse out - ignoring.
|
||||
}
|
||||
|
||||
@@ -65,32 +70,33 @@ var extractAbstractEvents = function(
|
||||
if (topLevelType === topLevelTypes.topMouseOut) {
|
||||
to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
|
||||
ExecutionEnvironment.global;
|
||||
from = renderedTarget;
|
||||
from = topLevelTarget;
|
||||
} else {
|
||||
to = renderedTarget;
|
||||
to = topLevelTarget;
|
||||
from = ExecutionEnvironment.global;
|
||||
}
|
||||
|
||||
// Nothing pertains to our managed components.
|
||||
if (from === to ) {
|
||||
return;
|
||||
if (from === to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var fromID = from ? getDOMNodeID(from) : '';
|
||||
var toID = to ? getDOMNodeID(to) : '';
|
||||
|
||||
var leave = AbstractEvent.getPooled(
|
||||
abstractEventTypes.mouseLeave,
|
||||
fromID,
|
||||
topLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
var enter = AbstractEvent.getPooled(
|
||||
abstractEventTypes.mouseEnter,
|
||||
toID,
|
||||
topLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
|
||||
EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
|
||||
|
||||
return [leave, enter];
|
||||
};
|
||||
|
||||
|
||||
@@ -162,14 +162,13 @@ var abstractEventTypes = {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {TopLevelTypes} topLevelType Top level event type being examined.
|
||||
* @param {DOMEvent} nativeEvent Native DOM event.
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {string} renderedTargetID ID of deepest React rendered element.
|
||||
*
|
||||
* @return {Accumulation<AbstractEvent>} Extracted events.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of extracted `AbstractEvent`s.
|
||||
*/
|
||||
var setResponderAndExtractTransfer =
|
||||
function(topLevelType, nativeEvent, renderedTargetID) {
|
||||
function(topLevelType, renderedTargetID, nativeEvent) {
|
||||
var type;
|
||||
var shouldSetEventType =
|
||||
isStartish(topLevelType) ? abstractEventTypes.startShouldSetResponder :
|
||||
@@ -235,7 +234,6 @@ var setResponderAndExtractTransfer =
|
||||
return extracted;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A transfer is a negotiation between a currently set responder and the next
|
||||
* element to claim responder status. Any start event could trigger a transfer
|
||||
@@ -253,51 +251,60 @@ function canTriggerTransfer(topLevelType) {
|
||||
(isPressing && isMoveish(topLevelType));
|
||||
}
|
||||
|
||||
var extractAbstractEvents =
|
||||
function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
|
||||
var extracted;
|
||||
// Must have missed an end event - reset the state here.
|
||||
if (responderID && isStartish(topLevelType)) {
|
||||
responderID = null;
|
||||
/**
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
* @see {EventPluginHub.extractAbstractEvents}
|
||||
*/
|
||||
var extractAbstractEvents = function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
var extracted;
|
||||
// Must have missed an end event - reset the state here.
|
||||
if (responderID && isStartish(topLevelType)) {
|
||||
responderID = null;
|
||||
}
|
||||
if (isStartish(topLevelType)) {
|
||||
isPressing = true;
|
||||
} else if (isEndish(topLevelType)) {
|
||||
isPressing = false;
|
||||
}
|
||||
if (canTriggerTransfer(topLevelType)) {
|
||||
var transfer = setResponderAndExtractTransfer(
|
||||
topLevelType,
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
if (transfer) {
|
||||
extracted = accumulate(extracted, transfer);
|
||||
}
|
||||
if (isStartish(topLevelType)) {
|
||||
isPressing = true;
|
||||
} else if (isEndish(topLevelType)) {
|
||||
isPressing = false;
|
||||
}
|
||||
if (canTriggerTransfer(topLevelType)) {
|
||||
var transfer = setResponderAndExtractTransfer(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget
|
||||
);
|
||||
if (transfer) {
|
||||
extracted = accumulate(extracted, transfer);
|
||||
}
|
||||
}
|
||||
// Now that we know the responder is set correctly, we can dispatch
|
||||
// responder type events (directly to the responder).
|
||||
var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
|
||||
isEndish(topLevelType) ? abstractEventTypes.responderRelease :
|
||||
isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
|
||||
if (type) {
|
||||
var data = AbstractEvent.normalizePointerData(nativeEvent);
|
||||
var gesture = AbstractEvent.getPooled(
|
||||
type,
|
||||
responderID,
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
data
|
||||
);
|
||||
EventPropagators.accumulateDirectDispatches(gesture);
|
||||
extracted = accumulate(extracted, gesture);
|
||||
}
|
||||
if (type === abstractEventTypes.responderRelease) {
|
||||
responderID = null;
|
||||
}
|
||||
return extracted;
|
||||
};
|
||||
}
|
||||
// Now that we know the responder is set correctly, we can dispatch
|
||||
// responder type events (directly to the responder).
|
||||
var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
|
||||
isEndish(topLevelType) ? abstractEventTypes.responderRelease :
|
||||
isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
|
||||
if (type) {
|
||||
var data = AbstractEvent.normalizePointerData(nativeEvent);
|
||||
var gesture = AbstractEvent.getPooled(
|
||||
type,
|
||||
responderID,
|
||||
nativeEvent,
|
||||
data
|
||||
);
|
||||
EventPropagators.accumulateDirectDispatches(gesture);
|
||||
extracted = accumulate(extracted, gesture);
|
||||
}
|
||||
if (type === abstractEventTypes.responderRelease) {
|
||||
responderID = null;
|
||||
}
|
||||
return extracted;
|
||||
};
|
||||
|
||||
/**
|
||||
* Event plugin for formalizing the negotiation between claiming locks on
|
||||
|
||||
@@ -155,6 +155,7 @@ var SimpleEventPlugin = {
|
||||
/**
|
||||
* Same as the default implementation, except cancels the event when return
|
||||
* value is false.
|
||||
*
|
||||
* @param {AbstractEvent} AbstractEvent to handle
|
||||
* @param {function} Application-level callback
|
||||
* @param {string} domID DOM id to pass to the callback.
|
||||
@@ -168,48 +169,55 @@ var SimpleEventPlugin = {
|
||||
},
|
||||
|
||||
/**
|
||||
* @see EventPluginHub.extractAbstractEvents
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
* @see {EventPluginHub.extractAbstractEvents}
|
||||
*/
|
||||
extractAbstractEvents:
|
||||
function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
|
||||
var data;
|
||||
var abstractEventType =
|
||||
SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
|
||||
if (!abstractEventType) {
|
||||
return null;
|
||||
}
|
||||
switch(topLevelType) {
|
||||
case topLevelTypes.topMouseWheel:
|
||||
data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
|
||||
break;
|
||||
case topLevelTypes.topScroll:
|
||||
data = AbstractEvent.normalizeScrollDataFromTarget(renderedTarget);
|
||||
break;
|
||||
case topLevelTypes.topClick:
|
||||
case topLevelTypes.topDoubleClick:
|
||||
case topLevelTypes.topChange:
|
||||
case topLevelTypes.topDOMCharacterDataModified:
|
||||
case topLevelTypes.topMouseDown:
|
||||
case topLevelTypes.topMouseUp:
|
||||
case topLevelTypes.topMouseMove:
|
||||
case topLevelTypes.topTouchMove:
|
||||
case topLevelTypes.topTouchStart:
|
||||
case topLevelTypes.topTouchEnd:
|
||||
data = AbstractEvent.normalizePointerData(nativeEvent);
|
||||
break;
|
||||
default:
|
||||
data = null;
|
||||
}
|
||||
var abstractEvent = AbstractEvent.getPooled(
|
||||
abstractEventType,
|
||||
renderedTargetID,
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
data
|
||||
);
|
||||
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
|
||||
return abstractEvent;
|
||||
extractAbstractEvents: function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
var data;
|
||||
var abstractEventType =
|
||||
SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
|
||||
if (!abstractEventType) {
|
||||
return null;
|
||||
}
|
||||
switch(topLevelType) {
|
||||
case topLevelTypes.topMouseWheel:
|
||||
data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
|
||||
break;
|
||||
case topLevelTypes.topScroll:
|
||||
data = AbstractEvent.normalizeScrollDataFromTarget(topLevelTarget);
|
||||
break;
|
||||
case topLevelTypes.topClick:
|
||||
case topLevelTypes.topDoubleClick:
|
||||
case topLevelTypes.topChange:
|
||||
case topLevelTypes.topDOMCharacterDataModified:
|
||||
case topLevelTypes.topMouseDown:
|
||||
case topLevelTypes.topMouseUp:
|
||||
case topLevelTypes.topMouseMove:
|
||||
case topLevelTypes.topTouchMove:
|
||||
case topLevelTypes.topTouchStart:
|
||||
case topLevelTypes.topTouchEnd:
|
||||
data = AbstractEvent.normalizePointerData(nativeEvent);
|
||||
break;
|
||||
default:
|
||||
data = null;
|
||||
}
|
||||
var abstractEvent = AbstractEvent.getPooled(
|
||||
abstractEventType,
|
||||
topLevelTargetID,
|
||||
nativeEvent,
|
||||
data
|
||||
);
|
||||
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
|
||||
return abstractEvent;
|
||||
}
|
||||
};
|
||||
|
||||
SimpleEventPlugin.topLevelTypesToAbstract = {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule TapEventPlugin
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -46,26 +47,27 @@ var abstractEventTypes = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @see EventPluginHub.extractAbstractEvents
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of `AbstractEvent`s.
|
||||
* @see {EventPluginHub.extractAbstractEvents}
|
||||
*/
|
||||
var extractAbstractEvents = function(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
renderedTargetID,
|
||||
renderedTarget) {
|
||||
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
|
||||
return;
|
||||
}
|
||||
var abstractEvent;
|
||||
var dist = eventDistance(startCoords, nativeEvent);
|
||||
if (isEndish(topLevelType) && dist < tapMoveThreshold) {
|
||||
var type = abstractEventTypes.touchTap;
|
||||
var abstractTargetID = renderedTargetID;
|
||||
abstractEvent = AbstractEvent.getPooled(
|
||||
type,
|
||||
abstractTargetID,
|
||||
topLevelType,
|
||||
abstractEventTypes.touchTap,
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,8 +167,9 @@ var existsInExtraction = function(extracted, test) {
|
||||
function assertGrantEvent(id, extracted) {
|
||||
var test = function(abstractEvent) {
|
||||
return abstractEvent instanceof AbstractEvent &&
|
||||
abstractEvent.type === responderAbstractEventTypes.responderGrant &&
|
||||
abstractEvent.abstractTargetID === id;
|
||||
abstractEvent.reactEventType ===
|
||||
responderAbstractEventTypes.responderGrant &&
|
||||
abstractEvent.reactTargetID === id;
|
||||
};
|
||||
expect(ResponderEventPlugin.getResponderID()).toBe(id);
|
||||
expect(existsInExtraction(extracted, test)).toBe(true);
|
||||
@@ -177,8 +178,9 @@ function assertGrantEvent(id, extracted) {
|
||||
function assertResponderMoveEvent(id, extracted) {
|
||||
var test = function(abstractEvent) {
|
||||
return abstractEvent instanceof AbstractEvent &&
|
||||
abstractEvent.type === responderAbstractEventTypes.responderMove &&
|
||||
abstractEvent.abstractTargetID === id;
|
||||
abstractEvent.reactEventType ===
|
||||
responderAbstractEventTypes.responderMove &&
|
||||
abstractEvent.reactTargetID === id;
|
||||
};
|
||||
expect(ResponderEventPlugin.getResponderID()).toBe(id);
|
||||
expect(existsInExtraction(extracted, test)).toBe(true);
|
||||
@@ -187,8 +189,9 @@ function assertResponderMoveEvent(id, extracted) {
|
||||
function assertTerminateEvent(id, extracted) {
|
||||
var test = function(abstractEvent) {
|
||||
return abstractEvent instanceof AbstractEvent &&
|
||||
abstractEvent.type === responderAbstractEventTypes.responderTerminate &&
|
||||
abstractEvent.abstractTargetID === id;
|
||||
abstractEvent.reactEventType ===
|
||||
responderAbstractEventTypes.responderTerminate &&
|
||||
abstractEvent.reactTargetID === id;
|
||||
};
|
||||
expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
|
||||
expect(existsInExtraction(extracted, test)).toBe(true);
|
||||
@@ -197,8 +200,9 @@ function assertTerminateEvent(id, extracted) {
|
||||
function assertRelease(id, extracted) {
|
||||
var test = function(abstractEvent) {
|
||||
return abstractEvent instanceof AbstractEvent &&
|
||||
abstractEvent.type === responderAbstractEventTypes.responderRelease &&
|
||||
abstractEvent.abstractTargetID === id;
|
||||
abstractEvent.reactEventType ===
|
||||
responderAbstractEventTypes.responderRelease &&
|
||||
abstractEvent.reactTargetID === id;
|
||||
};
|
||||
expect(ResponderEventPlugin.getResponderID()).toBe(null);
|
||||
expect(existsInExtraction(extracted, test)).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user