(firstTarget = n)} />
+ onMouseLeave={() => ops.push('leave parent')}
+ ref={n => (firstTarget = n)}>
+
{ReactDOM.unstable_createPortal(
ops.push('enter portal')}
diff --git a/src/renderers/dom/shared/ReactBrowserEventEmitter.js b/src/renderers/dom/shared/ReactBrowserEventEmitter.js
index 1c2661b57f9ab..7da7cd9bde868 100644
--- a/src/renderers/dom/shared/ReactBrowserEventEmitter.js
+++ b/src/renderers/dom/shared/ReactBrowserEventEmitter.js
@@ -171,6 +171,25 @@ var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, {
'scroll',
mountAt,
);
+ } else if (
+ dependency === 'topMouseEnter' ||
+ dependency === 'topMouseLeave'
+ ) {
+ if (isEventSupported('mouseenter', true)) {
+ ReactDOMEventListener.trapCapturedEvent(
+ 'topMouseEnter',
+ 'mouseenter',
+ mountAt,
+ );
+ ReactDOMEventListener.trapCapturedEvent(
+ 'topMouseLeave',
+ 'mouseleave',
+ mountAt,
+ );
+ }
+
+ isListening.topMouseEnter = true;
+ isListening.topMouseLeave = true;
} else if (dependency === 'topFocus' || dependency === 'topBlur') {
ReactDOMEventListener.trapCapturedEvent('topFocus', 'focus', mountAt);
ReactDOMEventListener.trapCapturedEvent('topBlur', 'blur', mountAt);
diff --git a/src/renderers/dom/shared/ReactDOMClientInjection.js b/src/renderers/dom/shared/ReactDOMClientInjection.js
index dc929517f41cd..89f7ee22c49da 100644
--- a/src/renderers/dom/shared/ReactDOMClientInjection.js
+++ b/src/renderers/dom/shared/ReactDOMClientInjection.js
@@ -14,7 +14,6 @@
var BeforeInputEventPlugin = require('BeforeInputEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var DOMEventPluginOrder = require('DOMEventPluginOrder');
-var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var EventPluginHub = require('EventPluginHub');
var EventPluginUtils = require('EventPluginUtils');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
@@ -39,7 +38,6 @@ EventPluginUtils.injection.injectComponentTree(ReactDOMComponentTree);
*/
EventPluginHub.injection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
- EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,
diff --git a/src/renderers/dom/shared/eventPlugins/DOMEventPluginOrder.js b/src/renderers/dom/shared/eventPlugins/DOMEventPluginOrder.js
index 510ec07ad0b11..2bf3e9e545284 100644
--- a/src/renderers/dom/shared/eventPlugins/DOMEventPluginOrder.js
+++ b/src/renderers/dom/shared/eventPlugins/DOMEventPluginOrder.js
@@ -24,7 +24,6 @@ var DOMEventPluginOrder = [
'ResponderEventPlugin',
'SimpleEventPlugin',
'TapEventPlugin',
- 'EnterLeaveEventPlugin',
'ChangeEventPlugin',
'SelectEventPlugin',
'BeforeInputEventPlugin',
diff --git a/src/renderers/dom/shared/eventPlugins/EnterLeaveEventPlugin.js b/src/renderers/dom/shared/eventPlugins/EnterLeaveEventPlugin.js
deleted file mode 100644
index fa9925f1448cd..0000000000000
--- a/src/renderers/dom/shared/eventPlugins/EnterLeaveEventPlugin.js
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule EnterLeaveEventPlugin
- */
-
-'use strict';
-
-var EventPropagators = require('EventPropagators');
-var ReactDOMComponentTree = require('ReactDOMComponentTree');
-var SyntheticMouseEvent = require('SyntheticMouseEvent');
-
-var eventTypes = {
- mouseEnter: {
- registrationName: 'onMouseEnter',
- dependencies: ['topMouseOut', 'topMouseOver'],
- },
- mouseLeave: {
- registrationName: 'onMouseLeave',
- dependencies: ['topMouseOut', 'topMouseOver'],
- },
-};
-
-var EnterLeaveEventPlugin = {
- 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.
- */
- extractEvents: function(
- topLevelType,
- targetInst,
- nativeEvent,
- nativeEventTarget,
- ) {
- if (
- topLevelType === 'topMouseOver' &&
- (nativeEvent.relatedTarget || nativeEvent.fromElement)
- ) {
- return null;
- }
- if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
- // Must not be a mouse in or mouse out - ignoring.
- return null;
- }
-
- var win;
- if (nativeEventTarget.window === nativeEventTarget) {
- // `nativeEventTarget` is probably a window object.
- win = nativeEventTarget;
- } else {
- // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
- var doc = nativeEventTarget.ownerDocument;
- if (doc) {
- win = doc.defaultView || doc.parentWindow;
- } else {
- win = window;
- }
- }
-
- var from;
- var to;
- if (topLevelType === 'topMouseOut') {
- from = targetInst;
- var related = nativeEvent.relatedTarget || nativeEvent.toElement;
- to = related
- ? ReactDOMComponentTree.getClosestInstanceFromNode(related)
- : null;
- } else {
- // Moving to a node from outside the window.
- from = null;
- to = targetInst;
- }
-
- if (from === to) {
- // Nothing pertains to our managed components.
- return null;
- }
-
- var fromNode = from == null
- ? win
- : ReactDOMComponentTree.getNodeFromInstance(from);
- var toNode = to == null
- ? win
- : ReactDOMComponentTree.getNodeFromInstance(to);
-
- var leave = SyntheticMouseEvent.getPooled(
- eventTypes.mouseLeave,
- from,
- nativeEvent,
- nativeEventTarget,
- );
- leave.type = 'mouseleave';
- leave.target = fromNode;
- leave.relatedTarget = toNode;
-
- var enter = SyntheticMouseEvent.getPooled(
- eventTypes.mouseEnter,
- to,
- nativeEvent,
- nativeEventTarget,
- );
- enter.type = 'mouseenter';
- enter.target = toNode;
- enter.relatedTarget = fromNode;
-
- EventPropagators.accumulateEnterLeaveDispatches(leave, enter, from, to);
-
- return [leave, enter];
- },
-};
-
-module.exports = EnterLeaveEventPlugin;
diff --git a/src/renderers/dom/shared/eventPlugins/SimpleEventPlugin.js b/src/renderers/dom/shared/eventPlugins/SimpleEventPlugin.js
index 7acdeaca06c3b..4effc443ce67b 100644
--- a/src/renderers/dom/shared/eventPlugins/SimpleEventPlugin.js
+++ b/src/renderers/dom/shared/eventPlugins/SimpleEventPlugin.js
@@ -36,6 +36,11 @@ import type {
import type {ReactInstance} from 'ReactInstanceType';
import type {EventTypes, PluginModule} from 'PluginModuleType';
+const topLevelDirectDispatches = {
+ topMouseEnter: true,
+ topMouseLeave: true,
+};
+
/**
* Turns
* ['abort', ...]
@@ -95,6 +100,8 @@ var topLevelEventsToDispatchConfig: {[key: TopLevelTypes]: DispatchConfig} = {};
'loadedMetadata',
'loadStart',
'mouseDown',
+ 'mouseEnter',
+ 'mouseLeave',
'mouseMove',
'mouseOut',
'mouseOver',
@@ -126,14 +133,23 @@ var topLevelEventsToDispatchConfig: {[key: TopLevelTypes]: DispatchConfig} = {};
var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
var onEvent = 'on' + capitalizedEvent;
var topEvent = 'top' + capitalizedEvent;
+ var isPhased = !topLevelDirectDispatches[topEvent];
var type = {
- phasedRegistrationNames: {
- bubbled: onEvent,
- captured: onEvent + 'Capture',
- },
dependencies: [topEvent],
+ phasedRegistrationNames: null,
+ registrationName: null,
};
+
+ if (isPhased) {
+ type.phasedRegistrationNames = {
+ bubbled: onEvent,
+ captured: onEvent + 'Capture',
+ };
+ } else {
+ type.registrationName = onEvent;
+ }
+
eventTypes[event] = type;
topLevelEventsToDispatchConfig[topEvent] = type;
});
@@ -213,6 +229,8 @@ var SimpleEventPlugin: PluginModule
= {
/* falls through */
case 'topDoubleClick':
case 'topMouseDown':
+ case 'topMouseEnter':
+ case 'topMouseLeave':
case 'topMouseMove':
case 'topMouseUp':
// TODO: Disabled elements should not respond to mouse events
@@ -269,7 +287,13 @@ var SimpleEventPlugin: PluginModule = {
nativeEvent,
nativeEventTarget,
);
- EventPropagators.accumulateTwoPhaseDispatches(event);
+
+ if (topLevelDirectDispatches[topLevelType]) {
+ EventPropagators.accumulateDirectDispatches(event);
+ } else {
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ }
+
return event;
},
};
diff --git a/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js b/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
deleted file mode 100644
index 2d7a6d426fe1d..0000000000000
--- a/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @emails react-core
- */
-
-'use strict';
-
-var EnterLeaveEventPlugin;
-var React;
-var ReactDOM;
-var ReactDOMComponentTree;
-
-describe('EnterLeaveEventPlugin', () => {
- beforeEach(() => {
- jest.resetModules();
-
- React = require('react');
- ReactDOM = require('react-dom');
- // TODO: can we express this test with only public API?
- ReactDOMComponentTree = require('ReactDOMComponentTree');
- EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
- });
-
- it('should set relatedTarget properly in iframe', () => {
- var iframe = document.createElement('iframe');
- document.body.appendChild(iframe);
-
- var iframeDocument = iframe.contentDocument;
-
- iframeDocument.write(
- '',
- );
- iframeDocument.close();
-
- var component = ReactDOM.render(
- ,
- iframeDocument.body.getElementsByTagName('div')[0],
- );
- var div = ReactDOM.findDOMNode(component);
-
- var extracted = EnterLeaveEventPlugin.extractEvents(
- 'topMouseOver',
- ReactDOMComponentTree.getInstanceFromNode(div),
- {target: div},
- div,
- );
- expect(extracted.length).toBe(2);
-
- var leave = extracted[0];
- var enter = extracted[1];
-
- expect(leave.target).toBe(iframe.contentWindow);
- expect(leave.relatedTarget).toBe(div);
- expect(enter.target).toBe(div);
- expect(enter.relatedTarget).toBe(iframe.contentWindow);
- });
-});
diff --git a/src/renderers/shared/shared/ReactTreeTraversal.js b/src/renderers/shared/shared/ReactTreeTraversal.js
index c6c8aa265610b..dd667a11343f7 100644
--- a/src/renderers/shared/shared/ReactTreeTraversal.js
+++ b/src/renderers/shared/shared/ReactTreeTraversal.js
@@ -109,38 +109,9 @@ function traverseTwoPhase(inst, fn, arg) {
}
}
-/**
- * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
- * should would receive a `mouseEnter` or `mouseLeave` event.
- *
- * Does not invoke the callback on the nearest common ancestor because nothing
- * "entered" or "left" that element.
- */
-function traverseEnterLeave(from, to, fn, argFrom, argTo) {
- var common = from && to ? getLowestCommonAncestor(from, to) : null;
- var pathFrom = [];
- while (from && from !== common) {
- pathFrom.push(from);
- from = getParent(from);
- }
- var pathTo = [];
- while (to && to !== common) {
- pathTo.push(to);
- to = getParent(to);
- }
- var i;
- for (i = 0; i < pathFrom.length; i++) {
- fn(pathFrom[i], 'bubbled', argFrom);
- }
- for (i = pathTo.length; i-- > 0; ) {
- fn(pathTo[i], 'captured', argTo);
- }
-}
-
module.exports = {
isAncestor: isAncestor,
getLowestCommonAncestor: getLowestCommonAncestor,
getParentInstance: getParentInstance,
traverseTwoPhase: traverseTwoPhase,
- traverseEnterLeave: traverseEnterLeave,
};
diff --git a/src/renderers/shared/shared/event/BrowserEventConstants.js b/src/renderers/shared/shared/event/BrowserEventConstants.js
index 0f460486be72c..aca602fa9177b 100644
--- a/src/renderers/shared/shared/event/BrowserEventConstants.js
+++ b/src/renderers/shared/shared/event/BrowserEventConstants.js
@@ -64,6 +64,8 @@ var topLevelTypes = {
topLoadedMetadata: 'loadedmetadata',
topLoadStart: 'loadstart',
topMouseDown: 'mousedown',
+ topMouseEnter: 'mouseenter',
+ topMouseLeave: 'mouseleave',
topMouseMove: 'mousemove',
topMouseOut: 'mouseout',
topMouseOver: 'mouseover',
diff --git a/src/renderers/shared/shared/event/EventPropagators.js b/src/renderers/shared/shared/event/EventPropagators.js
index 4a0426548c472..cd828ad0b104a 100644
--- a/src/renderers/shared/shared/event/EventPropagators.js
+++ b/src/renderers/shared/shared/event/EventPropagators.js
@@ -127,16 +127,6 @@ function accumulateTwoPhaseDispatchesSkipTarget(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
}
-function accumulateEnterLeaveDispatches(leave, enter, from, to) {
- ReactTreeTraversal.traverseEnterLeave(
- from,
- to,
- accumulateDispatches,
- leave,
- enter,
- );
-}
-
function accumulateDirectDispatches(events) {
forEachAccumulated(events, accumulateDirectDispatchesSingle);
}
@@ -156,7 +146,6 @@ var EventPropagators = {
accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget,
accumulateDirectDispatches: accumulateDirectDispatches,
- accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
};
module.exports = EventPropagators;