From 71ce07abd9dd7837c801ac7cc99daf042d6d8a56 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 4 Apr 2019 18:43:21 +0100 Subject: [PATCH 1/4] Add EventComponent responder lifecycle methods --- packages/events/EventTypes.js | 13 +- .../src/events/DOMEventResponderContext.js | 220 +++++++++++++ .../src/events/DOMEventResponderEvent.js | 35 +++ .../src/events/DOMEventResponderSystem.js | 295 +++--------------- .../DOMEventResponderSystem-test.internal.js | 64 ++-- packages/react-events/src/Drag.js | 29 +- packages/react-events/src/Focus.js | 35 ++- packages/react-events/src/Hover.js | 56 ++-- packages/react-events/src/Press.js | 62 +++- packages/react-events/src/Swipe.js | 27 +- .../__tests__/TouchHitTarget-test.internal.js | 2 +- packages/react-reconciler/src/ReactFiber.js | 1 + packages/shared/ReactTypes.js | 15 +- 13 files changed, 482 insertions(+), 372 deletions(-) create mode 100644 packages/react-dom/src/events/DOMEventResponderContext.js create mode 100644 packages/react-dom/src/events/DOMEventResponderEvent.js diff --git a/packages/events/EventTypes.js b/packages/events/EventTypes.js index 169b34aa07713..0a871b33e258d 100644 --- a/packages/events/EventTypes.js +++ b/packages/events/EventTypes.js @@ -10,12 +10,15 @@ import type {AnyNativeEvent} from 'events/PluginModuleType'; import type {ReactEventResponderEventType} from 'shared/ReactTypes'; -export type EventResponderContext = { - event: AnyNativeEvent, +export type ResponderEvent = { + nativeEvent: AnyNativeEvent, eventTarget: Element | Document, eventType: string, isPassive: () => boolean, isPassiveSupported: () => boolean, +}; + +export type ResponderContext = { dispatchEvent: ( eventObject: E, { @@ -28,7 +31,6 @@ export type EventResponderContext = { childTarget: Element | Document, parentTarget: Element | Document, ) => boolean, - isTargetOwned: (Element | Document) => boolean, isTargetWithinEventComponent: (Element | Document) => boolean, isPositionWithinTouchHitTarget: (x: number, y: number) => boolean, addRootEventTypes: ( @@ -37,7 +39,8 @@ export type EventResponderContext = { removeRootEventTypes: ( rootEventTypes: Array, ) => void, - requestOwnership: (target: Element | Document | null) => boolean, - releaseOwnership: (target: Element | Document | null) => boolean, + hasOwnership: () => boolean, + requestOwnership: () => boolean, + releaseOwnership: () => boolean, withAsyncDispatching: (func: () => void) => void, }; diff --git a/packages/react-dom/src/events/DOMEventResponderContext.js b/packages/react-dom/src/events/DOMEventResponderContext.js new file mode 100644 index 0000000000000..091b2b2d89e2e --- /dev/null +++ b/packages/react-dom/src/events/DOMEventResponderContext.js @@ -0,0 +1,220 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @flow + */ + +import type {AnyNativeEvent} from 'events/PluginModuleType'; +import warning from 'shared/warning'; +import {batchedUpdates} from 'events/ReactGenericBatching'; +import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; +import { + eventsWithStopPropagation, + rootEventTypesToEventComponents, + processEventQueue, + listenToResponderEventTypesImpl, + type PartialEventObject, +} from './DOMEventResponderSystem'; +import type {ReactEventResponderEventType} from 'shared/ReactTypes'; + +let currentOwner = null; + +export type EventQueue = { + bubble: null | Array<$Shape>, + capture: null | Array<$Shape>, + discrete: boolean, +}; + +export function createEventQueue(): EventQueue { + return { + bubble: null, + capture: null, + discrete: false, + }; +} + +export function DOMEventResponderContext() { + this._fiber = null; + this._responder = null; + this._discreteEvents = null; + this._nonDiscreteEvents = null; + this._isBatching = true; + this._eventQueue = null; + this._event = null; +} + +DOMEventResponderContext.prototype.dispatchEvent = function( + possibleEventObject: Object, + { + capture, + discrete, + stopPropagation, + }: { + capture?: boolean, + discrete?: boolean, + stopPropagation?: boolean, + }, +): void { + const eventQueue = this._eventQueue; + const {listener, target, type} = possibleEventObject; + + if (listener == null || target == null || type == null) { + throw new Error( + 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.', + ); + } + if (__DEV__) { + possibleEventObject.preventDefault = () => { + // Update this warning when we have a story around dealing with preventDefault + warning( + false, + 'preventDefault() is no longer available on event objects created from event responder modules.', + ); + }; + possibleEventObject.stopPropagation = () => { + // Update this warning when we have a story around dealing with stopPropgation + warning( + false, + 'stopPropagation() is no longer available on event objects created from event responder modules.', + ); + }; + } + const eventObject = ((possibleEventObject: any): $Shape); + let events; + + if (capture) { + events = eventQueue.capture; + if (events === null) { + events = eventQueue.capture = []; + } + } else { + events = eventQueue.bubble; + if (events === null) { + events = eventQueue.bubble = []; + } + } + if (discrete) { + eventQueue.discrete = true; + } + events.push(eventObject); + + if (stopPropagation) { + eventsWithStopPropagation.add(eventObject); + } +}; + +DOMEventResponderContext.prototype.isTargetWithinEventComponent = function( + target: AnyNativeEvent, +): boolean { + const eventFiber = this._fiber; + + if (target != null) { + let fiber = getClosestInstanceFromNode(target); + while (fiber !== null) { + if (fiber === eventFiber || fiber === eventFiber.alternate) { + return true; + } + fiber = fiber.return; + } + } + return false; +}; + +DOMEventResponderContext.prototype.isTargetWithinElement = function( + childTarget: EventTarget, + parentTarget: EventTarget, +): boolean { + const childFiber = getClosestInstanceFromNode(childTarget); + const parentFiber = getClosestInstanceFromNode(parentTarget); + + let currentFiber = childFiber; + while (currentFiber !== null) { + if (currentFiber === parentFiber) { + return true; + } + currentFiber = currentFiber.return; + } + return false; +}; + +DOMEventResponderContext.prototype.addRootEventTypes = function( + rootEventTypes: Array, +) { + const element = this._event.eventTarget.ownerDocument; + ((listenToResponderEventTypesImpl: any): Function)(rootEventTypes, element); + const eventComponent = this._fiber; + for (let i = 0; i < rootEventTypes.length; i++) { + const rootEventType = rootEventTypes[i]; + const topLevelEventType = + typeof rootEventType === 'string' ? rootEventType : rootEventType.name; + let rootEventComponents = rootEventTypesToEventComponents.get( + topLevelEventType, + ); + if (rootEventComponents === undefined) { + rootEventComponents = new Set(); + rootEventTypesToEventComponents.set( + topLevelEventType, + rootEventComponents, + ); + } + rootEventComponents.add(eventComponent); + } +}; + +DOMEventResponderContext.prototype.removeRootEventTypes = function( + rootEventTypes: Array, +): void { + const eventComponent = this._fiber; + for (let i = 0; i < rootEventTypes.length; i++) { + const rootEventType = rootEventTypes[i]; + const topLevelEventType = + typeof rootEventType === 'string' ? rootEventType : rootEventType.name; + let rootEventComponents = rootEventTypesToEventComponents.get( + topLevelEventType, + ); + if (rootEventComponents !== undefined) { + rootEventComponents.delete(eventComponent); + } + } +}; + +DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() { + // TODO +}; + +DOMEventResponderContext.prototype.hasOwnership = function(): boolean { + return currentOwner === this._fiber; +}; + +DOMEventResponderContext.prototype.requestOwnership = function(): boolean { + if (currentOwner !== null) { + return false; + } + currentOwner = this._fiber; + return true; +}; + +DOMEventResponderContext.prototype.releaseOwnership = function( + targetElement: Element | Node, +): boolean { + if (currentOwner !== this._fiber) { + return false; + } + currentOwner = null; + return false; +}; + +DOMEventResponderContext.prototype.withAsyncDispatching = function( + func: () => void, +) { + const previousEventQueue = this._eventQueue; + this._eventQueue = createEventQueue(); + try { + func(); + batchedUpdates(processEventQueue, this._eventQueue); + } finally { + this._eventQueue = previousEventQueue; + } +}; diff --git a/packages/react-dom/src/events/DOMEventResponderEvent.js b/packages/react-dom/src/events/DOMEventResponderEvent.js new file mode 100644 index 0000000000000..fb97258c11a12 --- /dev/null +++ b/packages/react-dom/src/events/DOMEventResponderEvent.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @flow + */ + +import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; +import type {AnyNativeEvent} from 'events/PluginModuleType'; +import { + type EventSystemFlags, + IS_PASSIVE, + PASSIVE_NOT_SUPPORTED, +} from 'events/EventSystemFlags'; + +export function DOMEventResponderEvent( + topLevelType: DOMTopLevelEventType, + nativeEvent: AnyNativeEvent, + nativeEventTarget: EventTarget, + eventSystemFlags: EventSystemFlags, +) { + this.nativeEvent = nativeEvent; + this.eventTarget = nativeEventTarget; + this.eventType = topLevelType; + this._flags = eventSystemFlags; +} + +DOMEventResponderEvent.prototype.isPassive = function(): boolean { + return (this._flags & IS_PASSIVE) !== 0; +}; + +DOMEventResponderEvent.prototype.isPassiveSupported = function(): boolean { + return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; +}; diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 45e6c95e463b6..0ce0ef7661b5b 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -6,11 +6,8 @@ * @flow */ -import { - type EventSystemFlags, - IS_PASSIVE, - PASSIVE_NOT_SUPPORTED, -} from 'events/EventSystemFlags'; +import type {ResponderContext, ResponderEvent} from 'events/EventTypes'; +import {type EventSystemFlags} from 'events/EventSystemFlags'; import type {AnyNativeEvent} from 'events/PluginModuleType'; import {EventComponent} from 'shared/ReactWorkTags'; import type { @@ -18,15 +15,18 @@ import type { ReactEventResponderEventType, } from 'shared/ReactTypes'; import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; -import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching'; +import {interactiveUpdates} from 'events/ReactGenericBatching'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; -import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; import {enableEventAPI} from 'shared/ReactFeatureFlags'; import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils'; -import warning from 'shared/warning'; +import {DOMEventResponderEvent} from './DOMEventResponderEvent'; +import { + DOMEventResponderContext, + createEventQueue, +} from './DOMEventResponderContext'; -let listenToResponderEventTypesImpl; +export let listenToResponderEventTypesImpl; export function setListenToResponderEventTypes( _listenToResponderEventTypesImpl: Function, @@ -34,39 +34,31 @@ export function setListenToResponderEventTypes( listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl; } -const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; +let eventResponderContext = null; -const rootEventTypesToEventComponents: Map< +if (enableEventAPI) { + // We re-use the same context object many times, to improve memory/peformance + eventResponderContext = new DOMEventResponderContext(); +} + +export const rootEventTypesToEventComponents: Map< DOMTopLevelEventType | string, Set, > = new Map(); +const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; +export const eventsWithStopPropagation: + | WeakSet + | Set<$Shape> = new PossiblyWeakSet(); const targetEventTypeCached: Map< Array, Set, > = new Map(); -const targetOwnership: Map = new Map(); -const eventsWithStopPropagation: - | WeakSet - | Set<$Shape> = new PossiblyWeakSet(); -type PartialEventObject = { +export type PartialEventObject = { listener: ($Shape) => void, target: Element | Document, type: string, }; -type EventQueue = { - bubble: null | Array<$Shape>, - capture: null | Array<$Shape>, - discrete: boolean, -}; - -function createEventQueue(): EventQueue { - return { - bubble: null, - capture: null, - discrete: false, - }; -} function processEvent(event: $Shape): void { const type = event.type; @@ -100,8 +92,12 @@ function processEvents( } } -function processEventQueue(eventQueue: EventQueue): void { - const {bubble, capture, discrete} = eventQueue; +export function processEventQueue(): void { + const { + bubble, + capture, + discrete, + } = ((eventResponderContext: any): Object)._eventQueue; if (discrete) { interactiveUpdates(() => { @@ -112,218 +108,6 @@ function processEventQueue(eventQueue: EventQueue): void { } } -// TODO add context methods for dispatching events -function DOMEventResponderContext( - topLevelType: DOMTopLevelEventType, - nativeEvent: AnyNativeEvent, - nativeEventTarget: EventTarget, - eventSystemFlags: EventSystemFlags, -) { - this.event = nativeEvent; - this.eventTarget = nativeEventTarget; - this.eventType = topLevelType; - this._flags = eventSystemFlags; - this._fiber = null; - this._responder = null; - this._discreteEvents = null; - this._nonDiscreteEvents = null; - this._isBatching = true; - this._eventQueue = createEventQueue(); -} - -DOMEventResponderContext.prototype.isPassive = function(): boolean { - return (this._flags & IS_PASSIVE) !== 0; -}; - -DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean { - return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; -}; - -DOMEventResponderContext.prototype.dispatchEvent = function( - possibleEventObject: Object, - { - capture, - discrete, - stopPropagation, - }: { - capture?: boolean, - discrete?: boolean, - stopPropagation?: boolean, - }, -): void { - const eventQueue = this._eventQueue; - const {listener, target, type} = possibleEventObject; - - if (listener == null || target == null || type == null) { - throw new Error( - 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.', - ); - } - if (__DEV__) { - possibleEventObject.preventDefault = () => { - // Update this warning when we have a story around dealing with preventDefault - warning( - false, - 'preventDefault() is no longer available on event objects created from event responder modules.', - ); - }; - possibleEventObject.stopPropagation = () => { - // Update this warning when we have a story around dealing with stopPropgation - warning( - false, - 'stopPropagation() is no longer available on event objects created from event responder modules.', - ); - }; - } - const eventObject = ((possibleEventObject: any): $Shape); - let events; - - if (capture) { - events = eventQueue.capture; - if (events === null) { - events = eventQueue.capture = []; - } - } else { - events = eventQueue.bubble; - if (events === null) { - events = eventQueue.bubble = []; - } - } - if (discrete) { - eventQueue.discrete = true; - } - events.push(eventObject); - - if (stopPropagation) { - eventsWithStopPropagation.add(eventObject); - } -}; - -DOMEventResponderContext.prototype.isTargetWithinEventComponent = function( - target: AnyNativeEvent, -): boolean { - const eventFiber = this._fiber; - - if (target != null) { - let fiber = getClosestInstanceFromNode(target); - while (fiber !== null) { - if (fiber === eventFiber || fiber === eventFiber.alternate) { - return true; - } - fiber = fiber.return; - } - } - return false; -}; - -DOMEventResponderContext.prototype.isTargetWithinElement = function( - childTarget: EventTarget, - parentTarget: EventTarget, -): boolean { - const childFiber = getClosestInstanceFromNode(childTarget); - const parentFiber = getClosestInstanceFromNode(parentTarget); - - let currentFiber = childFiber; - while (currentFiber !== null) { - if (currentFiber === parentFiber) { - return true; - } - currentFiber = currentFiber.return; - } - return false; -}; - -DOMEventResponderContext.prototype.addRootEventTypes = function( - rootEventTypes: Array, -) { - const element = this.eventTarget.ownerDocument; - listenToResponderEventTypesImpl(rootEventTypes, element); - const eventComponent = this._fiber; - for (let i = 0; i < rootEventTypes.length; i++) { - const rootEventType = rootEventTypes[i]; - const topLevelEventType = - typeof rootEventType === 'string' ? rootEventType : rootEventType.name; - let rootEventComponents = rootEventTypesToEventComponents.get( - topLevelEventType, - ); - if (rootEventComponents === undefined) { - rootEventComponents = new Set(); - rootEventTypesToEventComponents.set( - topLevelEventType, - rootEventComponents, - ); - } - rootEventComponents.add(eventComponent); - } -}; - -DOMEventResponderContext.prototype.removeRootEventTypes = function( - rootEventTypes: Array, -): void { - const eventComponent = this._fiber; - for (let i = 0; i < rootEventTypes.length; i++) { - const rootEventType = rootEventTypes[i]; - const topLevelEventType = - typeof rootEventType === 'string' ? rootEventType : rootEventType.name; - let rootEventComponents = rootEventTypesToEventComponents.get( - topLevelEventType, - ); - if (rootEventComponents !== undefined) { - rootEventComponents.delete(eventComponent); - } - } -}; - -DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() { - // TODO -}; - -DOMEventResponderContext.prototype.isTargetOwned = function( - targetElement: Element | Node, -): boolean { - const targetDoc = targetElement.ownerDocument; - return targetOwnership.has(targetDoc); -}; - -DOMEventResponderContext.prototype.requestOwnership = function( - targetElement: Element | Node, -): boolean { - const targetDoc = targetElement.ownerDocument; - if (targetOwnership.has(targetDoc)) { - return false; - } - targetOwnership.set(targetDoc, this._fiber); - return true; -}; - -DOMEventResponderContext.prototype.releaseOwnership = function( - targetElement: Element | Node, -): boolean { - const targetDoc = targetElement.ownerDocument; - if (!targetOwnership.has(targetDoc)) { - return false; - } - const owner = targetOwnership.get(targetDoc); - if (owner === this._fiber || owner === this._fiber.alternate) { - targetOwnership.delete(targetDoc); - return true; - } - return false; -}; - -DOMEventResponderContext.prototype.withAsyncDispatching = function( - func: () => void, -) { - const previousEventQueue = this._eventQueue; - this._eventQueue = createEventQueue(); - try { - func(); - batchedUpdates(processEventQueue, this._eventQueue); - } finally { - this._eventQueue = previousEventQueue; - } -}; - function getTargetEventTypes( eventTypes: Array, ): Set { @@ -345,7 +129,7 @@ function getTargetEventTypes( function handleTopLevelType( topLevelType: DOMTopLevelEventType, fiber: Fiber, - context: Object, + responerEvent: Object, isRootLevelEvent: boolean, ): void { const responder: ReactEventResponder = fiber.type.responder; @@ -360,9 +144,16 @@ function handleTopLevelType( if (state === null && responder.createInitialState !== undefined) { state = fiber.stateNode.state = responder.createInitialState(props); } - context._fiber = fiber; - context._responder = responder; - responder.handleEvent(context, props, state); + const currentEventResponderContext = ((eventResponderContext: any): Object); + currentEventResponderContext._fiber = fiber; + currentEventResponderContext._responder = responder; + currentEventResponderContext._event = responerEvent; + responder.onEvent( + ((responerEvent: any): ResponderEvent), + ((eventResponderContext: any): ResponderContext), + props, + state, + ); } export function runResponderEventsInBatch( @@ -373,7 +164,9 @@ export function runResponderEventsInBatch( eventSystemFlags: EventSystemFlags, ): void { if (enableEventAPI) { - const context = new DOMEventResponderContext( + const currentEventResponderContext = ((eventResponderContext: any): Object); + currentEventResponderContext._eventQueue = createEventQueue(); + const responerEvent = new DOMEventResponderEvent( topLevelType, nativeEvent, nativeEventTarget, @@ -383,7 +176,7 @@ export function runResponderEventsInBatch( // Traverse up the fiber tree till we find event component fibers. while (node !== null) { if (node.tag === EventComponent) { - handleTopLevelType(topLevelType, node, context, false); + handleTopLevelType(topLevelType, node, responerEvent, false); } node = node.return; } @@ -399,11 +192,11 @@ export function runResponderEventsInBatch( handleTopLevelType( topLevelType, rootEventComponentFiber, - context, + responerEvent, true, ); } } - processEventQueue(context._eventQueue); + processEventQueue(); } } diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index af0111e4eb9c8..15b09c6b49127 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -13,10 +13,10 @@ let React; let ReactFeatureFlags; let ReactDOM; -function createReactEventComponent(targetEventTypes, handleEvent) { +function createReactEventComponent(targetEventTypes, onEvent) { const testEventResponder = { targetEventTypes, - handleEvent, + onEvent, }; return { @@ -53,19 +53,19 @@ describe('DOMEventResponderSystem', () => { container = null; }); - it('the event responder handleEvent() function should fire on click event', () => { + it('the event responder onEvent() function should fire on click event', () => { let eventResponderFiredCount = 0; let eventLog = []; const buttonRef = React.createRef(); const ClickEventComponent = createReactEventComponent( ['click'], - (context, props) => { + (event, context, props) => { eventResponderFiredCount++; eventLog.push({ - name: context.eventType, - passive: context.isPassive(), - passiveSupported: context.isPassiveSupported(), + name: event.eventType, + passive: event.isPassive(), + passiveSupported: event.isPassiveSupported(), }); }, ); @@ -79,7 +79,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); expect(container.innerHTML).toBe(''); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); expect(eventResponderFiredCount).toBe(1); @@ -103,7 +103,7 @@ describe('DOMEventResponderSystem', () => { expect(eventResponderFiredCount).toBe(2); }); - it('the event responder handleEvent() function should fire on click event (passive events forced)', () => { + it('the event responder onEvent() function should fire on click event (passive events forced)', () => { // JSDOM does not support passive events, so this manually overrides the value to be true const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents'); checkPassiveEvents.passiveBrowserEventsSupported = true; @@ -113,11 +113,11 @@ describe('DOMEventResponderSystem', () => { const ClickEventComponent = createReactEventComponent( ['click'], - (context, props) => { + (event, context, props) => { eventLog.push({ - name: context.eventType, - passive: context.isPassive(), - passiveSupported: context.isPassiveSupported(), + name: event.eventType, + passive: event.isPassive(), + passiveSupported: event.isPassiveSupported(), }); }, ); @@ -130,7 +130,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); expect(eventLog.length).toBe(1); @@ -141,19 +141,19 @@ describe('DOMEventResponderSystem', () => { }); }); - it('nested event responders and their handleEvent() function should fire multiple times', () => { + it('nested event responders and their onEvent() function should fire multiple times', () => { let eventResponderFiredCount = 0; let eventLog = []; const buttonRef = React.createRef(); const ClickEventComponent = createReactEventComponent( ['click'], - (context, props) => { + (event, context, props) => { eventResponderFiredCount++; eventLog.push({ - name: context.eventType, - passive: context.isPassive(), - passiveSupported: context.isPassiveSupported(), + name: event.eventType, + passive: event.isPassive(), + passiveSupported: event.isPassiveSupported(), }); }, ); @@ -168,7 +168,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); expect(eventResponderFiredCount).toBe(2); @@ -186,7 +186,7 @@ describe('DOMEventResponderSystem', () => { }); }); - it('nested event responders and their handleEvent() should fire in the correct order', () => { + it('nested event responders and their onEvent() should fire in the correct order', () => { let eventLog = []; const buttonRef = React.createRef(); @@ -214,7 +214,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); @@ -227,14 +227,14 @@ describe('DOMEventResponderSystem', () => { const ClickEventComponent = createReactEventComponent( ['click'], - (context, props) => { + (event, context, props) => { if (props.onMagicClick) { - const event = { + const syntheticEvent = { listener: props.onMagicClick, - target: context.eventTarget, + target: event.eventTarget, type: 'magicclick', }; - context.dispatchEvent(event, {discrete: true}); + context.dispatchEvent(syntheticEvent, {discrete: true}); } }, ); @@ -251,7 +251,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); @@ -264,10 +264,10 @@ describe('DOMEventResponderSystem', () => { const LongPressEventComponent = createReactEventComponent( ['click'], - (context, props) => { + (event, context, props) => { const pressEvent = { listener: props.onPress, - target: context.eventTarget, + target: event.eventTarget, type: 'press', }; context.dispatchEvent(pressEvent, {discrete: true}); @@ -278,7 +278,7 @@ describe('DOMEventResponderSystem', () => { if (props.onLongPress) { const longPressEvent = { listener: props.onLongPress, - target: context.eventTarget, + target: event.eventTarget, type: 'longpress', }; context.dispatchEvent(longPressEvent, {discrete: true}); @@ -287,7 +287,7 @@ describe('DOMEventResponderSystem', () => { if (props.onLongPressChange) { const longPressChangeEvent = { listener: props.onLongPressChange, - target: context.eventTarget, + target: event.eventTarget, type: 'longpresschange', }; context.dispatchEvent(longPressChangeEvent, {discrete: true}); @@ -313,7 +313,7 @@ describe('DOMEventResponderSystem', () => { ReactDOM.render(, container); - // Clicking the button should trigger the event responder handleEvent() + // Clicking the button should trigger the event responder onEvent() let buttonElement = buttonRef.current; dispatchClickEvent(buttonElement); jest.runAllTimers(); diff --git a/packages/react-events/src/Drag.js b/packages/react-events/src/Drag.js index ed11c4260c270..7c7f48ed29fe4 100644 --- a/packages/react-events/src/Drag.js +++ b/packages/react-events/src/Drag.js @@ -7,7 +7,7 @@ * @flow */ -import type {EventResponderContext} from 'events/EventTypes'; +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; const targetEventTypes = ['pointerdown', 'pointercancel']; @@ -62,7 +62,7 @@ function createDragEvent( } function dispatchDragEvent( - context: EventResponderContext, + context: ResponderContext, name: DragEventType, listener: DragEvent => void, state: DragState, @@ -87,12 +87,13 @@ const DragResponder = { y: 0, }; }, - handleEvent( - context: EventResponderContext, + onEvent( + event: ResponderEvent, + context: ResponderContext, props: Object, state: DragState, ): void { - const {eventTarget, eventType, event} = context; + const {eventTarget, eventType, nativeEvent} = event; switch (eventType) { case 'touchstart': @@ -100,10 +101,12 @@ const DragResponder = { case 'pointerdown': { if (!state.isDragging) { if (props.onShouldClaimOwnership) { - context.releaseOwnership(state.dragTarget); + context.releaseOwnership(); } const obj = - eventType === 'touchstart' ? (event: any).changedTouches[0] : event; + eventType === 'touchstart' + ? (nativeEvent: any).changedTouches[0] + : nativeEvent; const x = (state.startX = (obj: any).screenX); const y = (state.startY = (obj: any).screenY); state.x = x; @@ -128,12 +131,14 @@ const DragResponder = { case 'touchmove': case 'mousemove': case 'pointermove': { - if (context.isPassive()) { + if (event.isPassive()) { return; } if (state.isPointerDown) { const obj = - eventType === 'touchmove' ? (event: any).changedTouches[0] : event; + eventType === 'touchmove' + ? (nativeEvent: any).changedTouches[0] + : nativeEvent; const x = (obj: any).screenX; const y = (obj: any).screenY; state.x = x; @@ -145,7 +150,7 @@ const DragResponder = { props.onShouldClaimOwnership && props.onShouldClaimOwnership() ) { - shouldEnableDragging = context.requestOwnership(state.dragTarget); + shouldEnableDragging = context.requestOwnership(); } if (shouldEnableDragging) { state.isDragging = true; @@ -181,7 +186,7 @@ const DragResponder = { eventData, ); } - (event: any).preventDefault(); + (nativeEvent: any).preventDefault(); } } break; @@ -193,7 +198,7 @@ const DragResponder = { case 'pointerup': { if (state.isDragging) { if (props.onShouldClaimOwnership) { - context.releaseOwnership(state.dragTarget); + context.releaseOwnership(); } if (props.onDragEnd) { dispatchDragEvent(context, 'dragend', props.onDragEnd, state, true); diff --git a/packages/react-events/src/Focus.js b/packages/react-events/src/Focus.js index e56379f8871dd..a6eb83e2a0b08 100644 --- a/packages/react-events/src/Focus.js +++ b/packages/react-events/src/Focus.js @@ -7,7 +7,7 @@ * @flow */ -import type {EventResponderContext} from 'events/EventTypes'; +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; const targetEventTypes = [ @@ -39,9 +39,13 @@ function createFocusEvent( }; } -function dispatchFocusInEvents(context: EventResponderContext, props: Object) { - const {event, eventTarget} = context; - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { +function dispatchFocusInEvents( + event: ResponderEvent, + context: ResponderContext, + props: Object, +) { + const {nativeEvent, eventTarget} = event; + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onFocus) { @@ -65,9 +69,13 @@ function dispatchFocusInEvents(context: EventResponderContext, props: Object) { } } -function dispatchFocusOutEvents(context: EventResponderContext, props: Object) { - const {event, eventTarget} = context; - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { +function dispatchFocusOutEvents( + event: ResponderEvent, + context: ResponderContext, + props: Object, +) { + const {nativeEvent, eventTarget} = event; + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onBlur) { @@ -94,24 +102,25 @@ const FocusResponder = { isFocused: false, }; }, - handleEvent( - context: EventResponderContext, + onEvent( + event: ResponderEvent, + context: ResponderContext, props: Object, state: FocusState, ): void { - const {eventTarget, eventType} = context; + const {eventType} = event; switch (eventType) { case 'focus': { - if (!state.isFocused && !context.isTargetOwned(eventTarget)) { - dispatchFocusInEvents(context, props); + if (!state.isFocused && !context.hasOwnership()) { + dispatchFocusInEvents(event, context, props); state.isFocused = true; } break; } case 'blur': { if (state.isFocused) { - dispatchFocusOutEvents(context, props); + dispatchFocusOutEvents(event, context, props); state.isFocused = false; } break; diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index 226430b838d09..fb12529ff8565 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -7,7 +7,7 @@ * @flow */ -import type {EventResponderContext} from 'events/EventTypes'; +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; const targetEventTypes = [ @@ -50,12 +50,13 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) { } function dispatchHoverStartEvents( - context: EventResponderContext, + event: ResponderEvent, + context: ResponderContext, props: Object, state: HoverState, ): void { - const {event, eventTarget} = context; - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { + const {nativeEvent, eventTarget} = event; + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onHoverStart) { @@ -79,9 +80,13 @@ function dispatchHoverStartEvents( } } -function dispatchHoverEndEvents(context: EventResponderContext, props: Object) { - const {event, eventTarget} = context; - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { +function dispatchHoverEndEvents( + event: ResponderEvent, + context: ResponderContext, + props: Object, +) { + const {nativeEvent, eventTarget} = event; + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onHoverEnd) { @@ -114,12 +119,13 @@ const HoverResponder = { isTouched: false, }; }, - handleEvent( - context: EventResponderContext, + onEvent( + event: ResponderEvent, + context: ResponderContext, props: Object, state: HoverState, ): void { - const {eventType, eventTarget, event} = context; + const {eventType, nativeEvent} = event; switch (eventType) { case 'touchstart': @@ -130,25 +136,21 @@ const HoverResponder = { break; case 'pointerover': case 'mouseover': { - if ( - !state.isHovered && - !state.isTouched && - !context.isTargetOwned(eventTarget) - ) { - if ((event: any).pointerType === 'touch') { + if (!state.isHovered && !state.isTouched && !context.hasOwnership()) { + if ((nativeEvent: any).pointerType === 'touch') { state.isTouched = true; return; } if ( context.isPositionWithinTouchHitTarget( - (event: any).x, - (event: any).y, + (nativeEvent: any).x, + (nativeEvent: any).y, ) ) { state.isInHitSlop = true; return; } - dispatchHoverStartEvents(context, props, state); + dispatchHoverStartEvents(event, context, props, state); state.isHovered = true; } break; @@ -156,7 +158,7 @@ const HoverResponder = { case 'pointerout': case 'mouseout': { if (state.isHovered && !state.isTouched) { - dispatchHoverEndEvents(context, props); + dispatchHoverEndEvents(event, context, props); state.isHovered = false; } state.isInHitSlop = false; @@ -168,22 +170,22 @@ const HoverResponder = { if (state.isInHitSlop) { if ( !context.isPositionWithinTouchHitTarget( - (event: any).x, - (event: any).y, + (nativeEvent: any).x, + (nativeEvent: any).y, ) ) { - dispatchHoverStartEvents(context, props, state); + dispatchHoverStartEvents(event, context, props, state); state.isHovered = true; state.isInHitSlop = false; } } else if ( state.isHovered && context.isPositionWithinTouchHitTarget( - (event: any).x, - (event: any).y, + (nativeEvent: any).x, + (nativeEvent: any).y, ) ) { - dispatchHoverEndEvents(context, props); + dispatchHoverEndEvents(event, context, props); state.isHovered = false; state.isInHitSlop = true; } @@ -192,7 +194,7 @@ const HoverResponder = { } case 'pointercancel': { if (state.isHovered && !state.isTouched) { - dispatchHoverEndEvents(context, props); + dispatchHoverEndEvents(event, context, props); state.isHovered = false; state.isTouched = false; } diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index b79c38758b9a8..12a4f9ea292eb 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -7,7 +7,7 @@ * @flow */ -import type {EventResponderContext} from 'events/EventTypes'; +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; // const DEFAULT_PRESS_DELAY_MS = 0; @@ -83,7 +83,7 @@ function createPressEvent( } function dispatchPressEvent( - context: EventResponderContext, + context: ResponderContext, state: PressState, name: PressEventType, listener: (e: Object) => void, @@ -94,7 +94,7 @@ function dispatchPressEvent( } function dispatchPressStartEvents( - context: EventResponderContext, + context: ResponderContext, props: PressProps, state: PressState, ): void { @@ -158,7 +158,7 @@ function dispatchPressStartEvents( } function dispatchPressEndEvents( - context: EventResponderContext, + context: ResponderContext, props: PressProps, state: PressState, ): void { @@ -202,6 +202,21 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) { return Math.max(min, maybeNumber != null ? maybeNumber : fallback); } +function unmountResponder( + context: ResponderContext, + props: PressProps, + state: PressState, +): void { + if (state.isPressed) { + state.isPressed = false; + dispatchPressEndEvents(context, props, state); + if (state.longPressTimeout !== null) { + clearTimeout(state.longPressTimeout); + state.longPressTimeout = null; + } + } +} + const PressResponder = { targetEventTypes, createInitialState(): PressState { @@ -215,19 +230,20 @@ const PressResponder = { shouldSkipMouseAfterTouch: false, }; }, - handleEvent( - context: EventResponderContext, + onEvent( + event: ResponderEvent, + context: ResponderContext, props: PressProps, state: PressState, ): void { - const {eventTarget, eventType, event} = context; + const {eventTarget, eventType, nativeEvent} = event; switch (eventType) { case 'keydown': { if ( !props.onPress || - context.isTargetOwned(eventTarget) || - !isValidKeyPress((event: any).key) + context.hasOwnership() || + !isValidKeyPress((nativeEvent: any).key) ) { return; } @@ -240,7 +256,7 @@ const PressResponder = { * support for pointer events. */ case 'touchstart': - if (!state.isPressed && !context.isTargetOwned(eventTarget)) { + if (!state.isPressed && !context.hasOwnership()) { // We bail out of polyfilling anchor tags, given the same heuristics // explained above in regards to needing to use click events. if (isAnchorTagElement(eventTarget)) { @@ -265,7 +281,7 @@ const PressResponder = { (props.onPress || props.onLongPress) ) { // Find if the X/Y of the end touch is still that of the original target - const changedTouch = (event: any).changedTouches[0]; + const changedTouch = (nativeEvent: any).changedTouches[0]; const doc = (eventTarget: any).ownerDocument; const target = doc.elementFromPoint( changedTouch.screenX, @@ -302,24 +318,24 @@ const PressResponder = { case 'mousedown': { if ( !state.isPressed && - !context.isTargetOwned(eventTarget) && + !context.hasOwnership() && !state.shouldSkipMouseAfterTouch ) { if ( - (event: any).pointerType === 'mouse' || + (nativeEvent: any).pointerType === 'mouse' || eventType === 'mousedown' ) { // Ignore if we are pressing on hit slop area with mouse if ( context.isPositionWithinTouchHitTarget( - (event: any).x, - (event: any).y, + (nativeEvent: any).x, + (nativeEvent: any).y, ) ) { return; } // Ignore middle- and right-clicks - if (event.button === 2 || event.button === 1) { + if (nativeEvent.button === 2 || nativeEvent.button === 1) { return; } } @@ -385,12 +401,24 @@ const PressResponder = { } case 'click': { if (state.defaultPrevented) { - (event: any).preventDefault(); + (nativeEvent: any).preventDefault(); state.defaultPrevented = false; } } } }, + // TODO This method doesn't work as of yet + onUnmount(context: ResponderContext, props: PressProps, state: PressState) { + unmountResponder(context, props, state); + }, + // TODO This method doesn't work as of yet + onOwnershipChange( + context: ResponderContext, + props: PressProps, + state: PressState, + ) { + unmountResponder(context, props, state); + }, }; export default { diff --git a/packages/react-events/src/Swipe.js b/packages/react-events/src/Swipe.js index 85df2cca3f10e..f0955155262b2 100644 --- a/packages/react-events/src/Swipe.js +++ b/packages/react-events/src/Swipe.js @@ -7,7 +7,7 @@ * @flow */ -import type {EventResponderContext} from 'events/EventTypes'; +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; const targetEventTypes = ['pointerdown', 'pointercancel']; @@ -52,7 +52,7 @@ function createSwipeEvent( } function dispatchSwipeEvent( - context: EventResponderContext, + context: ResponderContext, name: SwipeEventType, listener: SwipeEvent => void, state: SwipeState, @@ -91,21 +91,22 @@ const SwipeResponder = { y: 0, }; }, - handleEvent( - context: EventResponderContext, + onEvent( + event: ResponderEvent, + context: ResponderContext, props: Object, state: SwipeState, ): void { - const {eventTarget, eventType, event} = context; + const {eventTarget, eventType, nativeEvent} = event; switch (eventType) { case 'touchstart': case 'mousedown': case 'pointerdown': { - if (!state.isSwiping && !context.isTargetOwned(eventTarget)) { + if (!state.isSwiping && !context.hasOwnership()) { let obj = event; if (eventType === 'touchstart') { - obj = (event: any).targetTouches[0]; + obj = (nativeEvent: any).targetTouches[0]; state.touchId = obj.identifier; } const x = (obj: any).screenX; @@ -114,7 +115,7 @@ const SwipeResponder = { let shouldEnableSwiping = true; if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) { - shouldEnableSwiping = context.requestOwnership(eventTarget); + shouldEnableSwiping = context.requestOwnership(); } if (shouldEnableSwiping) { state.isSwiping = true; @@ -133,13 +134,13 @@ const SwipeResponder = { case 'touchmove': case 'mousemove': case 'pointermove': { - if (context.isPassive()) { + if (event.isPassive()) { return; } if (state.isSwiping) { let obj = null; if (eventType === 'touchmove') { - const targetTouches = (event: any).targetTouches; + const targetTouches = (nativeEvent: any).targetTouches; for (let i = 0; i < targetTouches.length; i++) { if (state.touchId === targetTouches[i].identifier) { obj = targetTouches[i]; @@ -147,7 +148,7 @@ const SwipeResponder = { } } } else { - obj = event; + obj = nativeEvent; } if (obj === null) { state.isSwiping = false; @@ -178,7 +179,7 @@ const SwipeResponder = { false, eventData, ); - (event: any).preventDefault(); + (nativeEvent: any).preventDefault(); } } break; @@ -193,7 +194,7 @@ const SwipeResponder = { return; } if (props.onShouldClaimOwnership) { - context.releaseOwnership(state.swipeTarget); + context.releaseOwnership(); } const direction = state.direction; const lastDirection = state.lastDirection; diff --git a/packages/react-events/src/__tests__/TouchHitTarget-test.internal.js b/packages/react-events/src/__tests__/TouchHitTarget-test.internal.js index e47f7f3cb4714..d30a6c92c2735 100644 --- a/packages/react-events/src/__tests__/TouchHitTarget-test.internal.js +++ b/packages/react-events/src/__tests__/TouchHitTarget-test.internal.js @@ -22,7 +22,7 @@ let TouchHitTarget; const noOpResponder = { targetEventTypes: [], - handleEvent() {}, + onEvent() {}, }; function createReactEventComponent() { diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 35b62021d574f..4448f129fbff1 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -624,6 +624,7 @@ export function createFiberFromEventComponent( fiber.elementType = eventComponent; fiber.type = eventComponent; fiber.stateNode = { + context: null, props: pendingProps, state: null, }; diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 52ce27f9dfb10..0988ea8d1efbc 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -7,6 +7,8 @@ * @flow */ +import type {ResponderEvent, ResponderContext} from 'events/EventTypes'; + export type ReactNode = | React$Element | ReactPortal @@ -88,7 +90,18 @@ export type ReactEventResponderEventType = export type ReactEventResponder = { targetEventTypes: Array, createInitialState?: (props: Object) => Object, - handleEvent: (context: Object, props: Object, state: Object) => void, + onEvent: ( + event: ResponderEvent, + context: ResponderContext, + props: Object, + state: Object, + ) => void, + onUnmount: (context: ResponderContext, props: Object, state: Object) => void, + onOwnershipChange: ( + context: ResponderContext, + props: Object, + state: Object, + ) => void, }; export type ReactEventComponent = {| From 58a053771e2ed63627fd6937fc3be1648c828057 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 4 Apr 2019 20:14:55 +0100 Subject: [PATCH 2/4] Clean up and moved to context.setTimeout --- packages/events/EventTypes.js | 11 +- .../src/events/DOMEventResponderContext.js | 220 -------------- .../src/events/DOMEventResponderEvent.js | 35 --- .../src/events/DOMEventResponderSystem.js | 276 +++++++++++++++--- .../DOMEventResponderSystem-test.internal.js | 54 ++-- packages/react-events/src/Drag.js | 4 +- packages/react-events/src/Press.js | 63 ++-- packages/react-events/src/Swipe.js | 7 +- 8 files changed, 298 insertions(+), 372 deletions(-) delete mode 100644 packages/react-dom/src/events/DOMEventResponderContext.js delete mode 100644 packages/react-dom/src/events/DOMEventResponderEvent.js diff --git a/packages/events/EventTypes.js b/packages/events/EventTypes.js index 0a871b33e258d..008c2cf899280 100644 --- a/packages/events/EventTypes.js +++ b/packages/events/EventTypes.js @@ -14,13 +14,13 @@ export type ResponderEvent = { nativeEvent: AnyNativeEvent, eventTarget: Element | Document, eventType: string, - isPassive: () => boolean, - isPassiveSupported: () => boolean, + passive: boolean, + passiveSupported: boolean, }; export type ResponderContext = { - dispatchEvent: ( - eventObject: E, + dispatchEvent: ( + eventObject: Object, { capture?: boolean, discrete?: boolean, @@ -34,6 +34,7 @@ export type ResponderContext = { isTargetWithinEventComponent: (Element | Document) => boolean, isPositionWithinTouchHitTarget: (x: number, y: number) => boolean, addRootEventTypes: ( + document: Document, rootEventTypes: Array, ) => void, removeRootEventTypes: ( @@ -42,5 +43,5 @@ export type ResponderContext = { hasOwnership: () => boolean, requestOwnership: () => boolean, releaseOwnership: () => boolean, - withAsyncDispatching: (func: () => void) => void, + setTimeout: (func: () => void, timeout: number) => TimeoutID, }; diff --git a/packages/react-dom/src/events/DOMEventResponderContext.js b/packages/react-dom/src/events/DOMEventResponderContext.js deleted file mode 100644 index 091b2b2d89e2e..0000000000000 --- a/packages/react-dom/src/events/DOMEventResponderContext.js +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * @flow - */ - -import type {AnyNativeEvent} from 'events/PluginModuleType'; -import warning from 'shared/warning'; -import {batchedUpdates} from 'events/ReactGenericBatching'; -import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; -import { - eventsWithStopPropagation, - rootEventTypesToEventComponents, - processEventQueue, - listenToResponderEventTypesImpl, - type PartialEventObject, -} from './DOMEventResponderSystem'; -import type {ReactEventResponderEventType} from 'shared/ReactTypes'; - -let currentOwner = null; - -export type EventQueue = { - bubble: null | Array<$Shape>, - capture: null | Array<$Shape>, - discrete: boolean, -}; - -export function createEventQueue(): EventQueue { - return { - bubble: null, - capture: null, - discrete: false, - }; -} - -export function DOMEventResponderContext() { - this._fiber = null; - this._responder = null; - this._discreteEvents = null; - this._nonDiscreteEvents = null; - this._isBatching = true; - this._eventQueue = null; - this._event = null; -} - -DOMEventResponderContext.prototype.dispatchEvent = function( - possibleEventObject: Object, - { - capture, - discrete, - stopPropagation, - }: { - capture?: boolean, - discrete?: boolean, - stopPropagation?: boolean, - }, -): void { - const eventQueue = this._eventQueue; - const {listener, target, type} = possibleEventObject; - - if (listener == null || target == null || type == null) { - throw new Error( - 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.', - ); - } - if (__DEV__) { - possibleEventObject.preventDefault = () => { - // Update this warning when we have a story around dealing with preventDefault - warning( - false, - 'preventDefault() is no longer available on event objects created from event responder modules.', - ); - }; - possibleEventObject.stopPropagation = () => { - // Update this warning when we have a story around dealing with stopPropgation - warning( - false, - 'stopPropagation() is no longer available on event objects created from event responder modules.', - ); - }; - } - const eventObject = ((possibleEventObject: any): $Shape); - let events; - - if (capture) { - events = eventQueue.capture; - if (events === null) { - events = eventQueue.capture = []; - } - } else { - events = eventQueue.bubble; - if (events === null) { - events = eventQueue.bubble = []; - } - } - if (discrete) { - eventQueue.discrete = true; - } - events.push(eventObject); - - if (stopPropagation) { - eventsWithStopPropagation.add(eventObject); - } -}; - -DOMEventResponderContext.prototype.isTargetWithinEventComponent = function( - target: AnyNativeEvent, -): boolean { - const eventFiber = this._fiber; - - if (target != null) { - let fiber = getClosestInstanceFromNode(target); - while (fiber !== null) { - if (fiber === eventFiber || fiber === eventFiber.alternate) { - return true; - } - fiber = fiber.return; - } - } - return false; -}; - -DOMEventResponderContext.prototype.isTargetWithinElement = function( - childTarget: EventTarget, - parentTarget: EventTarget, -): boolean { - const childFiber = getClosestInstanceFromNode(childTarget); - const parentFiber = getClosestInstanceFromNode(parentTarget); - - let currentFiber = childFiber; - while (currentFiber !== null) { - if (currentFiber === parentFiber) { - return true; - } - currentFiber = currentFiber.return; - } - return false; -}; - -DOMEventResponderContext.prototype.addRootEventTypes = function( - rootEventTypes: Array, -) { - const element = this._event.eventTarget.ownerDocument; - ((listenToResponderEventTypesImpl: any): Function)(rootEventTypes, element); - const eventComponent = this._fiber; - for (let i = 0; i < rootEventTypes.length; i++) { - const rootEventType = rootEventTypes[i]; - const topLevelEventType = - typeof rootEventType === 'string' ? rootEventType : rootEventType.name; - let rootEventComponents = rootEventTypesToEventComponents.get( - topLevelEventType, - ); - if (rootEventComponents === undefined) { - rootEventComponents = new Set(); - rootEventTypesToEventComponents.set( - topLevelEventType, - rootEventComponents, - ); - } - rootEventComponents.add(eventComponent); - } -}; - -DOMEventResponderContext.prototype.removeRootEventTypes = function( - rootEventTypes: Array, -): void { - const eventComponent = this._fiber; - for (let i = 0; i < rootEventTypes.length; i++) { - const rootEventType = rootEventTypes[i]; - const topLevelEventType = - typeof rootEventType === 'string' ? rootEventType : rootEventType.name; - let rootEventComponents = rootEventTypesToEventComponents.get( - topLevelEventType, - ); - if (rootEventComponents !== undefined) { - rootEventComponents.delete(eventComponent); - } - } -}; - -DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() { - // TODO -}; - -DOMEventResponderContext.prototype.hasOwnership = function(): boolean { - return currentOwner === this._fiber; -}; - -DOMEventResponderContext.prototype.requestOwnership = function(): boolean { - if (currentOwner !== null) { - return false; - } - currentOwner = this._fiber; - return true; -}; - -DOMEventResponderContext.prototype.releaseOwnership = function( - targetElement: Element | Node, -): boolean { - if (currentOwner !== this._fiber) { - return false; - } - currentOwner = null; - return false; -}; - -DOMEventResponderContext.prototype.withAsyncDispatching = function( - func: () => void, -) { - const previousEventQueue = this._eventQueue; - this._eventQueue = createEventQueue(); - try { - func(); - batchedUpdates(processEventQueue, this._eventQueue); - } finally { - this._eventQueue = previousEventQueue; - } -}; diff --git a/packages/react-dom/src/events/DOMEventResponderEvent.js b/packages/react-dom/src/events/DOMEventResponderEvent.js deleted file mode 100644 index fb97258c11a12..0000000000000 --- a/packages/react-dom/src/events/DOMEventResponderEvent.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * @flow - */ - -import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; -import type {AnyNativeEvent} from 'events/PluginModuleType'; -import { - type EventSystemFlags, - IS_PASSIVE, - PASSIVE_NOT_SUPPORTED, -} from 'events/EventSystemFlags'; - -export function DOMEventResponderEvent( - topLevelType: DOMTopLevelEventType, - nativeEvent: AnyNativeEvent, - nativeEventTarget: EventTarget, - eventSystemFlags: EventSystemFlags, -) { - this.nativeEvent = nativeEvent; - this.eventTarget = nativeEventTarget; - this.eventType = topLevelType; - this._flags = eventSystemFlags; -} - -DOMEventResponderEvent.prototype.isPassive = function(): boolean { - return (this._flags & IS_PASSIVE) !== 0; -}; - -DOMEventResponderEvent.prototype.isPassiveSupported = function(): boolean { - return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; -}; diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 0ce0ef7661b5b..253c0b8fe1005 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -7,7 +7,11 @@ */ import type {ResponderContext, ResponderEvent} from 'events/EventTypes'; -import {type EventSystemFlags} from 'events/EventSystemFlags'; +import { + type EventSystemFlags, + IS_PASSIVE, + PASSIVE_NOT_SUPPORTED, +} from 'events/EventSystemFlags'; import type {AnyNativeEvent} from 'events/PluginModuleType'; import {EventComponent} from 'shared/ReactWorkTags'; import type { @@ -15,16 +19,13 @@ import type { ReactEventResponderEventType, } from 'shared/ReactTypes'; import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; -import {interactiveUpdates} from 'events/ReactGenericBatching'; +import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; - +import warning from 'shared/warning'; import {enableEventAPI} from 'shared/ReactFeatureFlags'; import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils'; -import {DOMEventResponderEvent} from './DOMEventResponderEvent'; -import { - DOMEventResponderContext, - createEventQueue, -} from './DOMEventResponderContext'; + +import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; export let listenToResponderEventTypesImpl; @@ -34,19 +35,202 @@ export function setListenToResponderEventTypes( listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl; } -let eventResponderContext = null; +type EventQueue = { + bubble: null | Array<$Shape>, + capture: null | Array<$Shape>, + discrete: boolean, +}; + +type PartialEventObject = { + listener: ($Shape) => void, + target: Element | Document, + type: string, +}; -if (enableEventAPI) { - // We re-use the same context object many times, to improve memory/peformance - eventResponderContext = new DOMEventResponderContext(); -} +let currentOwner = null; +let currentFiber: Fiber; +let currentResponder: ReactEventResponder; +let currentEventQueue: EventQueue; + +const eventResponderContext: ResponderContext = { + dispatchEvent( + possibleEventObject: Object, + { + capture, + discrete, + stopPropagation, + }: { + capture?: boolean, + discrete?: boolean, + stopPropagation?: boolean, + }, + ): void { + const eventQueue = currentEventQueue; + const {listener, target, type} = possibleEventObject; + + if (listener == null || target == null || type == null) { + throw new Error( + 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.', + ); + } + if (__DEV__) { + possibleEventObject.preventDefault = () => { + // Update this warning when we have a story around dealing with preventDefault + warning( + false, + 'preventDefault() is no longer available on event objects created from event responder modules.', + ); + }; + possibleEventObject.stopPropagation = () => { + // Update this warning when we have a story around dealing with stopPropgation + warning( + false, + 'stopPropagation() is no longer available on event objects created from event responder modules.', + ); + }; + } + const eventObject = ((possibleEventObject: any): $Shape< + PartialEventObject, + >); + let events; + + if (capture) { + events = eventQueue.capture; + if (events === null) { + events = eventQueue.capture = []; + } + } else { + events = eventQueue.bubble; + if (events === null) { + events = eventQueue.bubble = []; + } + } + if (discrete) { + eventQueue.discrete = true; + } + events.push(eventObject); + + if (stopPropagation) { + eventsWithStopPropagation.add(eventObject); + } + }, + isPositionWithinTouchHitTarget(x: number, y: number): boolean { + return false; + }, + isTargetWithinEventComponent(target: Element | Document): boolean { + const eventFiber = currentFiber; + + if (target != null) { + let fiber = getClosestInstanceFromNode(target); + while (fiber !== null) { + if (fiber === eventFiber || fiber === eventFiber.alternate) { + return true; + } + fiber = fiber.return; + } + } + return false; + }, + isTargetWithinElement( + childTarget: Element | Document, + parentTarget: Element | Document, + ): boolean { + const childFiber = getClosestInstanceFromNode(childTarget); + const parentFiber = getClosestInstanceFromNode(parentTarget); + + let node = childFiber; + while (node !== null) { + if (node === parentFiber) { + return true; + } + node = node.return; + } + return false; + }, + addRootEventTypes( + doc: Document, + rootEventTypes: Array, + ): void { + listenToResponderEventTypesImpl(rootEventTypes, doc); + const eventComponent = currentFiber; + for (let i = 0; i < rootEventTypes.length; i++) { + const rootEventType = rootEventTypes[i]; + const topLevelEventType = + typeof rootEventType === 'string' ? rootEventType : rootEventType.name; + let rootEventComponents = rootEventTypesToEventComponents.get( + topLevelEventType, + ); + if (rootEventComponents === undefined) { + rootEventComponents = new Set(); + rootEventTypesToEventComponents.set( + topLevelEventType, + rootEventComponents, + ); + } + rootEventComponents.add(eventComponent); + } + }, + removeRootEventTypes( + rootEventTypes: Array, + ): void { + const eventComponent = currentFiber; + for (let i = 0; i < rootEventTypes.length; i++) { + const rootEventType = rootEventTypes[i]; + const topLevelEventType = + typeof rootEventType === 'string' ? rootEventType : rootEventType.name; + let rootEventComponents = rootEventTypesToEventComponents.get( + topLevelEventType, + ); + if (rootEventComponents !== undefined) { + rootEventComponents.delete(eventComponent); + } + } + }, + hasOwnership(): boolean { + return currentOwner === currentFiber; + }, + requestOwnership(): boolean { + if (currentOwner !== null) { + return false; + } + currentOwner = currentFiber; + return true; + }, + releaseOwnership(): boolean { + if (currentOwner !== currentFiber) { + return false; + } + currentOwner = null; + return false; + }, + setTimeout(func: () => void, delay): TimeoutID { + const contextResponder = currentResponder; + const contextFiber = currentFiber; + return setTimeout(() => { + const previousEventQueue = currentEventQueue; + const previousFiber = currentFiber; + const previousResponder = currentResponder; + currentEventQueue = createEventQueue(); + currentResponder = contextResponder; + currentFiber = contextFiber; + try { + func(); + batchedUpdates(processEventQueue, currentEventQueue); + } finally { + currentFiber = previousFiber; + currentEventQueue = previousEventQueue; + currentResponder = previousResponder; + } + }, delay); + }, +}; -export const rootEventTypesToEventComponents: Map< +const rootEventTypesToEventComponents: Map< DOMTopLevelEventType | string, Set, > = new Map(); const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; -export const eventsWithStopPropagation: +const eventsWithStopPropagation: | WeakSet | Set<$Shape> = new PossiblyWeakSet(); const targetEventTypeCached: Map< @@ -54,11 +238,28 @@ const targetEventTypeCached: Map< Set, > = new Map(); -export type PartialEventObject = { - listener: ($Shape) => void, - target: Element | Document, - type: string, -}; +function createResponderEvent( + topLevelType: string, + nativeEvent: AnyNativeEvent, + nativeEventTarget: Element | Document, + eventSystemFlags: EventSystemFlags, +): ResponderEvent { + return { + nativeEvent: nativeEvent, + eventTarget: nativeEventTarget, + eventType: topLevelType, + passive: (eventSystemFlags & IS_PASSIVE) !== 0, + passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0, + }; +} + +function createEventQueue(): EventQueue { + return { + bubble: null, + capture: null, + discrete: false, + }; +} function processEvent(event: $Shape): void { const type = event.type; @@ -93,11 +294,7 @@ function processEvents( } export function processEventQueue(): void { - const { - bubble, - capture, - discrete, - } = ((eventResponderContext: any): Object)._eventQueue; + const {bubble, capture, discrete} = currentEventQueue; if (discrete) { interactiveUpdates(() => { @@ -129,7 +326,7 @@ function getTargetEventTypes( function handleTopLevelType( topLevelType: DOMTopLevelEventType, fiber: Fiber, - responerEvent: Object, + responderEvent: ResponderEvent, isRootLevelEvent: boolean, ): void { const responder: ReactEventResponder = fiber.type.responder; @@ -144,16 +341,10 @@ function handleTopLevelType( if (state === null && responder.createInitialState !== undefined) { state = fiber.stateNode.state = responder.createInitialState(props); } - const currentEventResponderContext = ((eventResponderContext: any): Object); - currentEventResponderContext._fiber = fiber; - currentEventResponderContext._responder = responder; - currentEventResponderContext._event = responerEvent; - responder.onEvent( - ((responerEvent: any): ResponderEvent), - ((eventResponderContext: any): ResponderContext), - props, - state, - ); + currentFiber = fiber; + currentResponder = responder; + + responder.onEvent(responderEvent, eventResponderContext, props, state); } export function runResponderEventsInBatch( @@ -164,19 +355,18 @@ export function runResponderEventsInBatch( eventSystemFlags: EventSystemFlags, ): void { if (enableEventAPI) { - const currentEventResponderContext = ((eventResponderContext: any): Object); - currentEventResponderContext._eventQueue = createEventQueue(); - const responerEvent = new DOMEventResponderEvent( - topLevelType, + currentEventQueue = createEventQueue(); + const responderEvent = createResponderEvent( + ((topLevelType: any): string), nativeEvent, - nativeEventTarget, + ((nativeEventTarget: any): Element | Document), eventSystemFlags, ); let node = targetFiber; // Traverse up the fiber tree till we find event component fibers. while (node !== null) { if (node.tag === EventComponent) { - handleTopLevelType(topLevelType, node, responerEvent, false); + handleTopLevelType(topLevelType, node, responderEvent, false); } node = node.return; } @@ -192,7 +382,7 @@ export function runResponderEventsInBatch( handleTopLevelType( topLevelType, rootEventComponentFiber, - responerEvent, + responderEvent, true, ); } diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index 15b09c6b49127..d7472857ec3af 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -64,8 +64,8 @@ describe('DOMEventResponderSystem', () => { eventResponderFiredCount++; eventLog.push({ name: event.eventType, - passive: event.isPassive(), - passiveSupported: event.isPassiveSupported(), + passive: event.passive, + passiveSupported: event.passiveSupported, }); }, ); @@ -116,8 +116,8 @@ describe('DOMEventResponderSystem', () => { (event, context, props) => { eventLog.push({ name: event.eventType, - passive: event.isPassive(), - passiveSupported: event.isPassiveSupported(), + passive: event.passive, + passiveSupported: event.passiveSupported, }); }, ); @@ -152,8 +152,8 @@ describe('DOMEventResponderSystem', () => { eventResponderFiredCount++; eventLog.push({ name: event.eventType, - passive: event.isPassive(), - passiveSupported: event.isPassiveSupported(), + passive: event.passive, + passiveSupported: event.passiveSupported, }); }, ); @@ -272,29 +272,25 @@ describe('DOMEventResponderSystem', () => { }; context.dispatchEvent(pressEvent, {discrete: true}); - setTimeout( - () => - context.withAsyncDispatching(() => { - if (props.onLongPress) { - const longPressEvent = { - listener: props.onLongPress, - target: event.eventTarget, - type: 'longpress', - }; - context.dispatchEvent(longPressEvent, {discrete: true}); - } - - if (props.onLongPressChange) { - const longPressChangeEvent = { - listener: props.onLongPressChange, - target: event.eventTarget, - type: 'longpresschange', - }; - context.dispatchEvent(longPressChangeEvent, {discrete: true}); - } - }), - 500, - ); + context.setTimeout(() => { + if (props.onLongPress) { + const longPressEvent = { + listener: props.onLongPress, + target: event.eventTarget, + type: 'longpress', + }; + context.dispatchEvent(longPressEvent, {discrete: true}); + } + + if (props.onLongPressChange) { + const longPressChangeEvent = { + listener: props.onLongPressChange, + target: event.eventTarget, + type: 'longpresschange', + }; + context.dispatchEvent(longPressChangeEvent, {discrete: true}); + } + }, 500); }, ); diff --git a/packages/react-events/src/Drag.js b/packages/react-events/src/Drag.js index 7c7f48ed29fe4..cd72d3a34008b 100644 --- a/packages/react-events/src/Drag.js +++ b/packages/react-events/src/Drag.js @@ -124,14 +124,14 @@ const DragResponder = { ); } - context.addRootEventTypes(rootEventTypes); + context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); } break; } case 'touchmove': case 'mousemove': case 'pointermove': { - if (event.isPassive()) { + if (event.passive) { return; } if (state.isPointerDown) { diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index 12a4f9ea292eb..8933d901066b5 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -118,42 +118,33 @@ function dispatchPressStartEvents( DEFAULT_LONG_PRESS_DELAY_MS, ); - state.longPressTimeout = setTimeout( - () => - context.withAsyncDispatching(() => { - state.isLongPressed = true; - state.longPressTimeout = null; + state.longPressTimeout = context.setTimeout(() => { + state.isLongPressed = true; + state.longPressTimeout = null; - if (props.onLongPress) { - const longPressEventListener = e => { - props.onLongPress(e); - // TODO address this again at some point - // if (e.nativeEvent.defaultPrevented) { - // state.defaultPrevented = true; - // } - }; - dispatchPressEvent( - context, - state, - 'longpress', - longPressEventListener, - ); - } + if (props.onLongPress) { + const longPressEventListener = e => { + props.onLongPress(e); + // TODO address this again at some point + // if (e.nativeEvent.defaultPrevented) { + // state.defaultPrevented = true; + // } + }; + dispatchPressEvent(context, state, 'longpress', longPressEventListener); + } - if (props.onLongPressChange) { - const longPressChangeEventListener = () => { - props.onLongPressChange(true); - }; - dispatchPressEvent( - context, - state, - 'longpresschange', - longPressChangeEventListener, - ); - } - }), - delayLongPress, - ); + if (props.onLongPressChange) { + const longPressChangeEventListener = () => { + props.onLongPressChange(true); + }; + dispatchPressEvent( + context, + state, + 'longpresschange', + longPressChangeEventListener, + ); + } + }, delayLongPress); } } @@ -266,7 +257,7 @@ const PressResponder = { state.pressTarget = eventTarget; dispatchPressStartEvents(context, props, state); state.isPressed = true; - context.addRootEventTypes(rootEventTypes); + context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); } break; @@ -342,7 +333,7 @@ const PressResponder = { state.pressTarget = eventTarget; dispatchPressStartEvents(context, props, state); state.isPressed = true; - context.addRootEventTypes(rootEventTypes); + context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); } break; } diff --git a/packages/react-events/src/Swipe.js b/packages/react-events/src/Swipe.js index f0955155262b2..42949df73ba85 100644 --- a/packages/react-events/src/Swipe.js +++ b/packages/react-events/src/Swipe.js @@ -124,7 +124,10 @@ const SwipeResponder = { state.x = x; state.y = y; state.swipeTarget = eventTarget; - context.addRootEventTypes(rootEventTypes); + context.addRootEventTypes( + eventTarget.ownerDocument, + rootEventTypes, + ); } else { state.touchId = null; } @@ -134,7 +137,7 @@ const SwipeResponder = { case 'touchmove': case 'mousemove': case 'pointermove': { - if (event.isPassive()) { + if (event.passive) { return; } if (state.isSwiping) { From 6b182d57fe4b41e77c1bf7d0a95eab7419e5198c Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 4 Apr 2019 20:57:30 +0100 Subject: [PATCH 3/4] Rename event properties to shorter form --- packages/events/EventTypes.js | 4 +-- .../src/events/DOMEventResponderSystem.js | 4 +-- .../DOMEventResponderSystem-test.internal.js | 14 ++++---- packages/react-events/src/Drag.js | 12 +++---- packages/react-events/src/Focus.js | 28 +++++----------- packages/react-events/src/Hover.js | 24 +++++--------- packages/react-events/src/Press.js | 32 +++++++++---------- packages/react-events/src/Swipe.js | 15 ++++----- 8 files changed, 55 insertions(+), 78 deletions(-) diff --git a/packages/events/EventTypes.js b/packages/events/EventTypes.js index 008c2cf899280..eedab72cf39bf 100644 --- a/packages/events/EventTypes.js +++ b/packages/events/EventTypes.js @@ -12,8 +12,8 @@ import type {ReactEventResponderEventType} from 'shared/ReactTypes'; export type ResponderEvent = { nativeEvent: AnyNativeEvent, - eventTarget: Element | Document, - eventType: string, + target: Element | Document, + type: string, passive: boolean, passiveSupported: boolean, }; diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 253c0b8fe1005..2eeefbf82c768 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -246,8 +246,8 @@ function createResponderEvent( ): ResponderEvent { return { nativeEvent: nativeEvent, - eventTarget: nativeEventTarget, - eventType: topLevelType, + target: nativeEventTarget, + type: topLevelType, passive: (eventSystemFlags & IS_PASSIVE) !== 0, passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0, }; diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index d7472857ec3af..c304781341d66 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -63,7 +63,7 @@ describe('DOMEventResponderSystem', () => { (event, context, props) => { eventResponderFiredCount++; eventLog.push({ - name: event.eventType, + name: event.type, passive: event.passive, passiveSupported: event.passiveSupported, }); @@ -115,7 +115,7 @@ describe('DOMEventResponderSystem', () => { ['click'], (event, context, props) => { eventLog.push({ - name: event.eventType, + name: event.type, passive: event.passive, passiveSupported: event.passiveSupported, }); @@ -151,7 +151,7 @@ describe('DOMEventResponderSystem', () => { (event, context, props) => { eventResponderFiredCount++; eventLog.push({ - name: event.eventType, + name: event.type, passive: event.passive, passiveSupported: event.passiveSupported, }); @@ -231,7 +231,7 @@ describe('DOMEventResponderSystem', () => { if (props.onMagicClick) { const syntheticEvent = { listener: props.onMagicClick, - target: event.eventTarget, + target: event.target, type: 'magicclick', }; context.dispatchEvent(syntheticEvent, {discrete: true}); @@ -267,7 +267,7 @@ describe('DOMEventResponderSystem', () => { (event, context, props) => { const pressEvent = { listener: props.onPress, - target: event.eventTarget, + target: event.target, type: 'press', }; context.dispatchEvent(pressEvent, {discrete: true}); @@ -276,7 +276,7 @@ describe('DOMEventResponderSystem', () => { if (props.onLongPress) { const longPressEvent = { listener: props.onLongPress, - target: event.eventTarget, + target: event.target, type: 'longpress', }; context.dispatchEvent(longPressEvent, {discrete: true}); @@ -285,7 +285,7 @@ describe('DOMEventResponderSystem', () => { if (props.onLongPressChange) { const longPressChangeEvent = { listener: props.onLongPressChange, - target: event.eventTarget, + target: event.target, type: 'longpresschange', }; context.dispatchEvent(longPressChangeEvent, {discrete: true}); diff --git a/packages/react-events/src/Drag.js b/packages/react-events/src/Drag.js index cd72d3a34008b..86cb120af2667 100644 --- a/packages/react-events/src/Drag.js +++ b/packages/react-events/src/Drag.js @@ -93,9 +93,9 @@ const DragResponder = { props: Object, state: DragState, ): void { - const {eventTarget, eventType, nativeEvent} = event; + const {target, type, nativeEvent} = event; - switch (eventType) { + switch (type) { case 'touchstart': case 'mousedown': case 'pointerdown': { @@ -104,14 +104,14 @@ const DragResponder = { context.releaseOwnership(); } const obj = - eventType === 'touchstart' + type === 'touchstart' ? (nativeEvent: any).changedTouches[0] : nativeEvent; const x = (state.startX = (obj: any).screenX); const y = (state.startY = (obj: any).screenY); state.x = x; state.y = y; - state.dragTarget = eventTarget; + state.dragTarget = target; state.isPointerDown = true; if (props.onDragStart) { @@ -124,7 +124,7 @@ const DragResponder = { ); } - context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); + context.addRootEventTypes(target.ownerDocument, rootEventTypes); } break; } @@ -136,7 +136,7 @@ const DragResponder = { } if (state.isPointerDown) { const obj = - eventType === 'touchmove' + type === 'touchmove' ? (nativeEvent: any).changedTouches[0] : nativeEvent; const x = (obj: any).screenX; diff --git a/packages/react-events/src/Focus.js b/packages/react-events/src/Focus.js index 32f9f1f856604..30ff7102ba4cc 100644 --- a/packages/react-events/src/Focus.js +++ b/packages/react-events/src/Focus.js @@ -51,27 +51,19 @@ function dispatchFocusInEvents( context: ResponderContext, props: FocusProps, ) { - const {nativeEvent, eventTarget} = event; + const {nativeEvent, target} = event; if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onFocus) { - const syntheticEvent = createFocusEvent( - 'focus', - eventTarget, - props.onFocus, - ); + const syntheticEvent = createFocusEvent('focus', target, props.onFocus); context.dispatchEvent(syntheticEvent, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(true); }; - const syntheticEvent = createFocusEvent( - 'focuschange', - eventTarget, - listener, - ); + const syntheticEvent = createFocusEvent('focuschange', target, listener); context.dispatchEvent(syntheticEvent, {discrete: true}); } } @@ -81,23 +73,19 @@ function dispatchFocusOutEvents( context: ResponderContext, props: FocusProps, ) { - const {nativeEvent, eventTarget} = event; + const {nativeEvent, target} = event; if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onBlur) { - const syntheticEvent = createFocusEvent('blur', eventTarget, props.onBlur); + const syntheticEvent = createFocusEvent('blur', target, props.onBlur); context.dispatchEvent(syntheticEvent, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(false); }; - const syntheticEvent = createFocusEvent( - 'focuschange', - eventTarget, - listener, - ); + const syntheticEvent = createFocusEvent('focuschange', target, listener); context.dispatchEvent(syntheticEvent, {discrete: true}); } } @@ -115,9 +103,9 @@ const FocusResponder = { props: Object, state: FocusState, ): void { - const {eventType} = event; + const {type} = event; - switch (eventType) { + switch (type) { case 'focus': { if (!state.isFocused && !context.hasOwnership()) { dispatchFocusInEvents(event, context, props); diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index fde53cf52a684..050a50bda0c31 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -65,14 +65,14 @@ function dispatchHoverStartEvents( context: ResponderContext, props: HoverProps, ): void { - const {nativeEvent, eventTarget} = event; + const {nativeEvent, target} = event; if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onHoverStart) { const syntheticEvent = createHoverEvent( 'hoverstart', - eventTarget, + target, props.onHoverStart, ); context.dispatchEvent(syntheticEvent, {discrete: true}); @@ -81,11 +81,7 @@ function dispatchHoverStartEvents( const listener = () => { props.onHoverChange(true); }; - const syntheticEvent = createHoverEvent( - 'hoverchange', - eventTarget, - listener, - ); + const syntheticEvent = createHoverEvent('hoverchange', target, listener); context.dispatchEvent(syntheticEvent, {discrete: true}); } } @@ -95,14 +91,14 @@ function dispatchHoverEndEvents( context: ResponderContext, props: HoverProps, ) { - const {nativeEvent, eventTarget} = event; + const {nativeEvent, target} = event; if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { return; } if (props.onHoverEnd) { const syntheticEvent = createHoverEvent( 'hoverend', - eventTarget, + target, props.onHoverEnd, ); context.dispatchEvent(syntheticEvent, {discrete: true}); @@ -111,11 +107,7 @@ function dispatchHoverEndEvents( const listener = () => { props.onHoverChange(false); }; - const syntheticEvent = createHoverEvent( - 'hoverchange', - eventTarget, - listener, - ); + const syntheticEvent = createHoverEvent('hoverchange', target, listener); context.dispatchEvent(syntheticEvent, {discrete: true}); } } @@ -135,9 +127,9 @@ const HoverResponder = { props: HoverProps, state: HoverState, ): void { - const {eventType, nativeEvent} = event; + const {type, nativeEvent} = event; - switch (eventType) { + switch (type) { /** * Prevent hover events when touch is being used. */ diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index 94b3f77beb183..ab2750d6271b8 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -236,9 +236,9 @@ const PressResponder = { props: PressProps, state: PressState, ): void { - const {eventTarget, eventType, nativeEvent} = event; + const {target, type, nativeEvent} = event; - switch (eventType) { + switch (type) { /** * Respond to pointer events and fall back to mouse. */ @@ -251,7 +251,7 @@ const PressResponder = { ) { if ( (nativeEvent: any).pointerType === 'mouse' || - eventType === 'mousedown' + type === 'mousedown' ) { if ( // Ignore right- and middle-clicks @@ -266,9 +266,9 @@ const PressResponder = { return; } } - state.pressTarget = eventTarget; + state.pressTarget = target; dispatchPressStartEvents(context, props, state); - context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); + context.addRootEventTypes(target.ownerDocument, rootEventTypes); } break; } @@ -285,7 +285,7 @@ const PressResponder = { dispatchPressEndEvents(context, props, state); if (state.pressTarget !== null && props.onPress) { - if (context.isTargetWithinElement(eventTarget, state.pressTarget)) { + if (context.isTargetWithinElement(target, state.pressTarget)) { if ( !( wasLongPressed && @@ -318,13 +318,13 @@ const PressResponder = { if (!state.isPressed && !context.hasOwnership()) { // We bail out of polyfilling anchor tags, given the same heuristics // explained above in regards to needing to use click events. - if (isAnchorTagElement(eventTarget)) { + if (isAnchorTagElement(target)) { state.isAnchorTouched = true; return; } - state.pressTarget = eventTarget; + state.pressTarget = target; dispatchPressStartEvents(context, props, state); - context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); + context.addRootEventTypes(target.ownerDocument, rootEventTypes); } break; } @@ -338,17 +338,17 @@ const PressResponder = { dispatchPressEndEvents(context, props, state); - if (eventType !== 'touchcancel' && props.onPress) { + if (type !== 'touchcancel' && props.onPress) { // Find if the X/Y of the end touch is still that of the original target const changedTouch = (nativeEvent: any).changedTouches[0]; - const doc = (eventTarget: any).ownerDocument; - const target = doc.elementFromPoint( + const doc = (target: any).ownerDocument; + const fromTarget = doc.elementFromPoint( changedTouch.screenX, changedTouch.screenY, ); if ( - target !== null && - context.isTargetWithinEventComponent(target) + fromTarget !== null && + context.isTargetWithinEventComponent(fromTarget) ) { if ( !( @@ -382,9 +382,9 @@ const PressResponder = { if ((nativeEvent: any).key === ' ') { (nativeEvent: any).preventDefault(); } - state.pressTarget = eventTarget; + state.pressTarget = target; dispatchPressStartEvents(context, props, state); - context.addRootEventTypes(eventTarget.ownerDocument, rootEventTypes); + context.addRootEventTypes(target.ownerDocument, rootEventTypes); } break; } diff --git a/packages/react-events/src/Swipe.js b/packages/react-events/src/Swipe.js index 42949df73ba85..ed211c939ef48 100644 --- a/packages/react-events/src/Swipe.js +++ b/packages/react-events/src/Swipe.js @@ -97,15 +97,15 @@ const SwipeResponder = { props: Object, state: SwipeState, ): void { - const {eventTarget, eventType, nativeEvent} = event; + const {target, type, nativeEvent} = event; - switch (eventType) { + switch (type) { case 'touchstart': case 'mousedown': case 'pointerdown': { if (!state.isSwiping && !context.hasOwnership()) { let obj = event; - if (eventType === 'touchstart') { + if (type === 'touchstart') { obj = (nativeEvent: any).targetTouches[0]; state.touchId = obj.identifier; } @@ -123,11 +123,8 @@ const SwipeResponder = { state.startY = y; state.x = x; state.y = y; - state.swipeTarget = eventTarget; - context.addRootEventTypes( - eventTarget.ownerDocument, - rootEventTypes, - ); + state.swipeTarget = target; + context.addRootEventTypes(target.ownerDocument, rootEventTypes); } else { state.touchId = null; } @@ -142,7 +139,7 @@ const SwipeResponder = { } if (state.isSwiping) { let obj = null; - if (eventType === 'touchmove') { + if (type === 'touchmove') { const targetTouches = (nativeEvent: any).targetTouches; for (let i = 0; i < targetTouches.length; i++) { if (state.touchId === targetTouches[i].identifier) { From 7a81cdffeb228ef4cde4c645ea0f63472ef125be Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 4 Apr 2019 22:47:10 +0100 Subject: [PATCH 4/4] Address feedback --- packages/events/EventTypes.js | 12 +++++++----- .../src/events/DOMEventResponderSystem.js | 16 ++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/events/EventTypes.js b/packages/events/EventTypes.js index eedab72cf39bf..ab5c51db640a0 100644 --- a/packages/events/EventTypes.js +++ b/packages/events/EventTypes.js @@ -18,14 +18,16 @@ export type ResponderEvent = { passiveSupported: boolean, }; +export type ResponderDispatchEventOptions = { + capture?: boolean, + discrete?: boolean, + stopPropagation?: boolean, +}; + export type ResponderContext = { dispatchEvent: ( eventObject: Object, - { - capture?: boolean, - discrete?: boolean, - stopPropagation?: boolean, - }, + otpions: ResponderDispatchEventOptions, ) => void, isTargetWithinElement: ( childTarget: Element | Document, diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 2eeefbf82c768..7630d8d7b427f 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -6,7 +6,11 @@ * @flow */ -import type {ResponderContext, ResponderEvent} from 'events/EventTypes'; +import type { + ResponderContext, + ResponderEvent, + ResponderDispatchEventOptions, +} from 'events/EventTypes'; import { type EventSystemFlags, IS_PASSIVE, @@ -55,15 +59,7 @@ let currentEventQueue: EventQueue; const eventResponderContext: ResponderContext = { dispatchEvent( possibleEventObject: Object, - { - capture, - discrete, - stopPropagation, - }: { - capture?: boolean, - discrete?: boolean, - stopPropagation?: boolean, - }, + {capture, discrete, stopPropagation}: ResponderDispatchEventOptions, ): void { const eventQueue = currentEventQueue; const {listener, target, type} = possibleEventObject;