mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Create ReactEventListener
Per our discussion friday, this creates ReactEventListener and renames variables to use the "handle" nomenclature.
This commit is contained in:
committed by
Paul O’Shannessy
parent
a6cd945d9f
commit
e1c2d02fdd
@@ -13,29 +13,31 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactEventEmitter
|
||||
* @providesModule ReactBrowserEventEmitter
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventConstants = require('EventConstants');
|
||||
var EventListener = require('EventListener');
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginRegistry = require('EventPluginRegistry');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
|
||||
var ViewportMetrics = require('ViewportMetrics');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var isEventSupported = require('isEventSupported');
|
||||
var merge = require('merge');
|
||||
|
||||
/**
|
||||
* Summary of `ReactEventEmitter` event handling:
|
||||
* Summary of `ReactBrowserEventEmitter` event handling:
|
||||
*
|
||||
* - Top-level delegation is used to trap native browser events. We normalize
|
||||
* and de-duplicate events to account for browser quirks.
|
||||
* - Top-level delegation is used to trap most native browser events. This
|
||||
* may only occur in the main thread and is the responsibility of
|
||||
* ReactEventListener, which is injected and can therefore support pluggable
|
||||
* event sources. This is the only work that occurs in the main thread.
|
||||
*
|
||||
* - We normalize and de-duplicate events to account for browser quirks. This
|
||||
* may be done in the worker thread.
|
||||
*
|
||||
* - Forward these native events (with the associated top-level type used to
|
||||
* trap it) to `EventPluginHub`, which in turn will ask plugins if they want
|
||||
@@ -48,11 +50,16 @@ var merge = require('merge');
|
||||
*
|
||||
* Overview of React and the event system:
|
||||
*
|
||||
* .
|
||||
* +------------+ .
|
||||
* | DOM | .
|
||||
* +------------+ .
|
||||
* | .
|
||||
* v .
|
||||
* +------------+ .
|
||||
* | ReactEvent | .
|
||||
* | Listener | .
|
||||
* +------------+ . +-----------+
|
||||
* + . +--------+|SimpleEvent|
|
||||
* | . +--------+|SimpleEvent|
|
||||
* | . | |Plugin |
|
||||
* +-----|------+ . v +-----------+
|
||||
* | | | . +--------------+ +------------+
|
||||
@@ -139,68 +146,31 @@ function getListeningForDocument(mountAt) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
* `ReactBrowserEventEmitter` is used to attach top-level event listeners. For
|
||||
* example:
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {DOMEventTarget} element Element on which to attach listener.
|
||||
* @return {object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
return 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.
|
||||
* @return {object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
return EventListener.capture(
|
||||
element,
|
||||
handlerBaseName,
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
|
||||
*
|
||||
* ReactEventEmitter.putListener('myID', 'onClick', myFunction);
|
||||
* ReactBrowserEventEmitter.putListener('myID', 'onClick', myFunction);
|
||||
*
|
||||
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
var ReactBrowserEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
|
||||
/**
|
||||
* React references `ReactEventTopLevelCallback` using this property in order
|
||||
* to allow dependency injection.
|
||||
* Injectable event backend
|
||||
*/
|
||||
TopLevelCallbackCreator: null,
|
||||
ReactEventListener: null,
|
||||
|
||||
injection: {
|
||||
/**
|
||||
* @param {function} TopLevelCallbackCreator
|
||||
* @param {object} ReactEventListener
|
||||
*/
|
||||
injectTopLevelCallbackCreator: function(TopLevelCallbackCreator) {
|
||||
ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
|
||||
injectReactEventListener: function(ReactEventListener) {
|
||||
ReactEventListener.setHandleTopLevel(
|
||||
ReactBrowserEventEmitter.handleTopLevel
|
||||
);
|
||||
ReactBrowserEventEmitter.ReactEventListener = ReactEventListener;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -210,13 +180,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
* @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);
|
||||
if (ReactBrowserEventEmitter.ReactEventListener) {
|
||||
ReactBrowserEventEmitter.ReactEventListener.setEnabled(enabled);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -225,8 +190,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
*/
|
||||
isEnabled: function() {
|
||||
return !!(
|
||||
ReactEventEmitter.TopLevelCallbackCreator &&
|
||||
ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
|
||||
ReactBrowserEventEmitter.ReactEventListener &&
|
||||
ReactBrowserEventEmitter.ReactEventListener.isEnabled()
|
||||
);
|
||||
},
|
||||
|
||||
@@ -249,10 +214,10 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
* they bubble to document.
|
||||
*
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @param {DOMDocument} contentDocument Document which owns the container
|
||||
* @param {object} contentDocumentHandle Document which owns the container
|
||||
*/
|
||||
listenTo: function(registrationName, contentDocument) {
|
||||
var mountAt = contentDocument;
|
||||
listenTo: function(registrationName, contentDocumentHandle) {
|
||||
var mountAt = contentDocumentHandle;
|
||||
var isListening = getListeningForDocument(mountAt);
|
||||
var dependencies = EventPluginRegistry.
|
||||
registrationNameDependencies[registrationName];
|
||||
@@ -268,42 +233,79 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
|
||||
if (topLevelType === topLevelTypes.topWheel) {
|
||||
if (isEventSupported('wheel')) {
|
||||
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topWheel,
|
||||
'wheel',
|
||||
mountAt
|
||||
);
|
||||
} else if (isEventSupported('mousewheel')) {
|
||||
trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topWheel,
|
||||
'mousewheel',
|
||||
mountAt
|
||||
);
|
||||
} else {
|
||||
// Firefox needs to capture a different mouse scroll event.
|
||||
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
|
||||
trapBubbledEvent(
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topWheel,
|
||||
'DOMMouseScroll',
|
||||
mountAt);
|
||||
mountAt
|
||||
);
|
||||
}
|
||||
} else if (topLevelType === topLevelTypes.topScroll) {
|
||||
|
||||
if (isEventSupported('scroll', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||||
topLevelTypes.topScroll,
|
||||
'scroll',
|
||||
mountAt
|
||||
);
|
||||
} else {
|
||||
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topScroll,
|
||||
'scroll',
|
||||
ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE
|
||||
);
|
||||
}
|
||||
} else if (topLevelType === topLevelTypes.topFocus ||
|
||||
topLevelType === topLevelTypes.topBlur) {
|
||||
|
||||
if (isEventSupported('focus', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
|
||||
trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||||
topLevelTypes.topFocus,
|
||||
'focus',
|
||||
mountAt
|
||||
);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||||
topLevelTypes.topBlur,
|
||||
'blur',
|
||||
mountAt
|
||||
);
|
||||
} else if (isEventSupported('focusin')) {
|
||||
// IE has `focusin` and `focusout` events which bubble.
|
||||
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
|
||||
trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topFocus,
|
||||
'focusin',
|
||||
mountAt
|
||||
);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelTypes.topBlur,
|
||||
'focusout',
|
||||
mountAt
|
||||
);
|
||||
}
|
||||
|
||||
// to make sure blur and focus event listeners are only attached once
|
||||
isListening[topLevelTypes.topBlur] = true;
|
||||
isListening[topLevelTypes.topFocus] = true;
|
||||
} else if (topEventMapping.hasOwnProperty(dependency)) {
|
||||
trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
|
||||
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelType,
|
||||
topEventMapping[dependency],
|
||||
mountAt
|
||||
);
|
||||
}
|
||||
|
||||
isListening[dependency] = true;
|
||||
@@ -311,6 +313,22 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
}
|
||||
},
|
||||
|
||||
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
|
||||
return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||||
topLevelType,
|
||||
handlerBaseName,
|
||||
handle
|
||||
);
|
||||
},
|
||||
|
||||
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
|
||||
return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||||
topLevelType,
|
||||
handlerBaseName,
|
||||
handle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens to window scroll and resize events. We cache scroll values so that
|
||||
* application code can access them without triggering reflows.
|
||||
@@ -322,8 +340,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
ensureScrollValueMonitoring: function(){
|
||||
if (!isMonitoringScrollValue) {
|
||||
var refresh = ViewportMetrics.refreshScrollValues;
|
||||
EventListener.listen(window, 'scroll', refresh);
|
||||
EventListener.listen(window, 'resize', refresh);
|
||||
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
|
||||
isMonitoringScrollValue = true;
|
||||
}
|
||||
},
|
||||
@@ -338,12 +355,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
|
||||
deleteListener: EventPluginHub.deleteListener,
|
||||
|
||||
deleteAllListeners: EventPluginHub.deleteAllListeners,
|
||||
|
||||
trapBubbledEvent: trapBubbledEvent,
|
||||
|
||||
trapCapturedEvent: trapCapturedEvent
|
||||
deleteAllListeners: EventPluginHub.deleteAllListeners
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactEventEmitter;
|
||||
module.exports = ReactBrowserEventEmitter;
|
||||
@@ -19,7 +19,7 @@
|
||||
"use strict";
|
||||
|
||||
var PooledClass = require('PooledClass');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
|
||||
var mixInto = require('mixInto');
|
||||
|
||||
@@ -39,7 +39,7 @@ mixInto(ReactPutListenerQueue, {
|
||||
putListeners: function() {
|
||||
for (var i = 0; i < this.listenersToPut.length; i++) {
|
||||
var listenerToPut = this.listenersToPut[i];
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
listenerToPut.rootNodeID,
|
||||
listenerToPut.propKey,
|
||||
listenerToPut.propValue
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactInputSelection = require('ReactInputSelection');
|
||||
var ReactPutListenerQueue = require('ReactPutListenerQueue');
|
||||
var Transaction = require('Transaction');
|
||||
@@ -50,21 +50,22 @@ var SELECTION_RESTORATION = {
|
||||
*/
|
||||
var EVENT_SUPPRESSION = {
|
||||
/**
|
||||
* @return {boolean} The enabled status of `ReactEventEmitter` before the
|
||||
* reconciliation.
|
||||
* @return {boolean} The enabled status of `ReactBrowserEventEmitter` before
|
||||
* the reconciliation.
|
||||
*/
|
||||
initialize: function() {
|
||||
var currentlyEnabled = ReactEventEmitter.isEnabled();
|
||||
ReactEventEmitter.setEnabled(false);
|
||||
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
return currentlyEnabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {boolean} previouslyEnabled Enabled status of `ReactEventEmitter`
|
||||
* before the reconciliation occured. `close` restores the previous value.
|
||||
* @param {boolean} previouslyEnabled Enabled status of
|
||||
* `ReactBrowserEventEmitter` before the reconciliation occured. `close`
|
||||
* restores the previous value.
|
||||
*/
|
||||
close: function(previouslyEnabled) {
|
||||
ReactEventEmitter.setEnabled(previouslyEnabled);
|
||||
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+73
-57
@@ -21,7 +21,7 @@
|
||||
require('mock-modules')
|
||||
.dontMock('EventPluginHub')
|
||||
.dontMock('ReactMount')
|
||||
.dontMock('ReactEventEmitter')
|
||||
.dontMock('ReactBrowserEventEmitter')
|
||||
.dontMock('ReactInstanceHandles')
|
||||
.dontMock('EventPluginHub')
|
||||
.dontMock('TapEventPlugin')
|
||||
@@ -42,7 +42,7 @@ var setID = function(el, id) {
|
||||
var oldGetNode = ReactMount.getNode;
|
||||
|
||||
var EventPluginHub;
|
||||
var ReactEventEmitter;
|
||||
var ReactBrowserEventEmitter;
|
||||
var ReactTestUtils;
|
||||
var TapEventPlugin;
|
||||
var EventListener;
|
||||
@@ -67,12 +67,12 @@ var ON_CHANGE_KEY = keyOf({onChange: null});
|
||||
|
||||
|
||||
/**
|
||||
* Since `ReactEventEmitter` is fairly well separated from the DOM, we can test
|
||||
* almost all of `ReactEventEmitter` without ever rendering anything in the DOM.
|
||||
* As long as we provide IDs that follow `React's` conventional id namespace
|
||||
* hierarchy. The only reason why we create these DOM nodes, is so that when we
|
||||
* feed them into `ReactEventEmitter` (through `ReactTestUtils`), the event
|
||||
* handlers may receive a DOM node to inspect.
|
||||
* Since `ReactBrowserEventEmitter` is fairly well separated from the DOM, we
|
||||
* can test almost all of `ReactBrowserEventEmitter` without ever rendering
|
||||
* anything in the DOM. As long as we provide IDs that follow `React's`
|
||||
* conventional id namespace hierarchy. The only reason why we create these DOM
|
||||
* nodes is so that when we feed them into `ReactBrowserEventEmitter` (through
|
||||
* `ReactTestUtils`), the event handlers may receive a DOM node to inspect.
|
||||
*/
|
||||
var CHILD = document.createElement('div');
|
||||
var PARENT = document.createElement('div');
|
||||
@@ -82,14 +82,15 @@ setID(PARENT, '.0.0.0');
|
||||
setID(GRANDPARENT, '.0.0');
|
||||
|
||||
function registerSimpleTestHandler() {
|
||||
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
|
||||
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
ReactBrowserEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
|
||||
var listener =
|
||||
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
expect(listener).toEqual(LISTENER);
|
||||
return ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
return ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
}
|
||||
|
||||
|
||||
describe('ReactEventEmitter', function() {
|
||||
describe('ReactBrowserEventEmitter', function() {
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
LISTENER.mockClear();
|
||||
@@ -97,7 +98,7 @@ describe('ReactEventEmitter', function() {
|
||||
TapEventPlugin = require('TapEventPlugin');
|
||||
ReactMount = require('ReactMount');
|
||||
EventListener = require('EventListener');
|
||||
ReactEventEmitter = require('ReactEventEmitter');
|
||||
ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ReactMount.getNode = function(id) {
|
||||
return idToNode[id];
|
||||
@@ -115,20 +116,23 @@ describe('ReactEventEmitter', function() {
|
||||
|
||||
it('should store a listener correctly', function() {
|
||||
registerSimpleTestHandler();
|
||||
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
var listener =
|
||||
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
expect(listener).toBe(LISTENER);
|
||||
});
|
||||
|
||||
it('should retrieve a listener correctly', function() {
|
||||
registerSimpleTestHandler();
|
||||
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
var listener =
|
||||
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
expect(listener).toEqual(LISTENER);
|
||||
});
|
||||
|
||||
it('should clear all handlers when asked to', function() {
|
||||
registerSimpleTestHandler();
|
||||
ReactEventEmitter.deleteAllListeners(getID(CHILD));
|
||||
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
ReactBrowserEventEmitter.deleteAllListeners(getID(CHILD));
|
||||
var listener =
|
||||
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
|
||||
expect(listener).toBe(undefined);
|
||||
});
|
||||
|
||||
@@ -138,28 +142,31 @@ describe('ReactEventEmitter', function() {
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not invoke handlers if ReactEventEmitter is disabled', function() {
|
||||
registerSimpleTestHandler();
|
||||
ReactEventEmitter.setEnabled(false);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(0);
|
||||
ReactEventEmitter.setEnabled(true);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
});
|
||||
it(
|
||||
'should not invoke handlers if ReactBrowserEventEmitter is disabled',
|
||||
function() {
|
||||
registerSimpleTestHandler();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(0);
|
||||
ReactBrowserEventEmitter.setEnabled(true);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
}
|
||||
);
|
||||
|
||||
it('should bubble simply', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(PARENT))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(GRANDPARENT))
|
||||
@@ -172,7 +179,7 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should set currentTarget', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
function(event) {
|
||||
@@ -180,7 +187,7 @@ describe('ReactEventEmitter', function() {
|
||||
expect(event.currentTarget).toBe(CHILD);
|
||||
}
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
function(event) {
|
||||
@@ -188,7 +195,7 @@ describe('ReactEventEmitter', function() {
|
||||
expect(event.currentTarget).toBe(PARENT);
|
||||
}
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_CLICK_KEY,
|
||||
function(event) {
|
||||
@@ -204,17 +211,17 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should support stopPropagation()', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordIDAndStopPropagation.bind(null, getID(PARENT))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(GRANDPARENT))
|
||||
@@ -226,17 +233,17 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should stop after first dispatch if stopPropagation', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
recordIDAndStopPropagation.bind(null, getID(CHILD))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(PARENT))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(GRANDPARENT))
|
||||
@@ -247,17 +254,17 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should stopPropagation if false is returned', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
recordIDAndReturnFalse.bind(null, getID(CHILD))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(PARENT))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_CLICK_KEY,
|
||||
recordID.bind(null, getID(GRANDPARENT))
|
||||
@@ -279,10 +286,14 @@ describe('ReactEventEmitter', function() {
|
||||
it('should invoke handlers that were removed while bubbling', function() {
|
||||
var handleParentClick = mocks.getMockFunction();
|
||||
var handleChildClick = function(event) {
|
||||
ReactEventEmitter.deleteAllListeners(getID(PARENT));
|
||||
ReactBrowserEventEmitter.deleteAllListeners(getID(PARENT));
|
||||
};
|
||||
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
handleChildClick
|
||||
);
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
handleParentClick
|
||||
@@ -294,19 +305,23 @@ describe('ReactEventEmitter', function() {
|
||||
it('should not invoke newly inserted handlers while bubbling', function() {
|
||||
var handleParentClick = mocks.getMockFunction();
|
||||
var handleChildClick = function(event) {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_CLICK_KEY,
|
||||
handleParentClick
|
||||
);
|
||||
};
|
||||
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_CLICK_KEY,
|
||||
handleChildClick
|
||||
);
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(handleParentClick.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should infer onTouchTap from a touchStart/End', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
@@ -324,7 +339,7 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should infer onTouchTap from when dragging below threshold', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
@@ -342,7 +357,7 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should not onTouchTap from when dragging beyond threshold', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
@@ -360,15 +375,15 @@ describe('ReactEventEmitter', function() {
|
||||
|
||||
it('should listen to events only once', function() {
|
||||
spyOn(EventListener, 'listen');
|
||||
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
expect(EventListener.listen.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with event plugins without dependencies', function() {
|
||||
spyOn(EventListener, 'listen');
|
||||
|
||||
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
|
||||
expect(EventListener.listen.argsForCall[0][1]).toBe('click');
|
||||
});
|
||||
@@ -377,7 +392,7 @@ describe('ReactEventEmitter', function() {
|
||||
spyOn(EventListener, 'listen');
|
||||
spyOn(EventListener, 'capture');
|
||||
|
||||
ReactEventEmitter.listenTo(ON_CHANGE_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);
|
||||
|
||||
var setEventListeners = [];
|
||||
var listenCalls = EventListener.listen.argsForCall;
|
||||
@@ -389,7 +404,8 @@ describe('ReactEventEmitter', function() {
|
||||
setEventListeners.push(captureCalls[i][1]);
|
||||
}
|
||||
|
||||
var module = ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY];
|
||||
var module =
|
||||
ReactBrowserEventEmitter.registrationNameModules[ON_CHANGE_KEY];
|
||||
var dependencies = module.eventTypes.change.dependencies;
|
||||
expect(setEventListeners.length).toEqual(dependencies.length);
|
||||
|
||||
@@ -399,17 +415,17 @@ describe('ReactEventEmitter', function() {
|
||||
});
|
||||
|
||||
it('should bubble onTouchTap', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(CHILD),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(CHILD))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(PARENT),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(PARENT))
|
||||
);
|
||||
ReactEventEmitter.putListener(
|
||||
ReactBrowserEventEmitter.putListener(
|
||||
getID(GRANDPARENT),
|
||||
ON_TOUCH_TAP_KEY,
|
||||
recordID.bind(null, getID(GRANDPARENT))
|
||||
@@ -85,7 +85,7 @@ function manualDispatchChangeEvent(nativeEvent) {
|
||||
EventPropagators.accumulateTwoPhaseDispatches(event);
|
||||
|
||||
// If change and propertychange bubbled, we'd just bind to it like all the
|
||||
// other events and have it go through ReactEventTopLevelCallback. Since it
|
||||
// other events and have it go through ReactBrowserEventEmitter. Since it
|
||||
// doesn't, we manually listen for the events and so we have to enqueue and
|
||||
// process the abstract event manually.
|
||||
//
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('AnalyticsEventPlugin', function() {
|
||||
var EventPluginHub;
|
||||
var EventPluginRegistry;
|
||||
var React;
|
||||
var ReactEventEmitter;
|
||||
var ReactBrowserEventEmitter;
|
||||
var ReactTestUtils;
|
||||
|
||||
var DefaultEventPluginOrder;
|
||||
@@ -40,7 +40,7 @@ describe('AnalyticsEventPlugin', function() {
|
||||
EventPluginHub = require('EventPluginHub');
|
||||
EventPluginRegistry = require('EventPluginRegistry');
|
||||
React = require('React');
|
||||
ReactEventEmitter = require('ReactEventEmitter');
|
||||
ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
EventPluginRegistry._resetEventPlugins();
|
||||
|
||||
@@ -24,7 +24,7 @@ var DOMProperty = require('DOMProperty');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
@@ -35,9 +35,9 @@ var keyOf = require('keyOf');
|
||||
var merge = require('merge');
|
||||
var mixInto = require('mixInto');
|
||||
|
||||
var deleteListener = ReactEventEmitter.deleteListener;
|
||||
var listenTo = ReactEventEmitter.listenTo;
|
||||
var registrationNameModules = ReactEventEmitter.registrationNameModules;
|
||||
var deleteListener = ReactBrowserEventEmitter.deleteListener;
|
||||
var listenTo = ReactBrowserEventEmitter.listenTo;
|
||||
var registrationNameModules = ReactBrowserEventEmitter.registrationNameModules;
|
||||
|
||||
// For quickly matching children type, to test if can be treated as content.
|
||||
var CONTENT_TYPES = {'string': true, 'number': true};
|
||||
@@ -404,7 +404,7 @@ ReactDOMComponent.Mixin = {
|
||||
*/
|
||||
unmountComponent: function() {
|
||||
this.unmountChildren();
|
||||
ReactEventEmitter.deleteAllListeners(this._rootNodeID);
|
||||
ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID);
|
||||
ReactComponent.Mixin.unmountComponent.call(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ var ReactDOMInput = require('ReactDOMInput');
|
||||
var ReactDOMOption = require('ReactDOMOption');
|
||||
var ReactDOMSelect = require('ReactDOMSelect');
|
||||
var ReactDOMTextarea = require('ReactDOMTextarea');
|
||||
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
var ReactEventListener = require('ReactEventListener');
|
||||
var ReactInjection = require('ReactInjection');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactMount = require('ReactMount');
|
||||
@@ -51,8 +51,8 @@ var SVGDOMPropertyConfig = require('SVGDOMPropertyConfig');
|
||||
var createFullPageComponent = require('createFullPageComponent');
|
||||
|
||||
function inject() {
|
||||
ReactInjection.EventEmitter.injectTopLevelCallbackCreator(
|
||||
ReactEventTopLevelCallback
|
||||
ReactInjection.EventEmitter.injectReactEventListener(
|
||||
ReactEventListener
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
+119
-91
@@ -13,27 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactEventTopLevelCallback
|
||||
* @providesModule ReactEventListener
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventListener = require('EventListener');
|
||||
var PooledClass = require('PooledClass');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var getEventTarget = require('getEventTarget');
|
||||
var getUnboundedScrollPosition = require('getUnboundedScrollPosition');
|
||||
var mixInto = require('mixInto');
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
var _topLevelListenersEnabled = true;
|
||||
|
||||
/**
|
||||
* Finds the parent React component of `node`.
|
||||
*
|
||||
@@ -52,40 +47,6 @@ function findParent(node) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls ReactEventEmitter.handleTopLevel for each node stored in bookKeeping's
|
||||
* ancestor list. Separated from createTopLevelCallback to avoid try/finally
|
||||
* deoptimization.
|
||||
*
|
||||
* @param {TopLevelCallbackBookKeeping} bookKeeping
|
||||
*/
|
||||
function handleTopLevelImpl(bookKeeping) {
|
||||
var topLevelTarget = ReactMount.getFirstReactDOM(
|
||||
getEventTarget(bookKeeping.nativeEvent)
|
||||
) || window;
|
||||
|
||||
// Loop through the hierarchy, in case there's any nested components.
|
||||
// It's important that we build the array of ancestors before calling any
|
||||
// event handlers, because event handlers can modify the DOM, leading to
|
||||
// inconsistencies with ReactMount's node cache. See #1105.
|
||||
var ancestor = topLevelTarget;
|
||||
while (ancestor) {
|
||||
bookKeeping.ancestors.push(ancestor);
|
||||
ancestor = findParent(ancestor);
|
||||
}
|
||||
|
||||
for (var i = 0, l = bookKeeping.ancestors.length; i < l; i++) {
|
||||
topLevelTarget = bookKeeping.ancestors[i];
|
||||
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
|
||||
ReactEventEmitter.handleTopLevel(
|
||||
bookKeeping.topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
bookKeeping.nativeEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Used to store ancestor hierarchy in top level callback
|
||||
function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
|
||||
this.topLevelType = topLevelType;
|
||||
@@ -104,57 +65,124 @@ PooledClass.addPoolingTo(
|
||||
PooledClass.twoArgumentPooler
|
||||
);
|
||||
|
||||
/**
|
||||
* Top-level callback creator used to implement event handling using delegation.
|
||||
* This is used via dependency injection.
|
||||
*/
|
||||
var ReactEventTopLevelCallback = {
|
||||
function handleTopLevelImpl(bookKeeping) {
|
||||
var topLevelTarget = ReactMount.getFirstReactDOM(
|
||||
getEventTarget(bookKeeping.nativeEvent)
|
||||
) || window;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @return {function} Callback for handling top-level events.
|
||||
*/
|
||||
createTopLevelCallback: function(topLevelType) {
|
||||
return function(nativeEvent) {
|
||||
if (!_topLevelListenersEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
|
||||
topLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
try {
|
||||
// Event queue being processed in the same cycle allows
|
||||
// `preventDefault`.
|
||||
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
|
||||
} finally {
|
||||
TopLevelCallbackBookKeeping.release(bookKeeping);
|
||||
}
|
||||
};
|
||||
// Loop through the hierarchy, in case there's any nested components.
|
||||
// It's important that we build the array of ancestors before calling any
|
||||
// event handlers, because event handlers can modify the DOM, leading to
|
||||
// inconsistencies with ReactMount's node cache. See #1105.
|
||||
var ancestor = topLevelTarget;
|
||||
while (ancestor) {
|
||||
bookKeeping.ancestors.push(ancestor);
|
||||
ancestor = findParent(ancestor);
|
||||
}
|
||||
|
||||
for (var i = 0, l = bookKeeping.ancestors.length; i < l; i++) {
|
||||
topLevelTarget = bookKeeping.ancestors[i];
|
||||
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
|
||||
ReactEventListener._handleTopLevel(
|
||||
bookKeeping.topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
bookKeeping.nativeEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function scrollValueMonitor(cb) {
|
||||
var scrollPosition = getUnboundedScrollPosition(window);
|
||||
cb(scrollPosition);
|
||||
}
|
||||
|
||||
var ReactEventListener = {
|
||||
_enabled: true,
|
||||
_handleTopLevel: null,
|
||||
|
||||
WINDOW_HANDLE: window,
|
||||
|
||||
setHandleTopLevel: function(handleTopLevel) {
|
||||
ReactEventListener._handleTopLevel = handleTopLevel;
|
||||
},
|
||||
|
||||
setEnabled: function(enabled) {
|
||||
ReactEventListener._enabled = !!enabled;
|
||||
},
|
||||
|
||||
isEnabled: function() {
|
||||
return ReactEventListener._enabled;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {object} handle Element on which to attach listener.
|
||||
* @return {object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
|
||||
var element = handle;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
return EventListener.listen(
|
||||
element,
|
||||
handlerBaseName,
|
||||
ReactEventListener.dispatchEvent.bind(null, 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 {object} handle Element on which to attach listener.
|
||||
* @return {object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
|
||||
var element = handle;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
return EventListener.capture(
|
||||
element,
|
||||
handlerBaseName,
|
||||
ReactEventListener.dispatchEvent.bind(null, topLevelType)
|
||||
);
|
||||
},
|
||||
|
||||
monitorScrollValue: function(refresh) {
|
||||
var callback = scrollValueMonitor.bind(null, refresh);
|
||||
EventListener.listen(window, 'scroll', callback);
|
||||
EventListener.listen(window, 'resize', callback);
|
||||
},
|
||||
|
||||
dispatchEvent: function(topLevelType, nativeEvent) {
|
||||
if (!ReactEventListener._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
|
||||
topLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
try {
|
||||
// Event queue being processed in the same cycle allows
|
||||
// `preventDefault`.
|
||||
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
|
||||
} finally {
|
||||
TopLevelCallbackBookKeeping.release(bookKeeping);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactEventTopLevelCallback;
|
||||
module.exports = ReactEventListener;
|
||||
@@ -24,7 +24,7 @@ var ReactComponent = require('ReactComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactRootIndex = require('ReactRootIndex');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
@@ -36,7 +36,7 @@ var ReactInjection = {
|
||||
EmptyComponent: ReactEmptyComponent.injection,
|
||||
EventPluginHub: EventPluginHub.injection,
|
||||
DOM: ReactDOM.injection,
|
||||
EventEmitter: ReactEventEmitter.injection,
|
||||
EventEmitter: ReactBrowserEventEmitter.injection,
|
||||
Perf: ReactPerf.injection,
|
||||
RootIndex: ReactRootIndex.injection,
|
||||
Updates: ReactUpdates.injection
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
@@ -278,7 +278,7 @@ var ReactMount = {
|
||||
'_registerComponent(...): Target container is not a DOM element.'
|
||||
);
|
||||
|
||||
ReactEventEmitter.ensureScrollValueMonitoring();
|
||||
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
|
||||
|
||||
var reactRootID = ReactMount.registerContainer(container);
|
||||
instancesByReactRootID[reactRootID] = nextComponent;
|
||||
|
||||
@@ -388,7 +388,7 @@ describe('ReactDOMComponent', function() {
|
||||
describe('unmountComponent', function() {
|
||||
it("should clean up listeners", function() {
|
||||
var React = require('React');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var container = document.createElement('div');
|
||||
@@ -401,13 +401,13 @@ describe('ReactDOMComponent', function() {
|
||||
var rootNode = instance.getDOMNode();
|
||||
var rootNodeID = ReactMount.getID(rootNode);
|
||||
expect(
|
||||
ReactEventEmitter.getListener(rootNodeID, 'onClick')
|
||||
ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')
|
||||
).toBe(callback);
|
||||
|
||||
React.unmountComponentAtNode(container);
|
||||
|
||||
expect(
|
||||
ReactEventEmitter.getListener(rootNodeID, 'onClick')
|
||||
ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')
|
||||
).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
+36
-27
@@ -19,23 +19,27 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
require('mock-modules')
|
||||
.mock('ReactEventEmitter');
|
||||
var mocks = require('mocks');
|
||||
|
||||
|
||||
var EVENT_TARGET_PARAM = 1;
|
||||
|
||||
describe('ReactEventTopLevelCallback', function() {
|
||||
describe('ReactEventListener', function() {
|
||||
var React;
|
||||
var ReactEventTopLevelCallback;
|
||||
|
||||
var ReactMount;
|
||||
var ReactEventEmitter; // mocked
|
||||
var ReactEventListener;
|
||||
var handleTopLevel;
|
||||
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
React = require('React');
|
||||
ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
|
||||
ReactMount = require('ReactMount');
|
||||
ReactEventEmitter = require('ReactEventEmitter'); // mocked
|
||||
ReactEventListener = require('ReactEventListener');
|
||||
|
||||
handleTopLevel = mocks.getMockFunction();
|
||||
ReactEventListener._handleTopLevel = handleTopLevel;
|
||||
});
|
||||
|
||||
describe('Propagation', function() {
|
||||
@@ -45,15 +49,16 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var parentContainer = document.createElement('div');
|
||||
var parentControl = <div>Parent</div>;
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl =
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
|
||||
callback({
|
||||
target: childControl.getDOMNode()
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
var calls = handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childControl.getDOMNode());
|
||||
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
|
||||
@@ -67,17 +72,19 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var grandParentContainer = document.createElement('div');
|
||||
var grandParentControl = <div>Parent</div>;
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
grandParentControl = ReactMount.renderComponent(grandParentControl, grandParentContainer);
|
||||
parentControl =
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
grandParentControl =
|
||||
ReactMount.renderComponent(grandParentControl, grandParentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
grandParentControl.getDOMNode().appendChild(parentContainer);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
|
||||
callback({
|
||||
target: childControl.getDOMNode()
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
var calls = handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(3);
|
||||
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childControl.getDOMNode());
|
||||
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
|
||||
@@ -91,15 +98,16 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var parentContainer = document.createElement('div');
|
||||
var parentControl = <div>Parent</div>;
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl =
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
|
||||
// ReactEventEmitter.handleTopLevel might remove the target from the DOM.
|
||||
// Here, we have handleTopLevel remove the node when the first event
|
||||
// handlers are called; we'll still expect to receive a second call for
|
||||
// the parent control.
|
||||
// ReactBrowserEventEmitter.handleTopLevel might remove the
|
||||
// target from the DOM. Here, we have handleTopLevel remove the
|
||||
// node when the first event handlers are called; we'll still
|
||||
// expect to receive a second call for the parent control.
|
||||
var childNode = childControl.getDOMNode();
|
||||
ReactEventEmitter.handleTopLevel.mockImplementation(
|
||||
handleTopLevel.mockImplementation(
|
||||
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
|
||||
if (topLevelTarget === childNode) {
|
||||
ReactMount.unmountComponentAtNode(childContainer);
|
||||
@@ -107,12 +115,12 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
}
|
||||
);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
|
||||
callback({
|
||||
target: childNode
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
var calls = handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childNode);
|
||||
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
|
||||
@@ -134,7 +142,7 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
// Suppose an event handler in each root enqueues an update to the
|
||||
// childControl element -- the two updates should get batched together.
|
||||
var childNode = childControl.getDOMNode();
|
||||
ReactEventEmitter.handleTopLevel.mockImplementation(
|
||||
handleTopLevel.mockImplementation(
|
||||
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
|
||||
ReactMount.renderComponent(
|
||||
<div>{topLevelTarget === childNode ? '1' : '2'}</div>,
|
||||
@@ -145,12 +153,13 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
}
|
||||
);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
var callback =
|
||||
ReactEventListener.dispatchEvent.bind(ReactEventListener, 'test');
|
||||
callback({
|
||||
target: childNode
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
var calls = handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(2);
|
||||
expect(childNode.textContent).toBe('2');
|
||||
});
|
||||
@@ -173,12 +182,12 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
|
||||
var instance = ReactMount.renderComponent(<Wrapper />, container);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
|
||||
callback({
|
||||
target: instance.getInner().getDOMNode()
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
var calls = handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(1);
|
||||
expect(calls[0][EVENT_TARGET_PARAM]).toBe(instance.getInner().getDOMNode());
|
||||
});
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
|
||||
var accumulate = require('accumulate');
|
||||
var forEachAccumulated = require('forEachAccumulated');
|
||||
@@ -31,7 +31,7 @@ function remove(event) {
|
||||
var LocalEventTrapMixin = {
|
||||
trapBubbledEvent(topLevelType, handlerBaseName) {
|
||||
invariant(this.isMounted(), 'Must be mounted to trap events');
|
||||
var listener = ReactEventEmitter.trapBubbledEvent(
|
||||
var listener = ReactBrowserEventEmitter.trapBubbledEvent(
|
||||
topLevelType,
|
||||
handlerBaseName,
|
||||
this.getDOMNode()
|
||||
|
||||
@@ -24,7 +24,7 @@ var EventPropagators = require('EventPropagators');
|
||||
var React = require('React');
|
||||
var ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactTextComponent = require('ReactTextComponent');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
@@ -259,12 +259,11 @@ var ReactTestUtils = {
|
||||
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
simulateNativeEventOnNode: function(topLevelType, node, fakeNativeEvent) {
|
||||
var virtualHandler =
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
);
|
||||
fakeNativeEvent.target = node;
|
||||
virtualHandler(fakeNativeEvent);
|
||||
ReactBrowserEventEmitter.ReactEventListener.dispatchEvent(
|
||||
topLevelType,
|
||||
fakeNativeEvent
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -319,7 +318,7 @@ function makeSimulator(eventType) {
|
||||
// We don't use SyntheticEvent.getPooled in order to not have to worry about
|
||||
// properly destroying any properties assigned from `eventData` upon release
|
||||
var event = new SyntheticEvent(
|
||||
ReactEventEmitter.eventNameDispatchConfigs[eventType],
|
||||
ReactBrowserEventEmitter.eventNameDispatchConfigs[eventType],
|
||||
ReactMount.getID(node),
|
||||
fakeNativeEvent
|
||||
);
|
||||
@@ -337,7 +336,7 @@ function buildSimulators() {
|
||||
ReactTestUtils.Simulate = {};
|
||||
|
||||
var eventType;
|
||||
for (eventType in ReactEventEmitter.eventNameDispatchConfigs) {
|
||||
for (eventType in ReactBrowserEventEmitter.eventNameDispatchConfigs) {
|
||||
/**
|
||||
* @param {!Element || ReactDOMComponent} domComponentOrNode
|
||||
* @param {?object} eventData Fake event data to use in SyntheticEvent.
|
||||
|
||||
Reference in New Issue
Block a user