mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Listen to events on demand
Fixes #381 This is a squashed version of https://github.com/facebook/react/pull/462
This commit is contained in:
committed by
Paul O’Shannessy
parent
a575e93ecd
commit
80d7d2d0f8
+2
-1
@@ -19,6 +19,7 @@
|
||||
"use strict";
|
||||
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactContext = require('ReactContext');
|
||||
@@ -40,7 +41,7 @@ var React = {
|
||||
DOM: ReactDOM,
|
||||
PropTypes: ReactPropTypes,
|
||||
initializeTouchEvents: function(shouldUseTouch) {
|
||||
ReactMount.useTouchEvents = shouldUseTouch;
|
||||
EventPluginUtils.useTouchEvents = shouldUseTouch;
|
||||
},
|
||||
createClass: ReactCompositeComponent.createClass,
|
||||
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
|
||||
|
||||
@@ -24,6 +24,7 @@ var DOMProperty = require('DOMProperty');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
@@ -33,15 +34,17 @@ var keyOf = require('keyOf');
|
||||
var merge = require('merge');
|
||||
var mixInto = require('mixInto');
|
||||
|
||||
var putListener = ReactEventEmitter.putListener;
|
||||
var deleteListener = ReactEventEmitter.deleteListener;
|
||||
var registrationNames = ReactEventEmitter.registrationNames;
|
||||
var listenTo = ReactEventEmitter.listenTo;
|
||||
var registrationNameModules = ReactEventEmitter.registrationNameModules;
|
||||
|
||||
// For quickly matching children type, to test if can be treated as content.
|
||||
var CONTENT_TYPES = {'string': true, 'number': true};
|
||||
|
||||
var STYLE = keyOf({style: null});
|
||||
|
||||
var ELEMENT_NODE_TYPE = 1;
|
||||
|
||||
/**
|
||||
* @param {?object} props
|
||||
*/
|
||||
@@ -61,6 +64,19 @@ function assertValidProps(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function putListener(id, registrationName, listener) {
|
||||
var container = ReactMount.findReactContainerForID(id);
|
||||
if (container) {
|
||||
var doc = container.nodeType === ELEMENT_NODE_TYPE ?
|
||||
container.ownerDocument :
|
||||
container;
|
||||
listenTo(registrationName, doc);
|
||||
}
|
||||
|
||||
ReactEventEmitter.putListener(id, registrationName, listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @constructor ReactDOMComponent
|
||||
* @extends ReactComponent
|
||||
@@ -126,7 +142,7 @@ ReactDOMComponent.Mixin = {
|
||||
if (propValue == null) {
|
||||
continue;
|
||||
}
|
||||
if (registrationNames[propKey]) {
|
||||
if (registrationNameModules[propKey]) {
|
||||
putListener(this._rootNodeID, propKey, propValue);
|
||||
} else {
|
||||
if (propKey === STYLE) {
|
||||
@@ -243,7 +259,7 @@ ReactDOMComponent.Mixin = {
|
||||
styleUpdates[styleName] = '';
|
||||
}
|
||||
}
|
||||
} else if (registrationNames[propKey]) {
|
||||
} else if (registrationNameModules[propKey]) {
|
||||
deleteListener(this._rootNodeID, propKey);
|
||||
} else if (
|
||||
DOMProperty.isStandardName[propKey] ||
|
||||
@@ -285,7 +301,7 @@ ReactDOMComponent.Mixin = {
|
||||
// Relies on `updateStylesByID` not mutating `styleUpdates`.
|
||||
styleUpdates = nextProp;
|
||||
}
|
||||
} else if (registrationNames[propKey]) {
|
||||
} else if (registrationNameModules[propKey]) {
|
||||
putListener(this._rootNodeID, propKey, nextProp);
|
||||
} else if (
|
||||
DOMProperty.isStandardName[propKey] ||
|
||||
|
||||
+123
-139
@@ -22,6 +22,7 @@
|
||||
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');
|
||||
@@ -75,6 +76,62 @@ var merge = require('merge');
|
||||
* React Core . General Purpose Event Plugin System
|
||||
*/
|
||||
|
||||
var alreadyListeningTo = {};
|
||||
var isMonitoringScrollValue = false;
|
||||
var reactTopListenersCounter = 0;
|
||||
var topEventMapping = {
|
||||
topBlur: 'blur',
|
||||
topChange: 'change',
|
||||
topClick: 'click',
|
||||
topCompositionEnd: 'compositionend',
|
||||
topCompositionStart: 'compositionstart',
|
||||
topCompositionUpdate: 'compositionupdate',
|
||||
topContextMenu: 'contextmenu',
|
||||
topCopy: 'copy',
|
||||
topCut: 'cut',
|
||||
topDoubleClick: 'dblclick',
|
||||
topDrag: 'drag',
|
||||
topDragEnd: 'dragend',
|
||||
topDragEnter: 'dragenter',
|
||||
topDragExit: 'dragexit',
|
||||
topDragLeave: 'dragleave',
|
||||
topDragOver: 'dragover',
|
||||
topDragStart: 'dragstart',
|
||||
topDrop: 'drop',
|
||||
topFocus: 'focus',
|
||||
topInput: 'input',
|
||||
topKeyDown: 'keydown',
|
||||
topKeyPress: 'keypress',
|
||||
topKeyUp: 'keyup',
|
||||
topMouseDown: 'mousedown',
|
||||
topMouseMove: 'mousemove',
|
||||
topMouseOut: 'mouseout',
|
||||
topMouseOver: 'mouseover',
|
||||
topMouseUp: 'mouseup',
|
||||
topPaste: 'paste',
|
||||
topScroll: 'scroll',
|
||||
topSelectionChange: 'selectionchange',
|
||||
topSubmit: 'submit',
|
||||
topTouchCancel: 'touchcancel',
|
||||
topTouchEnd: 'touchend',
|
||||
topTouchMove: 'touchmove',
|
||||
topTouchStart: 'touchstart',
|
||||
topWheel: 'wheel'
|
||||
};
|
||||
|
||||
/**
|
||||
* To ensure no conflicts with other potential React instances on the page
|
||||
*/
|
||||
var topListenersIDKey = "_reactListenersID" + String(Math.random()).slice(2);
|
||||
|
||||
function getListeningDocument(mountAt) {
|
||||
if (mountAt[topListenersIDKey] == null) {
|
||||
mountAt[topListenersIDKey] = reactTopListenersCounter++;
|
||||
alreadyListeningTo[mountAt[topListenersIDKey]] = {};
|
||||
}
|
||||
return alreadyListeningTo[mountAt[topListenersIDKey]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
*
|
||||
@@ -111,21 +168,6 @@ function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to window scroll and resize events. We cache scroll values so that
|
||||
* application code can access them without triggering reflows.
|
||||
*
|
||||
* NOTE: Scroll events do not bubble.
|
||||
*
|
||||
* @private
|
||||
* @see http://www.quirksmode.org/dom/events/scroll.html
|
||||
*/
|
||||
function registerScrollValueMonitoring() {
|
||||
var refresh = ViewportMetrics.refreshScrollValues;
|
||||
EventListener.listen(window, 'scroll', refresh);
|
||||
EventListener.listen(window, 'resize', refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
|
||||
*
|
||||
@@ -143,39 +185,6 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
*/
|
||||
TopLevelCallbackCreator: null,
|
||||
|
||||
/**
|
||||
* Ensures that top-level event delegation listeners are installed.
|
||||
*
|
||||
* There are issues with listening to both touch events and mouse events on
|
||||
* the top-level, so we make the caller choose which one to listen to. (If
|
||||
* there's a touch top-level listeners, anchors don't receive clicks for some
|
||||
* reason, and only in some cases).
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @param {DOMDocument} contentDocument DOM document to listen on
|
||||
*/
|
||||
ensureListening: function(touchNotMouse, contentDocument) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'ensureListening(...): Cannot toggle event listening in a Worker ' +
|
||||
'thread. This is likely a bug in the framework. Please report ' +
|
||||
'immediately.'
|
||||
);
|
||||
invariant(
|
||||
ReactEventEmitter.TopLevelCallbackCreator,
|
||||
'ensureListening(...): Cannot be called without a top level callback ' +
|
||||
'creator being injected.'
|
||||
);
|
||||
// Call out to base implementation.
|
||||
ReactEventEmitterMixin.ensureListening.call(
|
||||
ReactEventEmitter,
|
||||
{
|
||||
touchNotMouse: touchNotMouse,
|
||||
contentDocument: contentDocument
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets whether or not any created callbacks should be enabled.
|
||||
*
|
||||
@@ -220,108 +229,84 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
|
||||
* they bubble to document.
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @param {DOMDocument} contentDocument Document which owns the container
|
||||
* @private
|
||||
* @see http://www.quirksmode.org/dom/events/keys.html.
|
||||
*/
|
||||
listenAtTopLevel: function(touchNotMouse, contentDocument) {
|
||||
invariant(
|
||||
!contentDocument._isListening,
|
||||
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
|
||||
);
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
listenTo: function(registrationName, contentDocument) {
|
||||
var mountAt = contentDocument;
|
||||
var listeningDocument = getListeningDocument(mountAt);
|
||||
var dependencies = EventPluginRegistry.
|
||||
registrationNameDependencies[registrationName];
|
||||
|
||||
registerScrollValueMonitoring();
|
||||
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topContextMenu, 'contextmenu', mountAt);
|
||||
if (touchNotMouse) {
|
||||
trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
|
||||
}
|
||||
trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
|
||||
trapBubbledEvent(
|
||||
topLevelTypes.topSelectionChange,
|
||||
'selectionchange',
|
||||
mountAt
|
||||
);
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
for (var i = 0, l = dependencies.length; i < l; i++) {
|
||||
var dependency = dependencies[i];
|
||||
if (!listeningDocument[dependency]) {
|
||||
var topLevelType = topLevelTypes[dependency];
|
||||
|
||||
trapBubbledEvent(
|
||||
topLevelTypes.topCompositionEnd,
|
||||
'compositionend',
|
||||
mountAt
|
||||
);
|
||||
trapBubbledEvent(
|
||||
topLevelTypes.topCompositionStart,
|
||||
'compositionstart',
|
||||
mountAt
|
||||
);
|
||||
trapBubbledEvent(
|
||||
topLevelTypes.topCompositionUpdate,
|
||||
'compositionupdate',
|
||||
mountAt
|
||||
);
|
||||
if (topLevelType === topLevelTypes.topWheel) {
|
||||
if (isEventSupported('wheel')) {
|
||||
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
|
||||
} else if (isEventSupported('mousewheel')) {
|
||||
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(
|
||||
topLevelTypes.topWheel,
|
||||
'DOMMouseScroll',
|
||||
mountAt);
|
||||
}
|
||||
} else if (topLevelType === topLevelTypes.topScroll) {
|
||||
|
||||
if (isEventSupported('drag')) {
|
||||
trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
|
||||
}
|
||||
if (isEventSupported('scroll', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
|
||||
} else {
|
||||
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
|
||||
}
|
||||
} else if (topLevelType === topLevelTypes.topFocus ||
|
||||
topLevelType === topLevelTypes.topBlur) {
|
||||
|
||||
if (isEventSupported('wheel')) {
|
||||
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
|
||||
} else if (isEventSupported('mousewheel')) {
|
||||
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(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
|
||||
}
|
||||
if (isEventSupported('focus', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
|
||||
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);
|
||||
}
|
||||
|
||||
// IE<9 does not support capturing so just trap the bubbled event there.
|
||||
if (isEventSupported('scroll', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
|
||||
} else {
|
||||
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
|
||||
}
|
||||
// to make sure blur and focus event listeners are only attached once
|
||||
listeningDocument[topLevelTypes.topBlur] = true;
|
||||
listeningDocument[topLevelTypes.topFocus] = true;
|
||||
} else {
|
||||
trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
|
||||
}
|
||||
|
||||
if (isEventSupported('focus', true)) {
|
||||
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
|
||||
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);
|
||||
}
|
||||
|
||||
if (isEventSupported('copy')) {
|
||||
trapBubbledEvent(topLevelTypes.topCopy, 'copy', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt);
|
||||
trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt);
|
||||
listeningDocument[dependency] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
registrationNames: EventPluginHub.registrationNames,
|
||||
/**
|
||||
* Listens to window scroll and resize events. We cache scroll values so that
|
||||
* application code can access them without triggering reflows.
|
||||
*
|
||||
* NOTE: Scroll events do not bubble.
|
||||
*
|
||||
* @see http://www.quirksmode.org/dom/events/scroll.html
|
||||
*/
|
||||
ensureScrollValueMonitoring: function(){
|
||||
if (!isMonitoringScrollValue) {
|
||||
var refresh = ViewportMetrics.refreshScrollValues;
|
||||
EventListener.listen(window, 'scroll', refresh);
|
||||
EventListener.listen(window, 'resize', refresh);
|
||||
isMonitoringScrollValue = true;
|
||||
}
|
||||
},
|
||||
|
||||
registrationNameModules: EventPluginHub.registrationNameModules,
|
||||
|
||||
putListener: EventPluginHub.putListener,
|
||||
|
||||
@@ -337,5 +322,4 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
|
||||
});
|
||||
|
||||
|
||||
module.exports = ReactEventEmitter;
|
||||
|
||||
@@ -27,38 +27,6 @@ function runEventQueueInBatch(events) {
|
||||
}
|
||||
|
||||
var ReactEventEmitterMixin = {
|
||||
/**
|
||||
* Whether or not `ensureListening` has been invoked.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isListening: false,
|
||||
|
||||
/**
|
||||
* Function, must be implemented. Listens to events on the top level of the
|
||||
* application.
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* listenAtTopLevel: null,
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ensures that top-level event delegation listeners are installed.
|
||||
*
|
||||
* There are issues with listening to both touch events and mouse events on
|
||||
* the top-level, so we make the caller choose which one to listen to. (If
|
||||
* there's a touch top-level listeners, anchors don't receive clicks for some
|
||||
* reason, and only in some cases).
|
||||
*
|
||||
* @param {*} config Configuration passed through to `listenAtTopLevel`.
|
||||
*/
|
||||
ensureListening: function(config) {
|
||||
if (!config.contentDocument._reactIsListening) {
|
||||
this.listenAtTopLevel(config.touchNotMouse, config.contentDocument);
|
||||
config.contentDocument._reactIsListening = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Streams a fired top-level event to `EventPluginHub` where plugins have the
|
||||
|
||||
+11
-23
@@ -208,27 +208,6 @@ var ReactMount = {
|
||||
renderCallback();
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that the top-level event delegation listener is set up. This will
|
||||
* be invoked some time before the first time any React component is rendered.
|
||||
* @param {DOMElement} container container we're rendering into
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
prepareEnvironmentForDOM: function(container) {
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE
|
||||
),
|
||||
'prepareEnvironmentForDOM(...): Target container is not a DOM element.'
|
||||
);
|
||||
var doc = container.nodeType === ELEMENT_NODE_TYPE ?
|
||||
container.ownerDocument :
|
||||
container;
|
||||
ReactEventEmitter.ensureListening(ReactMount.useTouchEvents, doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a component that's already mounted into the DOM and replace its props
|
||||
* @param {ReactComponent} prevComponent component instance already in the DOM
|
||||
@@ -256,13 +235,22 @@ var ReactMount = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a component into the instance map and start the events system.
|
||||
* Register a component into the instance map and starts scroll value
|
||||
* monitoring
|
||||
* @param {ReactComponent} nextComponent component instance to render
|
||||
* @param {DOMElement} container container to render into
|
||||
* @return {string} reactRoot ID prefix
|
||||
*/
|
||||
_registerComponent: function(nextComponent, container) {
|
||||
ReactMount.prepareEnvironmentForDOM(container);
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE
|
||||
),
|
||||
'_registerComponent(...): Target container is not a DOM element.'
|
||||
);
|
||||
|
||||
ReactEventEmitter.ensureScrollValueMonitoring();
|
||||
|
||||
var reactRootID = ReactMount.registerContainer(container);
|
||||
instancesByReactRootID[reactRootID] = nextComponent;
|
||||
|
||||
@@ -37,14 +37,14 @@ describe('ReactComponent', function() {
|
||||
expect(function() {
|
||||
React.renderComponent(<div></div>, [container]);
|
||||
}).toThrow(
|
||||
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
|
||||
'Invariant Violation: _registerComponent(...): Target container ' +
|
||||
'is not a DOM element.'
|
||||
);
|
||||
|
||||
expect(function() {
|
||||
React.renderComponent(<div></div>, null);
|
||||
}).toThrow(
|
||||
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
|
||||
'Invariant Violation: _registerComponent(...): Target container ' +
|
||||
'is not a DOM element.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ var setID = ReactMount.setID;
|
||||
var ReactEventEmitter;
|
||||
var ReactTestUtils;
|
||||
var TapEventPlugin;
|
||||
var EventListener;
|
||||
|
||||
var tapMoveThreshold;
|
||||
var idCallOrder = [];
|
||||
@@ -58,6 +59,7 @@ var recordIDAndReturnFalse = function(id, event) {
|
||||
var LISTENER = mocks.getMockFunction();
|
||||
var ON_CLICK_KEY = keyOf({onClick: null});
|
||||
var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
|
||||
var ON_CHANGE_KEY = keyOf({onChange: null});
|
||||
|
||||
|
||||
/**
|
||||
@@ -90,13 +92,13 @@ describe('ReactEventEmitter', function() {
|
||||
EventPluginHub = require('EventPluginHub');
|
||||
TapEventPlugin = require('TapEventPlugin');
|
||||
ReactMount = require('ReactMount');
|
||||
EventListener = require('EventListener');
|
||||
getID = ReactMount.getID;
|
||||
setID = ReactMount.setID;
|
||||
ReactEventEmitter = require('ReactEventEmitter');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
idCallOrder = [];
|
||||
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
|
||||
ReactEventEmitter.ensureListening(false, document);
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
TapEventPlugin: TapEventPlugin
|
||||
});
|
||||
@@ -315,6 +317,45 @@ describe('ReactEventEmitter', function() {
|
||||
expect(idCallOrder.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should listen to events only once', function() {
|
||||
spyOn(EventListener, 'listen');
|
||||
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactEventEmitter.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);
|
||||
|
||||
expect(EventListener.listen.argsForCall[0][1]).toBe('click');
|
||||
});
|
||||
|
||||
it('should work with event plugins with dependencies', function() {
|
||||
spyOn(EventListener, 'listen');
|
||||
spyOn(EventListener, 'capture');
|
||||
|
||||
ReactEventEmitter.listenTo(ON_CHANGE_KEY, document);
|
||||
|
||||
var setEventListeners = [];
|
||||
var listenCalls = EventListener.listen.argsForCall;
|
||||
var captureCalls = EventListener.capture.argsForCall;
|
||||
for (var i = 0, l = listenCalls.length; i < l; i++) {
|
||||
setEventListeners.push(listenCalls[i][1]);
|
||||
}
|
||||
for (i = 0, l = captureCalls.length; i < l; i++) {
|
||||
setEventListeners.push(captureCalls[i][1]);
|
||||
}
|
||||
|
||||
var module = ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY];
|
||||
var dependencies = module.eventTypes.change.dependencies;
|
||||
expect(setEventListeners.length).toEqual(dependencies.length);
|
||||
|
||||
for (i = 0, l = setEventListeners.length; i < l; i++) {
|
||||
expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should bubble onTouchTap', function() {
|
||||
ReactEventEmitter.putListener(
|
||||
|
||||
@@ -104,7 +104,7 @@ var EventPluginHub = {
|
||||
|
||||
},
|
||||
|
||||
registrationNames: EventPluginRegistry.registrationNames,
|
||||
registrationNameModules: EventPluginRegistry.registrationNameModules,
|
||||
|
||||
putListener: CallbackRegistry.putListener,
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ function recomputePluginOrdering() {
|
||||
var publishedEvents = PluginModule.eventTypes;
|
||||
for (var eventName in publishedEvents) {
|
||||
invariant(
|
||||
publishEventForPlugin(publishedEvents[eventName], PluginModule),
|
||||
publishEventForPlugin(publishedEvents[eventName], PluginModule, eventName),
|
||||
'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
|
||||
eventName,
|
||||
pluginName
|
||||
@@ -80,18 +80,18 @@ function recomputePluginOrdering() {
|
||||
* @return {boolean} True if the event was successfully published.
|
||||
* @private
|
||||
*/
|
||||
function publishEventForPlugin(dispatchConfig, PluginModule) {
|
||||
function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
|
||||
var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
|
||||
if (phasedRegistrationNames) {
|
||||
for (var phaseName in phasedRegistrationNames) {
|
||||
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
|
||||
var phasedRegistrationName = phasedRegistrationNames[phaseName];
|
||||
publishRegistrationName(phasedRegistrationName, PluginModule);
|
||||
publishRegistrationName(phasedRegistrationName, PluginModule, eventName);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (dispatchConfig.registrationName) {
|
||||
publishRegistrationName(dispatchConfig.registrationName, PluginModule);
|
||||
publishRegistrationName(dispatchConfig.registrationName, PluginModule, eventName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -105,14 +105,16 @@ function publishEventForPlugin(dispatchConfig, PluginModule) {
|
||||
* @param {object} PluginModule Plugin publishing the event.
|
||||
* @private
|
||||
*/
|
||||
function publishRegistrationName(registrationName, PluginModule) {
|
||||
function publishRegistrationName(registrationName, PluginModule, eventName) {
|
||||
invariant(
|
||||
!EventPluginRegistry.registrationNames[registrationName],
|
||||
!EventPluginRegistry.registrationNameModules[registrationName],
|
||||
'EventPluginHub: More than one plugin attempted to publish the same ' +
|
||||
'registration name, `%s`.',
|
||||
registrationName
|
||||
);
|
||||
EventPluginRegistry.registrationNames[registrationName] = PluginModule;
|
||||
EventPluginRegistry.registrationNameModules[registrationName] = PluginModule;
|
||||
EventPluginRegistry.registrationNameDependencies[registrationName] =
|
||||
PluginModule.eventTypes[eventName].dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +132,12 @@ var EventPluginRegistry = {
|
||||
/**
|
||||
* Mapping from registration names to plugin modules.
|
||||
*/
|
||||
registrationNames: {},
|
||||
registrationNameModules: {},
|
||||
|
||||
/**
|
||||
* Mapping from registration name to event name
|
||||
*/
|
||||
registrationNameDependencies: {},
|
||||
|
||||
/**
|
||||
* Injects an ordering of plugins (by plugin name). This allows the ordering
|
||||
@@ -194,7 +201,7 @@ var EventPluginRegistry = {
|
||||
getPluginModuleForEvent: function(event) {
|
||||
var dispatchConfig = event.dispatchConfig;
|
||||
if (dispatchConfig.registrationName) {
|
||||
return EventPluginRegistry.registrationNames[
|
||||
return EventPluginRegistry.registrationNameModules[
|
||||
dispatchConfig.registrationName
|
||||
] || null;
|
||||
}
|
||||
@@ -202,7 +209,7 @@ var EventPluginRegistry = {
|
||||
if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) {
|
||||
continue;
|
||||
}
|
||||
var PluginModule = EventPluginRegistry.registrationNames[
|
||||
var PluginModule = EventPluginRegistry.registrationNameModules[
|
||||
dispatchConfig.phasedRegistrationNames[phase]
|
||||
];
|
||||
if (PluginModule) {
|
||||
@@ -224,10 +231,10 @@ var EventPluginRegistry = {
|
||||
}
|
||||
}
|
||||
EventPluginRegistry.plugins.length = 0;
|
||||
var registrationNames = EventPluginRegistry.registrationNames;
|
||||
for (var registrationName in registrationNames) {
|
||||
if (registrationNames.hasOwnProperty(registrationName)) {
|
||||
delete registrationNames[registrationName];
|
||||
var registrationNameModules = EventPluginRegistry.registrationNameModules;
|
||||
for (var registrationName in registrationNameModules) {
|
||||
if (registrationNameModules.hasOwnProperty(registrationName)) {
|
||||
delete registrationNameModules[registrationName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,8 @@ var EventPluginUtils = {
|
||||
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
|
||||
executeDirectDispatch: executeDirectDispatch,
|
||||
hasDispatches: hasDispatches,
|
||||
executeDispatch: executeDispatch
|
||||
executeDispatch: executeDispatch,
|
||||
useTouchEvents: false
|
||||
};
|
||||
|
||||
module.exports = EventPluginUtils;
|
||||
|
||||
@@ -174,16 +174,16 @@ describe('EventPluginRegistry', function() {
|
||||
EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});
|
||||
EventPluginRegistry.injectEventPluginOrder(['one', 'two']);
|
||||
|
||||
expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(2);
|
||||
expect(EventPluginRegistry.registrationNames.onClick).toBe(OnePlugin);
|
||||
expect(EventPluginRegistry.registrationNames.onFocus).toBe(OnePlugin);
|
||||
expect(Object.keys(EventPluginRegistry.registrationNameModules).length).toBe(2);
|
||||
expect(EventPluginRegistry.registrationNameModules.onClick).toBe(OnePlugin);
|
||||
expect(EventPluginRegistry.registrationNameModules.onFocus).toBe(OnePlugin);
|
||||
|
||||
EventPluginRegistry.injectEventPluginsByName({two: TwoPlugin});
|
||||
|
||||
expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(4);
|
||||
expect(EventPluginRegistry.registrationNames.onMagicBubble).toBe(TwoPlugin);
|
||||
expect(Object.keys(EventPluginRegistry.registrationNameModules).length).toBe(4);
|
||||
expect(EventPluginRegistry.registrationNameModules.onMagicBubble).toBe(TwoPlugin);
|
||||
expect(
|
||||
EventPluginRegistry.registrationNames.onMagicCapture
|
||||
EventPluginRegistry.registrationNameModules.onMagicCapture
|
||||
).toBe(TwoPlugin);
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,15 @@ var eventTypes = {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onChange: null}),
|
||||
captured: keyOf({onChangeCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: [
|
||||
topLevelTypes.topBlur,
|
||||
topLevelTypes.topChange,
|
||||
topLevelTypes.topFocus,
|
||||
topLevelTypes.topInput,
|
||||
topLevelTypes.topKeyDown,
|
||||
topLevelTypes.topKeyUp
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -42,19 +42,43 @@ var eventTypes = {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onCompositionEnd: null}),
|
||||
captured: keyOf({onCompositionEndCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: [
|
||||
topLevelTypes.topBlur,
|
||||
topLevelTypes.topCompositionEnd,
|
||||
topLevelTypes.topKeyDown,
|
||||
topLevelTypes.topKeyPress,
|
||||
topLevelTypes.topKeyUp,
|
||||
topLevelTypes.topMouseDown
|
||||
]
|
||||
},
|
||||
compositionStart: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onCompositionStart: null}),
|
||||
captured: keyOf({onCompositionStartCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: [
|
||||
topLevelTypes.topBlur,
|
||||
topLevelTypes.topCompositionStart,
|
||||
topLevelTypes.topKeyDown,
|
||||
topLevelTypes.topKeyPress,
|
||||
topLevelTypes.topKeyUp,
|
||||
topLevelTypes.topMouseDown
|
||||
]
|
||||
},
|
||||
compositionUpdate: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onCompositionUpdate: null}),
|
||||
captured: keyOf({onCompositionUpdateCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: [
|
||||
topLevelTypes.topBlur,
|
||||
topLevelTypes.topCompositionUpdate,
|
||||
topLevelTypes.topKeyDown,
|
||||
topLevelTypes.topKeyPress,
|
||||
topLevelTypes.topKeyUp,
|
||||
topLevelTypes.topMouseDown
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,8 +30,20 @@ var topLevelTypes = EventConstants.topLevelTypes;
|
||||
var getFirstReactDOM = ReactMount.getFirstReactDOM;
|
||||
|
||||
var eventTypes = {
|
||||
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
|
||||
mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
|
||||
mouseEnter: {
|
||||
registrationName: keyOf({onMouseEnter: null}),
|
||||
dependencies: [
|
||||
topLevelTypes.topMouseOut,
|
||||
topLevelTypes.topMouseOver
|
||||
]
|
||||
},
|
||||
mouseLeave: {
|
||||
registrationName: keyOf({onMouseLeave: null}),
|
||||
dependencies: [
|
||||
topLevelTypes.topMouseOut,
|
||||
topLevelTypes.topMouseOver
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var extractedEvents = [null, null];
|
||||
|
||||
@@ -37,7 +37,15 @@ var eventTypes = {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onSelect: null}),
|
||||
captured: keyOf({onSelectCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: [
|
||||
topLevelTypes.topBlur,
|
||||
topLevelTypes.topFocus,
|
||||
topLevelTypes.topKeyDown,
|
||||
topLevelTypes.topMouseDown,
|
||||
topLevelTypes.topMouseUp,
|
||||
topLevelTypes.topSelectionChange
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -253,6 +253,10 @@ var topLevelEventsToDispatchConfig = {
|
||||
topWheel: eventTypes.wheel
|
||||
};
|
||||
|
||||
for (var topLevelType in topLevelEventsToDispatchConfig) {
|
||||
topLevelEventsToDispatchConfig[topLevelType].dependencies = [topLevelType];
|
||||
}
|
||||
|
||||
var SimpleEventPlugin = {
|
||||
|
||||
eventTypes: eventTypes,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventConstants = require('EventConstants');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var SyntheticUIEvent = require('SyntheticUIEvent');
|
||||
@@ -26,6 +27,7 @@ var TouchEventUtils = require('TouchEventUtils');
|
||||
var ViewportMetrics = require('ViewportMetrics');
|
||||
|
||||
var keyOf = require('keyOf');
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
|
||||
var isStartish = EventPluginUtils.isStartish;
|
||||
var isEndish = EventPluginUtils.isEndish;
|
||||
@@ -61,12 +63,28 @@ function getDistance(coords, nativeEvent) {
|
||||
);
|
||||
}
|
||||
|
||||
var dependencies = [
|
||||
topLevelTypes.topMouseDown,
|
||||
topLevelTypes.topMouseMove,
|
||||
topLevelTypes.topMouseUp
|
||||
];
|
||||
|
||||
if (EventPluginUtils.useTouchEvents) {
|
||||
dependencies.push(
|
||||
topLevelTypes.topTouchCancel,
|
||||
topLevelTypes.topTouchEnd,
|
||||
topLevelTypes.topTouchStart,
|
||||
topLevelTypes.topTouchMove
|
||||
);
|
||||
}
|
||||
|
||||
var eventTypes = {
|
||||
touchTap: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: keyOf({onTouchTap: null}),
|
||||
captured: keyOf({onTouchTapCapture: null})
|
||||
}
|
||||
},
|
||||
dependencies: dependencies
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ describe('AnalyticsEventPlugin', function() {
|
||||
'ChangeEventPlugin': ChangeEventPlugin
|
||||
});
|
||||
|
||||
ReactEventEmitter.ensureListening(false, document);
|
||||
});
|
||||
|
||||
it('should count events correctly', function() {
|
||||
|
||||
Reference in New Issue
Block a user