diff --git a/packages/react-dom/src/events/DOMPluginEventSystem.js b/packages/react-dom/src/events/DOMPluginEventSystem.js index b11807fdca3e6..eea047b959879 100644 --- a/packages/react-dom/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMPluginEventSystem.js @@ -15,7 +15,10 @@ import { SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS, } from './EventSystemFlags'; import type {AnyNativeEvent} from './PluginModuleType'; -import type {ReactSyntheticEvent} from './ReactSyntheticEventType'; +import type { + KnownReactSyntheticEvent, + ReactSyntheticEvent, +} from './ReactSyntheticEventType'; import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree'; import type {EventPriority} from 'shared/ReactTypes'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; @@ -917,15 +920,12 @@ function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null { function accumulateEnterLeaveListenersForEvent( dispatchQueue: DispatchQueue, - event: ReactSyntheticEvent, + event: KnownReactSyntheticEvent, target: Fiber, common: Fiber | null, inCapturePhase: boolean, ): void { const registrationName = event._reactName; - if (registrationName === undefined) { - return; - } const listeners: Array = []; let instance = target; @@ -969,8 +969,8 @@ function accumulateEnterLeaveListenersForEvent( // phase event listeners. export function accumulateEnterLeaveTwoPhaseListeners( dispatchQueue: DispatchQueue, - leaveEvent: ReactSyntheticEvent, - enterEvent: null | ReactSyntheticEvent, + leaveEvent: KnownReactSyntheticEvent, + enterEvent: null | KnownReactSyntheticEvent, from: Fiber | null, to: Fiber | null, ): void { diff --git a/packages/react-dom/src/events/ReactSyntheticEventType.js b/packages/react-dom/src/events/ReactSyntheticEventType.js index e37af02d162d3..853aa3a6aefa7 100644 --- a/packages/react-dom/src/events/ReactSyntheticEventType.js +++ b/packages/react-dom/src/events/ReactSyntheticEventType.js @@ -22,14 +22,26 @@ export type DispatchConfig = {| eventPriority?: EventPriority, |}; -export type ReactSyntheticEvent = {| +type BaseSyntheticEvent = { isPersistent: () => boolean, isPropagationStopped: () => boolean, _dispatchInstances?: null | Array | Fiber, _dispatchListeners?: null | Array | Function, - _reactName: string, _targetInst: Fiber, nativeEvent: Event, + target?: mixed, + relatedTarget?: mixed, type: string, currentTarget: null | EventTarget, -|}; +}; + +export type KnownReactSyntheticEvent = BaseSyntheticEvent & { + _reactName: string, +}; +export type UnknownReactSyntheticEvent = BaseSyntheticEvent & { + _reactName: null, +}; + +export type ReactSyntheticEvent = + | KnownReactSyntheticEvent + | UnknownReactSyntheticEvent; diff --git a/packages/react-dom/src/events/SyntheticEvent.js b/packages/react-dom/src/events/SyntheticEvent.js index e226e95833cae..01742c705278c 100644 --- a/packages/react-dom/src/events/SyntheticEvent.js +++ b/packages/react-dom/src/events/SyntheticEvent.js @@ -3,17 +3,23 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @flow */ /* eslint valid-typeof: 0 */ import getEventCharCode from './getEventCharCode'; +type EventInterfaceType = { + [propName: string]: 0 | ((event: {[propName: string]: mixed}) => mixed), +}; + /** * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -const EventInterface = { +const EventInterface: EventInterfaceType = { eventPhase: 0, bubbles: 0, cancelable: 0, @@ -46,12 +52,12 @@ function functionThatReturnsFalse() { * DOM interface; custom application-specific events can also subclass this. */ export function SyntheticEvent( - reactName, - reactEventType, - targetInst, - nativeEvent, - nativeEventTarget, - Interface = EventInterface, + reactName: string | null, + reactEventType: string, + targetInst: Fiber, + nativeEvent: {[propName: string]: mixed}, + nativeEventTarget: null | EventTarget, + Interface: EventInterfaceType = EventInterface, ) { this._reactName = reactName; this._targetInst = targetInst; @@ -95,6 +101,7 @@ Object.assign(SyntheticEvent.prototype, { if (event.preventDefault) { event.preventDefault(); + // $FlowFixMe - flow is not aware of `unknown` in IE } else if (typeof event.returnValue !== 'unknown') { event.returnValue = false; } @@ -109,6 +116,7 @@ Object.assign(SyntheticEvent.prototype, { if (event.stopPropagation) { event.stopPropagation(); + // $FlowFixMe - flow is not aware of `unknown` in IE } else if (typeof event.cancelBubble !== 'unknown') { // The ChangeEventPlugin registers a "propertychange" event for // IE. This event does not support bubbling or cancelling, and @@ -138,7 +146,7 @@ Object.assign(SyntheticEvent.prototype, { isPersistent: functionThatReturnsTrue, }); -export const UIEventInterface = { +export const UIEventInterface: EventInterfaceType = { ...EventInterface, view: 0, detail: 0, @@ -154,7 +162,7 @@ let isMovementYSet = false; * @interface MouseEvent * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -export const MouseEventInterface = { +export const MouseEventInterface: EventInterfaceType = { ...UIEventInterface, screenX: 0, screenY: 0, @@ -213,7 +221,7 @@ export const MouseEventInterface = { * @interface DragEvent * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -export const DragEventInterface = { +export const DragEventInterface: EventInterfaceType = { ...MouseEventInterface, dataTransfer: 0, }; @@ -222,7 +230,7 @@ export const DragEventInterface = { * @interface FocusEvent * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -export const FocusEventInterface = { +export const FocusEventInterface: EventInterfaceType = { ...UIEventInterface, relatedTarget: 0, }; @@ -232,7 +240,7 @@ export const FocusEventInterface = { * @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface * @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent */ -export const AnimationEventInterface = { +export const AnimationEventInterface: EventInterfaceType = { ...EventInterface, animationName: 0, elapsedTime: 0, @@ -243,7 +251,7 @@ export const AnimationEventInterface = { * @interface Event * @see http://www.w3.org/TR/clipboard-apis/ */ -export const ClipboardEventInterface = { +export const ClipboardEventInterface: EventInterfaceType = { ...EventInterface, clipboardData: function(event) { return 'clipboardData' in event @@ -256,7 +264,7 @@ export const ClipboardEventInterface = { * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents */ -export const CompositionEventInterface = { +export const CompositionEventInterface: EventInterfaceType = { ...EventInterface, data: 0, }; @@ -267,7 +275,7 @@ export const CompositionEventInterface = { * /#events-inputevents */ // Happens to share the same list for now. -export const InputEventInterface = CompositionEventInterface; +export const InputEventInterface: EventInterfaceType = CompositionEventInterface; /** * Normalization of deprecated HTML5 `key` values diff --git a/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js index 32ab29a91042b..196d99c711997 100644 --- a/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js @@ -25,6 +25,7 @@ import { isContainerMarkedAsRoot, } from '../../client/ReactDOMComponentTree'; import {accumulateEnterLeaveTwoPhaseListeners} from '../DOMPluginEventSystem'; +import type {KnownReactSyntheticEvent} from '../ReactSyntheticEventType'; import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags'; import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection'; @@ -147,23 +148,23 @@ function extractEvents( leave.target = fromNode; leave.relatedTarget = toNode; - let enter = new SyntheticEvent( - enterEventType, - eventTypePrefix + 'enter', - to, - nativeEvent, - nativeEventTarget, - eventInterface, - ); - enter.target = toNode; - enter.relatedTarget = fromNode; + let enter: KnownReactSyntheticEvent | null = null; - // If we are not processing the first ancestor, then we - // should not process the same nativeEvent again, as we - // will have already processed it in the first ancestor. + // We should only process this nativeEvent if we are processing + // the first ancestor. Next time, we will ignore the event. const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any)); - if (nativeTargetInst !== targetInst) { - enter = null; + if (nativeTargetInst === targetInst) { + const enterEvent: KnownReactSyntheticEvent = new SyntheticEvent( + enterEventType, + eventTypePrefix + 'enter', + to, + nativeEvent, + nativeEventTarget, + eventInterface, + ); + enterEvent.target = toNode; + enterEvent.relatedTarget = fromNode; + enter = enterEvent; } accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);