Use Synthetic Events

Swaps out usage of `AbstractEvent` with `SyntheticEvent` (and subclasses).
This commit is contained in:
CommitSyncScript
2013-06-17 12:49:45 -07:00
committed by Paul O’Shannessy
parent 03464dc148
commit 88e90d5601
15 changed files with 714 additions and 950 deletions
@@ -229,7 +229,7 @@ describe('ReactEventEmitter', function() {
it('should invoke handlers that were removed while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
var handleChildClick = function(event) {
ReactEventEmitter.deleteAllListeners(PARENT.id);
};
ReactEventEmitter.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
@@ -240,7 +240,7 @@ describe('ReactEventEmitter', function() {
it('should not invoke newly inserted handlers while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
var handleChildClick = function(event) {
ReactEventEmitter.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
};
ReactEventEmitter.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
-253
View File
@@ -1,253 +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 AbstractEvent
*/
"use strict";
var BrowserEnv = require('BrowserEnv');
var PooledClass = require('PooledClass');
var TouchEventUtils = require('TouchEventUtils');
var emptyFunction = require('emptyFunction');
var MAX_POOL_SIZE = 20;
/**
* AbstractEvent copy constructor. @see `PooledClass`. Provides a single place
* to define all cross browser normalization of DOM events. Does not attempt to
* extend a native event, rather creates a completely new object that has a
* reference to the nativeEvent through .nativeEvent member. The property .data
* should hold all data that is extracted from the event in a cross browser
* manner. Application code should use the data field when possible, not the
* unreliable native event.
*/
function AbstractEvent(
reactEventType,
reactTargetID, // Allows the abstract target to differ from native.
nativeEvent,
data) {
this.reactEventType = reactEventType;
this.reactTargetID = reactTargetID || '';
this.nativeEvent = nativeEvent;
this.data = data;
// TODO: Deprecate storing target - doesn't always make sense for some types
this.target = nativeEvent && nativeEvent.target;
/**
* As a performance optimization, we tag the existing event with the listeners
* (or listener [singular] if only one). This avoids having to package up an
* abstract event along with the set of listeners into a wrapping "dispatch"
* object. No one should ever read this property except event system and
* plugin/dispatcher code. We also tag the abstract event with a parallel
* ID array. _dispatchListeners[i] is being dispatched to a DOM node at ID
* _dispatchIDs[i]. The lengths should never, ever, ever be different.
*/
this._dispatchListeners = null;
this._dispatchIDs = null;
this.isPropagationStopped = false;
this.isPersistent = emptyFunction.thatReturnsFalse;
}
/** `PooledClass` looks for this. */
AbstractEvent.poolSize = MAX_POOL_SIZE;
/**
* `PooledClass` looks for `destructor` on each instance it releases. We need to
* ensure that we remove all references to listeners which could trap large
* amounts of memory in their closures.
*/
AbstractEvent.prototype.destructor = function() {
this.target = null;
this._dispatchListeners = null;
this._dispatchIDs = null;
};
/**
* Enhance the `AbstractEvent` class to have pooling abilities. We instruct
* `PooledClass` that our copy constructor accepts five arguments (this is just
* a performance optimization). These objects are instantiated frequently.
*/
PooledClass.addPoolingTo(AbstractEvent, PooledClass.fiveArgumentPooler);
AbstractEvent.prototype.stopPropagation = function() {
this.isPropagationStopped = true;
if (this.nativeEvent.stopPropagation) {
this.nativeEvent.stopPropagation();
}
// IE8 only understands cancelBubble, not stopPropagation().
this.nativeEvent.cancelBubble = true;
};
AbstractEvent.prototype.preventDefault = function() {
AbstractEvent.preventDefaultOnNativeEvent(this.nativeEvent);
};
/**
* We clear out all dispatched `AbstractEvent`s after each event loop, adding
* them back into the pool. This allows a way to hold onto a reference that
* won't be added back into the pool.
*/
AbstractEvent.prototype.persist = function() {
this.isPersistent = emptyFunction.thatReturnsTrue;
};
/**
* Utility function for preventing default in cross browser manner.
*/
AbstractEvent.preventDefaultOnNativeEvent = function(nativeEvent) {
if (nativeEvent.preventDefault) {
nativeEvent.preventDefault();
} else {
nativeEvent.returnValue = false;
}
};
/**
* @param {Element} target The target element.
*/
AbstractEvent.normalizeScrollDataFromTarget = function(target) {
return {
scrollTop: target.scrollTop,
scrollLeft: target.scrollLeft,
clientWidth: target.clientWidth,
clientHeight: target.clientHeight,
scrollHeight: target.scrollHeight,
scrollWidth: target.scrollWidth
};
};
/*
* There are some normalizations that need to happen for various browsers. In
* addition to replacing the general event fixing with a framework such as
* jquery, we need to normalize mouse events here. Code below is mostly borrowed
* from: jScrollPane/script/jquery.mousewheel.js
*/
AbstractEvent.normalizeMouseWheelData = function(nativeEvent) {
var delta = 0;
var deltaX = 0;
var deltaY = 0;
/* traditional scroll wheel data */
if ( nativeEvent.wheelDelta ) { delta = nativeEvent.wheelDelta/120; }
if ( nativeEvent.detail ) { delta = -nativeEvent.detail/3; }
/* Multidimensional scroll (touchpads) with deltas */
deltaY = delta;
/* Gecko based browsers */
if (nativeEvent.axis !== undefined &&
nativeEvent.axis === nativeEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -delta;
}
/* Webkit based browsers */
if (nativeEvent.wheelDeltaY !== undefined ) {
deltaY = nativeEvent.wheelDeltaY/120;
}
if (nativeEvent.wheelDeltaX !== undefined ) {
deltaX = -nativeEvent.wheelDeltaX/120;
}
return { delta: delta, deltaX: deltaX, deltaY: deltaY };
};
/**
* I <3 Quirksmode.org:
* http://www.quirksmode.org/js/events_properties.html
*/
AbstractEvent.isNativeClickEventRightClick = function(nativeEvent) {
return nativeEvent.which ? nativeEvent.which === 3 :
nativeEvent.button ? nativeEvent.button === 2 :
false;
};
AbstractEvent.normalizePointerData = function(nativeEvent) {
return {
globalX: AbstractEvent.eventPageX(nativeEvent),
globalY: AbstractEvent.eventPageY(nativeEvent),
rightMouseButton:
AbstractEvent.isNativeClickEventRightClick(nativeEvent)
};
};
AbstractEvent.normalizeDragEventData =
function(nativeEvent, globalX, globalY, startX, startY) {
return {
globalX: globalX,
globalY: globalY,
startX: startX,
startY: startY
};
};
/**
* Warning: It is possible to move your finger on a touch surface, yet not
* effect the `eventPageX/Y` because the touch had caused a scroll that
* compensated for your movement. To track movements across the page, prevent
* default to avoid scrolling, and control scrolling in javascript.
*/
/**
* Gets the exact position of a touch/mouse event on the page with respect to
* the document body. The only reason why this method is needed instead of using
* `TouchEventUtils.extractSingleTouch` is to support IE8-. Mouse events in all
* browsers except IE8- contain a pageY. IE8 and below require clientY
* computation:
*
* @param {Event} nativeEvent Native event, possibly touch or mouse.
* @return {number} Coordinate with respect to document body.
*/
AbstractEvent.eventPageY = function(nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch.pageY;
} else if (typeof nativeEvent.pageY !== 'undefined') {
return nativeEvent.pageY;
} else {
return nativeEvent.clientY + BrowserEnv.currentPageScrollTop;
}
};
/**
* @see `AbstractEvent.eventPageY`.
*
* @param {Event} nativeEvent Native event, possibly touch or mouse.
* @return {number} Coordinate with respect to document body.
*/
AbstractEvent.eventPageX = function(nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch.pageX;
} else if (typeof nativeEvent.pageX !== 'undefined') {
return nativeEvent.pageX;
} else {
return nativeEvent.clientX + BrowserEnv.currentPageScrollLeft;
}
};
/**
* @deprecated
*/
AbstractEvent.persistentCloneOf = function(abstractEvent) {
abstractEvent.persist();
return abstractEvent;
};
module.exports = AbstractEvent;
+1 -1
View File
@@ -65,7 +65,7 @@ var executeDispatchesAndRelease = function(event) {
* Required. When a top-level event is fired, this method is expected to
* extract synthetic events that will in turn be queued and dispatched.
*
* `abstractEventTypes` {object}
* `eventTypes` {object}
* Optional, plugins that fire events must publish a mapping of registration
* names that are used to register listeners. Values of this mapping must
* be objects that contain `registrationName` or `phasedRegistrationNames`.
+2 -2
View File
@@ -60,7 +60,7 @@ function recomputePluginOrdering() {
pluginName
);
EventPluginRegistry.plugins[pluginIndex] = PluginModule;
var publishedEvents = PluginModule.abstractEventTypes;
var publishedEvents = PluginModule.eventTypes;
for (var eventName in publishedEvents) {
invariant(
publishEventForPlugin(publishedEvents[eventName], PluginModule),
@@ -198,7 +198,7 @@ var EventPluginRegistry = {
* @internal
*/
getPluginModuleForEvent: function(event) {
var dispatchConfig = event.reactEventType;
var dispatchConfig = event.dispatchConfig;
if (dispatchConfig.registrationName) {
return EventPluginRegistry.registrationNames[
dispatchConfig.registrationName
+43 -64
View File
@@ -19,7 +19,6 @@
"use strict";
var EventConstants = require('EventConstants');
var AbstractEvent = require('AbstractEvent');
var invariant = require('invariant');
@@ -40,27 +39,11 @@ function isStartish(topLevelType) {
topLevelType === topLevelTypes.topTouchStart;
}
function storePageCoordsIn(obj, nativeEvent) {
var pageX = AbstractEvent.eventPageX(nativeEvent);
var pageY = AbstractEvent.eventPageY(nativeEvent);
obj.pageX = pageX;
obj.pageY = pageY;
}
function eventDistance(coords, nativeEvent) {
var pageX = AbstractEvent.eventPageX(nativeEvent);
var pageY = AbstractEvent.eventPageY(nativeEvent);
return Math.pow(
Math.pow(pageX - coords.pageX, 2) + Math.pow(pageY - coords.pageY, 2),
0.5
);
}
var validateEventDispatches;
if (__DEV__) {
validateEventDispatches = function(abstractEvent) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
validateEventDispatches = function(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
var listenersIsArr = Array.isArray(dispatchListeners);
var idsIsArr = Array.isArray(dispatchIDs);
@@ -71,53 +54,52 @@ if (__DEV__) {
invariant(
idsIsArr === listenersIsArr && IDsLen === listenersLen,
'EventPluginUtils: Invalid `abstractEvent`.'
'EventPluginUtils: Invalid `event`.'
);
};
}
/**
* Invokes `cb(abstractEvent, listener, id)`. Avoids using call if no scope is
* Invokes `cb(event, listener, id)`. Avoids using call if no scope is
* provided. The `(listener,id)` pair effectively forms the "dispatch" but are
* kept separate to conserve memory.
*/
function forEachEventDispatch(abstractEvent, cb) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
function forEachEventDispatch(event, cb) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
var i;
for (
i = 0;
i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
i++) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and IDs are two parallel arrays that are always in sync.
cb(abstractEvent, dispatchListeners[i], dispatchIDs[i]);
cb(event, dispatchListeners[i], dispatchIDs[i]);
}
} else if (dispatchListeners) {
cb(abstractEvent, dispatchListeners, dispatchIDs);
cb(event, dispatchListeners, dispatchIDs);
}
}
/**
* Default implementation of PluginModule.executeDispatch().
* @param {AbstractEvent} AbstractEvent to handle
* @param {SyntheticEvent} SyntheticEvent to handle
* @param {function} Application-level callback
* @param {string} domID DOM id to pass to the callback.
*/
function executeDispatch(abstractEvent, listener, domID) {
listener(abstractEvent, domID);
function executeDispatch(event, listener, domID) {
listener(event, domID);
}
/**
* Standard/simple iteration through an event's collected dispatches.
*/
function executeDispatchesInOrder(abstractEvent, executeDispatch) {
forEachEventDispatch(abstractEvent, executeDispatch);
abstractEvent._dispatchListeners = null;
abstractEvent._dispatchIDs = null;
function executeDispatchesInOrder(event, executeDispatch) {
forEachEventDispatch(event, executeDispatch);
event._dispatchListeners = null;
event._dispatchIDs = null;
}
/**
@@ -127,25 +109,24 @@ function executeDispatchesInOrder(abstractEvent, executeDispatch) {
* @returns id of the first dispatch execution who's listener returns true, or
* null if no listener returned true.
*/
function executeDispatchesInOrderStopAtTrue(abstractEvent) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
function executeDispatchesInOrderStopAtTrue(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
var i;
for (
i = 0;
i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
i++) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and IDs are two parallel arrays that are always in sync.
if (dispatchListeners[i](abstractEvent, dispatchIDs[i])) {
if (dispatchListeners[i](event, dispatchIDs[i])) {
return dispatchIDs[i];
}
}
} else if (dispatchListeners) {
if (dispatchListeners(abstractEvent, dispatchIDs)) {
if (dispatchListeners(event, dispatchIDs)) {
return dispatchIDs;
}
}
@@ -161,30 +142,30 @@ function executeDispatchesInOrderStopAtTrue(abstractEvent) {
*
* @returns The return value of executing the single dispatch.
*/
function executeDirectDispatch(abstractEvent) {
function executeDirectDispatch(event) {
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
var dispatchListener = abstractEvent._dispatchListeners;
var dispatchID = abstractEvent._dispatchIDs;
var dispatchListener = event._dispatchListeners;
var dispatchID = event._dispatchIDs;
invariant(
!Array.isArray(dispatchListener),
'executeDirectDispatch(...): Invalid `abstractEvent`.'
'executeDirectDispatch(...): Invalid `event`.'
);
var res = dispatchListener ?
dispatchListener(abstractEvent, dispatchID) :
dispatchListener(event, dispatchID) :
null;
abstractEvent._dispatchListeners = null;
abstractEvent._dispatchIDs = null;
event._dispatchListeners = null;
event._dispatchIDs = null;
return res;
}
/**
* @param {AbstractEvent} abstractEvent
* @param {SyntheticEvent} event
* @returns {bool} True iff number of dispatches accumulated is greater than 0.
*/
function hasDispatches(abstractEvent) {
return !!abstractEvent._dispatchListeners;
function hasDispatches(event) {
return !!event._dispatchListeners;
}
/**
@@ -194,8 +175,6 @@ var EventPluginUtils = {
isEndish: isEndish,
isMoveish: isMoveish,
isStartish: isStartish,
storePageCoordsIn: storePageCoordsIn,
eventDistance: eventDistance,
executeDispatchesInOrder: executeDispatchesInOrder,
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
executeDirectDispatch: executeDirectDispatch,
+27 -29
View File
@@ -56,19 +56,19 @@ var injection = {
* Some event types have a notion of different registration names for different
* "phases" of propagation. This finds listeners by a given phase.
*/
function listenerAtPhase(id, abstractEvent, propagationPhase) {
function listenerAtPhase(id, event, propagationPhase) {
var registrationName =
abstractEvent.reactEventType.phasedRegistrationNames[propagationPhase];
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(id, registrationName);
}
/**
* Tags an `AbstractEvent` with dispatched listeners. Creating this function
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
* here, allows us to not have to bind or create functions for each event.
* Mutating the event's members allows us to not have to create a wrapping
* "dispatch" object that pairs the event with the listener.
*/
function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
function accumulateDirectionalDispatches(domID, upwards, event) {
if (__DEV__) {
if (!domID) {
throw new Error('Dispatching id must not be null');
@@ -76,11 +76,10 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
injection.validate();
}
var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
var listener = listenerAtPhase(domID, abstractEvent, phase);
var listener = listenerAtPhase(domID, event, phase);
if (listener) {
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, domID);
event._dispatchListeners = accumulate(event._dispatchListeners, listener);
event._dispatchIDs = accumulate(event._dispatchIDs, domID);
}
}
@@ -91,12 +90,12 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
* single traversal for the entire collection of events because each event may
* have a different target.
*/
function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.phasedRegistrationNames) {
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
injection.InstanceHandle.traverseTwoPhase(
abstractEvent.reactTargetID,
event.dispatchMarker,
accumulateDirectionalDispatches,
abstractEvent
event
);
}
}
@@ -105,36 +104,35 @@ function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
/**
* Accumulates without regard to direction, does not look for phased
* registration names. Same as `accumulateDirectDispatchesSingle` but without
* requiring that the `reactTargetID` be the same as the dispatched ID.
* requiring that the `dispatchMarker` be the same as the dispatched ID.
*/
function accumulateDispatches(id, ignoredDirection, abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
var registrationName = abstractEvent.reactEventType.registrationName;
function accumulateDispatches(id, ignoredDirection, event) {
if (event && event.dispatchConfig.registrationName) {
var registrationName = event.dispatchConfig.registrationName;
var listener = getListener(id, registrationName);
if (listener) {
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, id);
event._dispatchListeners = accumulate(event._dispatchListeners, listener);
event._dispatchIDs = accumulate(event._dispatchIDs, id);
}
}
}
/**
* Accumulates dispatches on an `AbstractEvent`, but only for the
* `reactTargetID`.
* @param {AbstractEvent} abstractEvent
* Accumulates dispatches on an `SyntheticEvent`, but only for the
* `dispatchMarker`.
* @param {SyntheticEvent} event
*/
function accumulateDirectDispatchesSingle(abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
accumulateDispatches(abstractEvent.reactTargetID, null, abstractEvent);
function accumulateDirectDispatchesSingle(event) {
if (event && event.dispatchConfig.registrationName) {
accumulateDispatches(event.dispatchMarker, null, event);
}
}
function accumulateTwoPhaseDispatches(abstractEvents) {
function accumulateTwoPhaseDispatches(events) {
if (__DEV__) {
injection.validate();
}
forEachAccumulated(abstractEvents, accumulateTwoPhaseDispatchesSingle);
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
@@ -151,11 +149,11 @@ function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
}
function accumulateDirectDispatches(abstractEvents) {
function accumulateDirectDispatches(events) {
if (__DEV__) {
injection.validate();
}
forEachAccumulated(abstractEvents, accumulateDirectDispatchesSingle);
forEachAccumulated(events, accumulateDirectDispatchesSingle);
}
@@ -155,13 +155,13 @@ describe('EventPluginRegistry', function() {
it('should publish registration names of injected plugins', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
click: {registrationName: 'onClick'},
focus: {registrationName: 'onFocus'}
}
});
var TwoPlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
magic: {
phasedRegistrationNames: {
bubbled: 'onMagicBubble',
@@ -189,12 +189,12 @@ describe('EventPluginRegistry', function() {
it('should throw if multiple registration names collide', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
photoCapture: {registrationName: 'onPhotoCapture'}
}
});
var TwoPlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
photo: {
phasedRegistrationNames: {
bubbled: 'onPhotoBubble',
@@ -219,7 +219,7 @@ describe('EventPluginRegistry', function() {
it('should throw if an invalid event is published', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
badEvent: {/* missing configuration */}
}
});
@@ -246,14 +246,14 @@ describe('EventPluginRegistry', function() {
};
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
click: clickDispatchConfig,
magic: magicDispatchConfig
}
});
var clickEvent = {reactEventType: clickDispatchConfig};
var magicEvent = {reactEventType: magicDispatchConfig};
var clickEvent = {dispatchConfig: clickDispatchConfig};
var magicEvent = {dispatchConfig: magicDispatchConfig};
expect(EventPluginRegistry.getPluginModuleForEvent(clickEvent)).toBe(null);
expect(EventPluginRegistry.getPluginModuleForEvent(magicEvent)).toBe(null);
@@ -133,14 +133,14 @@ 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
* This plugin does not really extract any synthetic events. Rather it just
* looks at the top-level event and bumps up counters as appropriate
*
* @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.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
function extractEvents(
+68 -69
View File
@@ -19,11 +19,11 @@
"use strict";
var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var ReactInstanceHandles = require('ReactInstanceHandles');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var getDOMNodeID = require('getDOMNodeID');
var keyOf = require('keyOf');
@@ -31,78 +31,77 @@ var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var getFirstReactDOM = ReactInstanceHandles.getFirstReactDOM;
var abstractEventTypes = {
var eventTypes = {
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
};
/**
* 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.
*
* 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.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topMouseOver &&
(nativeEvent.relatedTarget || nativeEvent.fromElement)) {
return null;
}
if (topLevelType !== topLevelTypes.topMouseOut &&
topLevelType !== topLevelTypes.topMouseOver) {
return null; // Must not be a mouse in or mouse out - ignoring.
}
var to, from;
if (topLevelType === topLevelTypes.topMouseOut) {
to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
ExecutionEnvironment.global;
from = topLevelTarget;
} else {
to = topLevelTarget;
from = ExecutionEnvironment.global;
}
// Nothing pertains to our managed components.
if (from === to) {
return null;
}
var fromID = from ? getDOMNodeID(from) : '';
var toID = to ? getDOMNodeID(to) : '';
var leave = AbstractEvent.getPooled(
abstractEventTypes.mouseLeave,
fromID,
nativeEvent
);
var enter = AbstractEvent.getPooled(
abstractEventTypes.mouseEnter,
toID,
nativeEvent
);
EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
return [leave, enter];
};
var EnterLeaveEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
eventTypes: eventTypes,
/**
* For almost every interaction we care about, there will be both a top-level
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
* we do not extract duplicate events. However, moving the mouse into the
* browser from outside will not fire a `mouseout` event. In this case, we use
* the `mouseover` top-level 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 synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topMouseOver &&
(nativeEvent.relatedTarget || nativeEvent.fromElement)) {
return null;
}
if (topLevelType !== topLevelTypes.topMouseOut &&
topLevelType !== topLevelTypes.topMouseOver) {
// Must not be a mouse in or mouse out - ignoring.
return null;
}
var from, to;
if (topLevelType === topLevelTypes.topMouseOut) {
from = topLevelTarget;
to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement)
|| ExecutionEnvironment.global;
} else {
from = ExecutionEnvironment.global;
to = topLevelTarget;
}
if (from === to) {
// Nothing pertains to our managed components.
return null;
}
var fromID = from ? getDOMNodeID(from) : '';
var toID = to ? getDOMNodeID(to) : '';
var leave = SyntheticMouseEvent.getPooled(
eventTypes.mouseLeave,
fromID,
nativeEvent
);
var enter = SyntheticMouseEvent.getPooled(
eventTypes.mouseEnter,
toID,
nativeEvent
);
EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
return [leave, enter];
}
};
module.exports = EnterLeaveEventPlugin;
+142 -145
View File
@@ -18,10 +18,10 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticEvent = require('SyntheticEvent');
var accumulate = require('accumulate');
var keyOf = require('keyOf');
@@ -41,11 +41,7 @@ var executeDispatchesInOrderStopAtTrue =
var responderID = null;
var isPressing = false;
var getResponderID = function() {
return responderID;
};
var abstractEventTypes = {
var eventTypes = {
/**
* On a `touchStart`/`mouseDown`, is it desired that this element become the
* responder?
@@ -116,22 +112,22 @@ var abstractEventTypes = {
* `extractEvents()`.
* - These events that are returned from `extractEvents` are "deferred
* dispatched events".
* - When returned from `extractEvents`, deferred dispatched events
* contain an "accumulation" of deferred dispatches.
* -- These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
* - When returned from `extractEvents`, deferred-dispatched events contain an
* "accumulation" of deferred dispatches.
* - These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
*
* In the process of returning their deferred dispatched events, event plugins
* In the process of returning their deferred-dispatched events, event plugins
* themselves can dispatch events on-demand without returning them from
* `extractEvents`. Plugins might want to do this, so that they can use
* event dispatching as a tool that helps them decide which events should be
* extracted in the first place.
* `extractEvents`. Plugins might want to do this, so that they can use event
* dispatching as a tool that helps them decide which events should be extracted
* in the first place.
*
* "On-Demand-Dispatched Events":
*
* - On-demand dispatched are not returned from `extractEvents`.
* - On-demand dispatched events are dispatched during the process of returning
* - On-demand-dispatched events are not returned from `extractEvents`.
* - On-demand-dispatched events are dispatched during the process of returning
* the deferred-dispatched events.
* - They should not have side effects.
* - They should be avoided, and/or eventually be replaced with another
@@ -158,81 +154,81 @@ var abstractEventTypes = {
* - `touchStart` (`EventPluginHub` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
*
* @returns {Accumulation<AbstractEvent>}
*/
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} renderedTargetID ID of deepest React rendered element.
* @param {string} topLevelTargetID ID of deepest React rendered element.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of extracted `AbstractEvent`s.
* @return {*} An accumulation of synthetic events.
*/
var setResponderAndExtractTransfer =
function(topLevelType, renderedTargetID, nativeEvent) {
var type;
var shouldSetEventType =
isStartish(topLevelType) ? abstractEventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? abstractEventTypes.moveShouldSetResponder :
abstractEventTypes.scrollShouldSetResponder;
function setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent) {
var shouldSetEventType =
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
eventTypes.scrollShouldSetResponder;
var bubbleShouldSetFrom = responderID || renderedTargetID;
var shouldSetEvent = AbstractEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
topLevelType,
nativeEvent,
AbstractEvent.normalizePointerData(nativeEvent)
);
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
AbstractEvent.release(shouldSetEvent);
var bubbleShouldSetFrom = responderID || topLevelTargetID;
var shouldSetEvent = SyntheticEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
if (!shouldSetEvent.isPersistent()) {
shouldSetEvent.constructor.release(shouldSetEvent);
}
if (!wantsResponderID || wantsResponderID === responderID) {
return null;
}
var extracted;
var grantEvent = AbstractEvent.getPooled(
abstractEventTypes.responderGrant,
wantsResponderID,
topLevelType,
if (!wantsResponderID || wantsResponderID === responderID) {
return null;
}
var extracted;
var grantEvent = SyntheticEvent.getPooled(
eventTypes.responderGrant,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(grantEvent);
if (responderID) {
var terminationRequestEvent = SyntheticEvent.getPooled(
eventTypes.responderTerminationRequest,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(grantEvent);
if (responderID) {
type = abstractEventTypes.responderTerminationRequest;
var terminationRequestEvent = AbstractEvent.getPooled(type, responderID);
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
AbstractEvent.release(terminationRequestEvent);
if (shouldSwitch) {
var terminateType = abstractEventTypes.responderTerminate;
var terminateEvent = AbstractEvent.getPooled(
terminateType,
responderID,
topLevelType,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
responderID = wantsResponderID;
} else {
var rejectEvent = AbstractEvent.getPooled(
abstractEventTypes.responderReject,
wantsResponderID,
topLevelType,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulate(extracted, rejectEvent);
}
} else {
extracted = accumulate(extracted, grantEvent);
responderID = wantsResponderID;
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
if (!terminationRequestEvent.isPersistent()) {
terminationRequestEvent.constructor.release(terminationRequestEvent);
}
return extracted;
};
if (shouldSwitch) {
var terminateType = eventTypes.responderTerminate;
var terminateEvent = SyntheticEvent.getPooled(
terminateType,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
responderID = wantsResponderID;
} else {
var rejectEvent = SyntheticEvent.getPooled(
eventTypes.responderReject,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulate(extracted, rejectEvent);
}
} else {
extracted = accumulate(extracted, grantEvent);
responderID = wantsResponderID;
}
return extracted;
}
/**
* A transfer is a negotiation between a currently set responder and the next
@@ -241,9 +237,8 @@ var setResponderAndExtractTransfer =
* currently a responder set (in other words as long as the user is pressing
* down).
*
* @param {EventConstants.topLevelTypes} topLevelType
* @return {boolean} Whether or not a transfer of responder could possibly
* occur.
* @param {string} topLevelType Record from `EventConstants`.
* @return {boolean} True if a transfer of responder could possibly occur.
*/
function canTriggerTransfer(topLevelType) {
return topLevelType === EventConstants.topLevelTypes.topScroll ||
@@ -251,69 +246,71 @@ function canTriggerTransfer(topLevelType) {
(isPressing && isMoveish(topLevelType));
}
/**
* @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.extractEvents}
*/
var extractEvents = 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);
}
}
// 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
* receiving touches.
*/
var ResponderEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents,
getResponderID: getResponderID
getResponderID: function() {
return responderID;
},
eventTypes: eventTypes,
/**
* @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 synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: 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);
}
}
// Now that we know the responder is set correctly, we can dispatch
// responder type events (directly to the responder).
var type = isMoveish(topLevelType) ? eventTypes.responderMove :
isEndish(topLevelType) ? eventTypes.responderRelease :
isStartish(topLevelType) ? eventTypes.responderStart : null;
if (type) {
var gesture = SyntheticEvent.getPooled(
type,
responderID || '',
nativeEvent
);
EventPropagators.accumulateDirectDispatches(gesture);
extracted = accumulate(extracted, gesture);
}
if (type === eventTypes.responderRelease) {
responderID = null;
}
return extracted;
}
};
module.exports = ResponderEventPlugin;
+263 -238
View File
@@ -18,201 +18,237 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var SyntheticEvent = require('SyntheticEvent');
var SyntheticFocusEvent = require('SyntheticFocusEvent');
var SyntheticKeyboardEvent = require('SyntheticKeyboardEvent');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var SyntheticMutationEvent = require('SyntheticMutationEvent');
var SyntheticTouchEvent = require('SyntheticTouchEvent');
var SyntheticUIEvent = require('SyntheticUIEvent');
var SyntheticWheelEvent = require('SyntheticWheelEvent');
var invariant = require('invariant');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var SimpleEventPlugin = {
abstractEventTypes: {
// Note: We do not allow listening to mouseOver events. Instead, use the
// onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
mouseDown: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseDown: true}),
captured: keyOf({onMouseDownCapture: true})
}
},
mouseUp: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseUp: true}),
captured: keyOf({onMouseUpCapture: true})
}
},
mouseMove: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseMove: true}),
captured: keyOf({onMouseMoveCapture: true})
}
},
doubleClick: {
phasedRegistrationNames: {
bubbled: keyOf({onDoubleClick: true}),
captured: keyOf({onDoubleClickCapture: true})
}
},
click: {
phasedRegistrationNames: {
bubbled: keyOf({onClick: true}),
captured: keyOf({onClickCapture: true})
}
},
wheel: {
phasedRegistrationNames: {
bubbled: keyOf({onWheel: true}),
captured: keyOf({onWheelCapture: true})
}
},
touchStart: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchStart: true}),
captured: keyOf({onTouchStartCapture: true})
}
},
touchEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchEnd: true}),
captured: keyOf({onTouchEndCapture: true})
}
},
touchCancel: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchCancel: true}),
captured: keyOf({onTouchCancelCapture: true})
}
},
touchMove: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchMove: true}),
captured: keyOf({onTouchMoveCapture: true})
}
},
keyUp: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyUp: true}),
captured: keyOf({onKeyUpCapture: true})
}
},
keyPress: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyPress: true}),
captured: keyOf({onKeyPressCapture: true})
}
},
keyDown: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyDown: true}),
captured: keyOf({onKeyDownCapture: true})
}
},
input: {
phasedRegistrationNames: {
bubbled: keyOf({onInput: true}),
captured: keyOf({onInputCapture: true})
}
},
focus: {
phasedRegistrationNames: {
bubbled: keyOf({onFocus: true}),
captured: keyOf({onFocusCapture: true})
}
},
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
captured: keyOf({onBlurCapture: true})
}
},
scroll: {
phasedRegistrationNames: {
bubbled: keyOf({onScroll: true}),
captured: keyOf({onScrollCapture: true})
}
},
change: {
phasedRegistrationNames: {
bubbled: keyOf({onChange: true}),
captured: keyOf({onChangeCapture: true})
}
},
submit: {
phasedRegistrationNames: {
bubbled: keyOf({onSubmit: true}),
captured: keyOf({onSubmitCapture: true})
}
},
DOMCharacterDataModified: {
phasedRegistrationNames: {
bubbled: keyOf({onDOMCharacterDataModified: true}),
captured: keyOf({onDOMCharacterDataModifiedCapture: true})
}
},
drag: {
phasedRegistrationNames: {
bubbled: keyOf({onDrag: true}),
captured: keyOf({onDragCapture: true})
}
},
dragEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnd: true}),
captured: keyOf({onDragEndCapture: true})
}
},
dragEnter: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnter: true}),
captured: keyOf({onDragEnterCapture: true})
}
},
dragExit: {
phasedRegistrationNames: {
bubbled: keyOf({onDragExit: true}),
captured: keyOf({onDragExitCapture: true})
}
},
dragLeave: {
phasedRegistrationNames: {
bubbled: keyOf({onDragLeave: true}),
captured: keyOf({onDragLeaveCapture: true})
}
},
dragOver: {
phasedRegistrationNames: {
bubbled: keyOf({onDragOver: true}),
captured: keyOf({onDragOverCapture: true})
}
},
dragStart: {
phasedRegistrationNames: {
bubbled: keyOf({onDragStart: true}),
captured: keyOf({onDragStartCapture: true})
}
},
drop: {
phasedRegistrationNames: {
bubbled: keyOf({onDrop: true}),
captured: keyOf({onDropCapture: true})
}
var eventTypes = {
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
captured: keyOf({onBlurCapture: true})
}
},
change: {
phasedRegistrationNames: {
bubbled: keyOf({onChange: true}),
captured: keyOf({onChangeCapture: true})
}
},
click: {
phasedRegistrationNames: {
bubbled: keyOf({onClick: true}),
captured: keyOf({onClickCapture: true})
}
},
doubleClick: {
phasedRegistrationNames: {
bubbled: keyOf({onDoubleClick: true}),
captured: keyOf({onDoubleClickCapture: true})
}
},
drag: {
phasedRegistrationNames: {
bubbled: keyOf({onDrag: true}),
captured: keyOf({onDragCapture: true})
}
},
dragEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnd: true}),
captured: keyOf({onDragEndCapture: true})
}
},
dragEnter: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnter: true}),
captured: keyOf({onDragEnterCapture: true})
}
},
dragExit: {
phasedRegistrationNames: {
bubbled: keyOf({onDragExit: true}),
captured: keyOf({onDragExitCapture: true})
}
},
dragLeave: {
phasedRegistrationNames: {
bubbled: keyOf({onDragLeave: true}),
captured: keyOf({onDragLeaveCapture: true})
}
},
dragOver: {
phasedRegistrationNames: {
bubbled: keyOf({onDragOver: true}),
captured: keyOf({onDragOverCapture: true})
}
},
dragStart: {
phasedRegistrationNames: {
bubbled: keyOf({onDragStart: true}),
captured: keyOf({onDragStartCapture: true})
}
},
drop: {
phasedRegistrationNames: {
bubbled: keyOf({onDrop: true}),
captured: keyOf({onDropCapture: true})
}
},
focus: {
phasedRegistrationNames: {
bubbled: keyOf({onFocus: true}),
captured: keyOf({onFocusCapture: true})
}
},
input: {
phasedRegistrationNames: {
bubbled: keyOf({onInput: true}),
captured: keyOf({onInputCapture: true})
}
},
keyDown: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyDown: true}),
captured: keyOf({onKeyDownCapture: true})
}
},
keyPress: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyPress: true}),
captured: keyOf({onKeyPressCapture: true})
}
},
keyUp: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyUp: true}),
captured: keyOf({onKeyUpCapture: true})
}
},
// Note: We do not allow listening to mouseOver events. Instead, use the
// onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
mouseDown: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseDown: true}),
captured: keyOf({onMouseDownCapture: true})
}
},
mouseMove: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseMove: true}),
captured: keyOf({onMouseMoveCapture: true})
}
},
mouseUp: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseUp: true}),
captured: keyOf({onMouseUpCapture: true})
}
},
scroll: {
phasedRegistrationNames: {
bubbled: keyOf({onScroll: true}),
captured: keyOf({onScrollCapture: true})
}
},
submit: {
phasedRegistrationNames: {
bubbled: keyOf({onSubmit: true}),
captured: keyOf({onSubmitCapture: true})
}
},
touchCancel: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchCancel: true}),
captured: keyOf({onTouchCancelCapture: true})
}
},
touchEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchEnd: true}),
captured: keyOf({onTouchEndCapture: true})
}
},
touchMove: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchMove: true}),
captured: keyOf({onTouchMoveCapture: true})
}
},
touchStart: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchStart: true}),
captured: keyOf({onTouchStartCapture: true})
}
},
wheel: {
phasedRegistrationNames: {
bubbled: keyOf({onWheel: true}),
captured: keyOf({onWheelCapture: true})
}
}
};
var topLevelEventsToDispatchConfig = {
topBlur: eventTypes.blur,
topChange: eventTypes.change,
topClick: eventTypes.click,
topDoubleClick: eventTypes.doubleClick,
topDOMCharacterDataModified: eventTypes.DOMCharacterDataModified,
topDrag: eventTypes.drag,
topDragEnd: eventTypes.dragEnd,
topDragEnter: eventTypes.dragEnter,
topDragExit: eventTypes.dragExit,
topDragLeave: eventTypes.dragLeave,
topDragOver: eventTypes.dragOver,
topDragStart: eventTypes.dragStart,
topDrop: eventTypes.drop,
topFocus: eventTypes.focus,
topInput: eventTypes.input,
topKeyDown: eventTypes.keyDown,
topKeyPress: eventTypes.keyPress,
topKeyUp: eventTypes.keyUp,
topMouseDown: eventTypes.mouseDown,
topMouseMove: eventTypes.mouseMove,
topMouseUp: eventTypes.mouseUp,
topScroll: eventTypes.scroll,
topSubmit: eventTypes.submit,
topTouchCancel: eventTypes.touchCancel,
topTouchEnd: eventTypes.touchEnd,
topTouchMove: eventTypes.touchMove,
topTouchStart: eventTypes.touchStart,
topWheel: eventTypes.wheel
};
var SimpleEventPlugin = {
eventTypes: eventTypes,
/**
* 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.
* @param {object} Event to be dispatched.
* @param {function} Application-level callback.
* @param {string} domID DOM ID to pass to the callback.
*/
executeDispatch: function(abstractEvent, listener, domID) {
var returnValue = listener(abstractEvent, domID);
executeDispatch: function(event, listener, domID) {
var returnValue = listener(event, domID);
if (returnValue === false) {
abstractEvent.stopPropagation();
abstractEvent.preventDefault();
event.stopPropagation();
event.preventDefault();
}
},
@@ -221,7 +257,7 @@ var SimpleEventPlugin = {
* @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.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
@@ -229,29 +265,30 @@ var SimpleEventPlugin = {
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var data;
var abstractEventType =
SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
if (!abstractEventType) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) {
return null;
}
var EventConstructor;
switch(topLevelType) {
case topLevelTypes.topWheel:
data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
case topLevelTypes.topChange:
case topLevelTypes.topInput:
case topLevelTypes.topSubmit:
// HTML Events
// @see http://www.w3.org/TR/html5/index.html#events-0
EventConstructor = SyntheticEvent;
break;
case topLevelTypes.topScroll:
data = AbstractEvent.normalizeScrollDataFromTarget(topLevelTarget);
case topLevelTypes.topKeyDown:
case topLevelTypes.topKeyPress:
case topLevelTypes.topKeyUp:
EventConstructor = SyntheticKeyboardEvent;
break;
case topLevelTypes.topBlur:
case topLevelTypes.topFocus:
EventConstructor = SyntheticFocusEvent;
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:
case topLevelTypes.topDrag:
case topLevelTypes.topDragEnd:
case topLevelTypes.topDragEnter:
@@ -260,53 +297,41 @@ var SimpleEventPlugin = {
case topLevelTypes.topDragOver:
case topLevelTypes.topDragStart:
case topLevelTypes.topDrop:
data = AbstractEvent.normalizePointerData(nativeEvent);
// todo: Use AbstractEvent.normalizeDragEventData for drag/drop?
case topLevelTypes.topMouseDown:
case topLevelTypes.topMouseMove:
case topLevelTypes.topMouseUp:
EventConstructor = SyntheticMouseEvent;
break;
case topLevelTypes.topDOMCharacterDataModified:
EventConstructor = SyntheticMutationEvent;
break;
case topLevelTypes.topTouchCancel:
case topLevelTypes.topTouchEnd:
case topLevelTypes.topTouchMove:
case topLevelTypes.topTouchStart:
EventConstructor = SyntheticTouchEvent;
break;
case topLevelTypes.topScroll:
EventConstructor = SyntheticUIEvent;
break;
case topLevelTypes.topWheel:
EventConstructor = SyntheticWheelEvent;
break;
default:
data = null;
}
var abstractEvent = AbstractEvent.getPooled(
abstractEventType,
topLevelTargetID,
nativeEvent,
data
invariant(
EventConstructor,
'SimpleEventPlugin: Unhandled event type, `%s`.',
topLevelType
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
var event = EventConstructor.getPooled(
dispatchConfig,
topLevelTargetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
};
SimpleEventPlugin.topLevelTypesToAbstract = {
topMouseDown: SimpleEventPlugin.abstractEventTypes.mouseDown,
topMouseUp: SimpleEventPlugin.abstractEventTypes.mouseUp,
topMouseMove: SimpleEventPlugin.abstractEventTypes.mouseMove,
topClick: SimpleEventPlugin.abstractEventTypes.click,
topDoubleClick: SimpleEventPlugin.abstractEventTypes.doubleClick,
topWheel: SimpleEventPlugin.abstractEventTypes.wheel,
topTouchStart: SimpleEventPlugin.abstractEventTypes.touchStart,
topTouchEnd: SimpleEventPlugin.abstractEventTypes.touchEnd,
topTouchMove: SimpleEventPlugin.abstractEventTypes.touchMove,
topTouchCancel: SimpleEventPlugin.abstractEventTypes.touchCancel,
topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp,
topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress,
topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown,
topInput: SimpleEventPlugin.abstractEventTypes.input,
topFocus: SimpleEventPlugin.abstractEventTypes.focus,
topBlur: SimpleEventPlugin.abstractEventTypes.blur,
topScroll: SimpleEventPlugin.abstractEventTypes.scroll,
topChange: SimpleEventPlugin.abstractEventTypes.change,
topSubmit: SimpleEventPlugin.abstractEventTypes.submit,
topDOMCharacterDataModified:
SimpleEventPlugin.abstractEventTypes.DOMCharacterDataModified,
topDrag: SimpleEventPlugin.abstractEventTypes.drag,
topDragEnd: SimpleEventPlugin.abstractEventTypes.dragEnd,
topDragEnter: SimpleEventPlugin.abstractEventTypes.dragEnter,
topDragExit: SimpleEventPlugin.abstractEventTypes.dragExit,
topDragLeave: SimpleEventPlugin.abstractEventTypes.dragLeave,
topDragOver: SimpleEventPlugin.abstractEventTypes.dragOver,
topDragStart: SimpleEventPlugin.abstractEventTypes.dragStart,
topDrop: SimpleEventPlugin.abstractEventTypes.drop
};
module.exports = SimpleEventPlugin;
+70 -44
View File
@@ -19,25 +19,49 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var BrowserEnv = require('BrowserEnv');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticUIEvent = require('SyntheticUIEvent');
var TouchEventUtils = require('TouchEventUtils');
var keyOf = require('keyOf');
var isStartish = EventPluginUtils.isStartish;
var isEndish = EventPluginUtils.isEndish;
var storePageCoordsIn = EventPluginUtils.storePageCoordsIn;
var eventDistance = EventPluginUtils.eventDistance;
/**
* The number of pixels that are tolerated in between a touchStart and
* touchEnd in order to still be considered a 'tap' event.
* Number of pixels that are tolerated in between a `touchStart` and `touchEnd`
* in order to still be considered a 'tap' event.
*/
var tapMoveThreshold = 10;
var startCoords = {x: null, y: null};
var abstractEventTypes = {
var Axis = {
x: {page: 'pageX', client: 'clientX', envScroll: 'currentPageScrollLeft'},
y: {page: 'pageY', client: 'clientY', envScroll: 'currentPageScrollTop'}
};
function getAxisCoordOfEvent(axis, nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch[axis.page];
}
return axis.page in nativeEvent ?
nativeEvent[axis.page] :
nativeEvent[axis.client] + BrowserEnv[axis.envScroll];
}
function getDistance(coords, nativeEvent) {
var pageX = getAxisCoordOfEvent(Axis.x, nativeEvent);
var pageY = getAxisCoordOfEvent(Axis.y, nativeEvent);
return Math.pow(
Math.pow(pageX - coords.x, 2) + Math.pow(pageY - coords.y, 2),
0.5
);
}
var eventTypes = {
touchTap: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchTap: null}),
@@ -46,46 +70,48 @@ var abstractEventTypes = {
}
};
/**
* @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.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return;
}
var abstractEvent;
var dist = eventDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && dist < tapMoveThreshold) {
abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
storePageCoordsIn(startCoords, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
};
var TapEventPlugin = {
tapMoveThreshold: tapMoveThreshold,
startCoords: startCoords,
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
eventTypes: eventTypes,
/**
* @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 synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return null;
}
var event = null;
var distance = getDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && distance < tapMoveThreshold) {
event = SyntheticUIEvent.getPooled(
eventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
startCoords.x = getAxisCoordOfEvent(Axis.x, nativeEvent);
startCoords.y = getAxisCoordOfEvent(Axis.y, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
};
module.exports = TapEventPlugin;
+58 -61
View File
@@ -18,18 +18,18 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPluginHub = require('EventPluginHub');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var SyntheticEvent = require('SyntheticEvent');
var isEventSupported = require('isEventSupported');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var abstractEventTypes = {
var eventTypes = {
textChange: {
phasedRegistrationNames: {
bubbled: keyOf({onTextChange: null}),
@@ -121,29 +121,28 @@ var stopWatching = function() {
* the value of the active element has changed.
*/
var handlePropertyChange = function(nativeEvent) {
var value;
var abstractEvent;
if (nativeEvent.propertyName === "value") {
value = nativeEvent.srcElement.value;
if (value !== activeElementValue) {
activeElementValue = value;
abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.textChange,
activeElementID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
// If propertychange bubbled, we'd just bind to it like all the other
// events and have it go through ReactEventTopLevelCallback. Since it
// doesn't, we manually listen for the propertychange event and so we
// have to enqueue and process the abstract event manually.
EventPluginHub.enqueueEvents(abstractEvent);
EventPluginHub.processEventQueue();
}
if (nativeEvent.propertyName !== "value") {
return;
}
var value = nativeEvent.srcElement.value;
if (value === activeElementValue) {
return;
}
activeElementValue = value;
var event = SyntheticEvent.getPooled(
eventTypes.textChange,
activeElementID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
// If propertychange bubbled, we'd just bind to it like all the other events
// and have it go through ReactEventTopLevelCallback. Since it doesn't, we
// manually listen for the propertychange event and so we have to enqueue and
// process the abstract event manually.
EventPluginHub.enqueueEvents(event);
EventPluginHub.processEventQueue();
};
/**
@@ -154,8 +153,7 @@ if (isInputSupported) {
targetIDForTextChangeEvent = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
topLevelTargetID) {
if (topLevelType === topLevelTypes.topInput) {
// In modern browsers (i.e., not IE8 or IE9), the input event is exactly
// what we want so fall through here and trigger an abstract event...
@@ -171,8 +169,7 @@ if (isInputSupported) {
targetIDForTextChangeEvent = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
topLevelTargetID) {
if (topLevelType === topLevelTypes.topFocus) {
// In IE8, we can capture almost all .value changes by adding a
// propertychange handler and looking for events with propertyName
@@ -214,40 +211,40 @@ if (isInputSupported) {
};
}
/**
* @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.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var targetID = targetIDForTextChangeEvent(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent
);
if (targetID) {
var abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.textChange,
targetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
}
};
var TextChangeEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
eventTypes: eventTypes,
/**
* @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 synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var targetID = targetIDForTextChangeEvent(
topLevelType,
topLevelTarget,
topLevelTargetID
);
if (targetID) {
var event = SyntheticEvent.getPooled(
eventTypes.textChange,
targetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
}
};
module.exports = TextChangeEventPlugin;
@@ -23,21 +23,21 @@ var EventConstants;
var EventPropagators;
var ReactInstanceHandles;
var ResponderEventPlugin;
var AbstractEvent;
var SyntheticEvent;
var GRANDPARENT_ID = '.reactRoot[0]';
var PARENT_ID = '.reactRoot[0].0';
var CHILD_ID = '.reactRoot[0].0.0';
var topLevelTypes;
var responderAbstractEventTypes;
var responderEventTypes;
var spies;
var DUMMY_NATIVE_EVENT = {};
var DUMMY_RENDERED_TARGET = {};
var onStartShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderAbstractEventTypes
var registrationNames = responderEventTypes
.startShouldSetResponder
.phasedRegistrationNames;
CallbackRegistry.putListener(
@@ -48,7 +48,7 @@ var onStartShouldSetResponder = function(id, cb, capture) {
};
var onScrollShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderAbstractEventTypes
var registrationNames = responderEventTypes
.scrollShouldSetResponder
.phasedRegistrationNames;
CallbackRegistry.putListener(
@@ -59,7 +59,7 @@ var onScrollShouldSetResponder = function(id, cb, capture) {
};
var onMoveShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderAbstractEventTypes
var registrationNames = responderEventTypes
.moveShouldSetResponder
.phasedRegistrationNames;
CallbackRegistry.putListener(
@@ -73,7 +73,7 @@ var onMoveShouldSetResponder = function(id, cb, capture) {
var onResponderGrant = function(id, cb) {
CallbackRegistry.putListener(
id,
responderAbstractEventTypes.responderGrant.registrationName,
responderEventTypes.responderGrant.registrationName,
cb
);
};
@@ -165,44 +165,40 @@ var existsInExtraction = function(extracted, test) {
* Helper validators.
*/
function assertGrantEvent(id, extracted) {
var test = function(abstractEvent) {
return abstractEvent instanceof AbstractEvent &&
abstractEvent.reactEventType ===
responderAbstractEventTypes.responderGrant &&
abstractEvent.reactTargetID === id;
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderGrant &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertResponderMoveEvent(id, extracted) {
var test = function(abstractEvent) {
return abstractEvent instanceof AbstractEvent &&
abstractEvent.reactEventType ===
responderAbstractEventTypes.responderMove &&
abstractEvent.reactTargetID === id;
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderMove &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertTerminateEvent(id, extracted) {
var test = function(abstractEvent) {
return abstractEvent instanceof AbstractEvent &&
abstractEvent.reactEventType ===
responderAbstractEventTypes.responderTerminate &&
abstractEvent.reactTargetID === id;
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderTerminate &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertRelease(id, extracted) {
var test = function(abstractEvent) {
return abstractEvent instanceof AbstractEvent &&
abstractEvent.reactEventType ===
responderAbstractEventTypes.responderRelease &&
abstractEvent.reactTargetID === id;
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderRelease &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(null);
expect(existsInExtraction(extracted, test)).toBe(true);
@@ -226,12 +222,12 @@ describe('ResponderEventPlugin', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
AbstractEvent = require('AbstractEvent');
CallbackRegistry = require('CallbackRegistry');
EventConstants = require('EventConstants');
EventPropagators = require('EventPropagators');
ReactInstanceHandles = require('ReactInstanceHandles');
ResponderEventPlugin = require('ResponderEventPlugin');
SyntheticEvent = require('SyntheticEvent');
EventPropagators.injection.injectInstanceHandle(ReactInstanceHandles);
// dumpCache, in open-source tests, only resets existing mocks. It does not
@@ -240,7 +236,7 @@ describe('ResponderEventPlugin', function() {
CallbackRegistry.__purge();
topLevelTypes = EventConstants.topLevelTypes;
responderAbstractEventTypes = ResponderEventPlugin.abstractEventTypes;
responderEventTypes = ResponderEventPlugin.eventTypes;
spies = {
onStartShouldSetResponderChild: function() {},
+3 -3
View File
@@ -187,7 +187,7 @@ var ReactTestUtils = {
* on and `Element` node.
* @param topLevelType {Object} A type from `EventConstants.topLevelTypes`
* @param {!Element} node The dom to simulate an event occurring on.
* @param {?Event} fakeNativeEvent Fake native event to pass to ReactEvent.
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
simulateEventOnNode: function(topLevelType, node, fakeNativeEvent) {
var virtualHandler =
@@ -203,7 +203,7 @@ var ReactTestUtils = {
* on the `ReactNativeComponent` `comp`.
* @param topLevelType {Object} A type from `EventConstants.topLevelTypes`.
* @param comp {!ReactNativeComponent}
* @param {?Event} fakeNativeEvent Fake native event to pass to ReactEvent.
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
simulateEventOnDOMComponent: function(topLevelType, comp, fakeNativeEvent) {
var reactRootID = comp._rootNodeID || comp._rootDomId;
@@ -280,7 +280,7 @@ for (eventType in topLevelTypes) {
eventType.charAt(3).toLowerCase() + eventType.substr(4) : eventType;
/**
* @param {!Element || ReactNativeComponent} domComponentOrNode
* @param {?Event} nativeEventData Fake native event to pass to ReactEvent.
* @param {?Event} nativeEventData Fake native event to use in SyntheticEvent.
*/
ReactTestUtils.Simulate[convenienceName] = makeSimulator(eventType);
}