From 339bbb9bebc562525860c03627547e1976e39eeb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 10:55:36 -0700 Subject: [PATCH 1/8] Adds Press and Hover event modules + more features to the Event Responder System --- packages/events/EventTypes.js | 37 ++ .../react-dom/src/client/ReactDOMComponent.js | 11 +- .../src/client/ReactDOMHostConfig.js | 7 +- .../src/events/DOMEventResponderSystem.js | 195 ++++++---- packages/react-events/npm/press.js | 7 + packages/react-events/package.json | 1 + packages/react-events/press.js | 14 + packages/react-events/src/Press.js | 332 ++++++++++++++++++ scripts/rollup/bundles.js | 8 + scripts/rollup/results.json | 44 ++- 10 files changed, 573 insertions(+), 83 deletions(-) create mode 100644 packages/events/EventTypes.js create mode 100644 packages/react-events/npm/press.js create mode 100644 packages/react-events/press.js create mode 100644 packages/react-events/src/Press.js diff --git a/packages/events/EventTypes.js b/packages/events/EventTypes.js new file mode 100644 index 00000000000..bba4e239881 --- /dev/null +++ b/packages/events/EventTypes.js @@ -0,0 +1,37 @@ +/** + * 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 SyntheticEvent from 'events/SyntheticEvent'; +import type {AnyNativeEvent} from 'events/PluginModuleType'; + +export type EventResponderContext = { + event: AnyNativeEvent, + eventTarget: EventTarget, + eventType: string, + isPassive: () => boolean, + isPassiveSupported: () => boolean, + dispatchEvent: ( + name: string, + listener: (e: SyntheticEvent) => void | null, + pressTarget: EventTarget | null, + discrete: boolean, + extraProperties?: Object, + ) => void, + isTargetWithinElement: ( + childTarget: EventTarget, + parentTarget: EventTarget, + ) => boolean, + isTargetOwned: EventTarget => boolean, + isTargetWithinEventComponent: EventTarget => boolean, + isPositionWithinTouchHitTarget: (x: number, y: number) => boolean, + addRootEventTypes: (rootEventTypes: Array) => void, + removeRootEventTypes: (rootEventTypes: Array) => void, + requestOwnership: (target: EventTarget | null) => boolean, + releaseOwnership: (target: EventTarget | null) => boolean, +}; diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index bc1060d2d6d..2d671b6cd73 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -13,7 +13,7 @@ import {registrationNameModules} from 'events/EventPluginRegistry'; import warning from 'shared/warning'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import warningWithoutStack from 'shared/warningWithoutStack'; -import type {ReactEventResponder} from 'shared/ReactTypes'; +import type {ReactEventResponderEventType} from 'shared/ReactTypes'; import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; import { @@ -1277,19 +1277,18 @@ export function restoreControlledState( } } -export function listenToEventResponderEvents( - eventResponder: ReactEventResponder, +export function listenToEventResponderEventTypes( + eventTypes: Array, element: Element | Document, ): void { if (enableEventAPI) { - const {targetEventTypes} = eventResponder; // Get the listening Set for this element. We use this to track // what events we're listening to. const listeningSet = getListeningSetForElement(element); // Go through each target event type of the event responder - for (let i = 0, length = targetEventTypes.length; i < length; ++i) { - const targetEventType = targetEventTypes[i]; + for (let i = 0, length = eventTypes.length; i < length; ++i) { + const targetEventType = eventTypes[i]; let topLevelType; let capture = false; let passive = true; diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 52541406190..97bfa60274d 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -24,7 +24,7 @@ import { warnForDeletedHydratableText, warnForInsertedHydratedElement, warnForInsertedHydratedText, - listenToEventResponderEvents, + listenToEventResponderEventTypes, } from './ReactDOMComponent'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; @@ -864,7 +864,10 @@ export function handleEventComponent( ): void { if (enableEventAPI) { const rootElement = rootContainerInstance.ownerDocument; - listenToEventResponderEvents(eventResponder, rootElement); + listenToEventResponderEventTypes( + eventResponder.targetEventTypes, + rootElement, + ); } } diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 98969ac8a5b..5e97259fdbb 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -13,25 +13,23 @@ import { } from 'events/EventSystemFlags'; import type {AnyNativeEvent} from 'events/PluginModuleType'; import {EventComponent} from 'shared/ReactWorkTags'; -import type {ReactEventResponder} from 'shared/ReactTypes'; -import warning from 'shared/warning'; +import type { + ReactEventResponder, + ReactEventResponderEventType, +} from 'shared/ReactTypes'; import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; import SyntheticEvent from 'events/SyntheticEvent'; import {runEventsInBatch} from 'events/EventBatching'; import {interactiveUpdates} from 'events/ReactGenericBatching'; +import {executeDispatch} from 'events/EventPluginUtils'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; +import {listenToEventResponderEventTypes} from '../client/ReactDOMComponent'; import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; -// Event responders provide us an array of target event types. -// To ensure we fire the right responders for given events, we check -// if the incoming event type is actually relevant for an event -// responder. Instead of doing an O(n) lookup on the event responder -// target event types array each time, we instead create a Set for -// faster O(1) lookups. -export const eventResponderValidEventTypes: Map< - ReactEventResponder, - Set, +const rootEventTypesToEventComponents: Map< + DOMTopLevelEventType | string, + Set, > = new Map(); type EventListener = (event: SyntheticEvent) => void; @@ -44,13 +42,14 @@ function DOMEventResponderContext( eventSystemFlags: EventSystemFlags, ) { this.event = nativeEvent; - this.eventType = topLevelType; this.eventTarget = nativeEventTarget; + this.eventType = topLevelType; this._flags = eventSystemFlags; this._fiber = null; this._responder = null; this._discreteEvents = null; this._nonDiscreteEvents = null; + this._isBatching = true; } DOMEventResponderContext.prototype.isPassive = function(): boolean { @@ -88,80 +87,121 @@ DOMEventResponderContext.prototype.dispatchEvent = function( syntheticEvent._dispatchInstances = [eventTargetFiber]; syntheticEvent._dispatchListeners = [eventListener]; - let events; - if (discrete) { - events = this._discreteEvents; - if (events === null) { - events = this._discreteEvents = []; + if (this._isBatching) { + let events; + if (discrete) { + events = this._discreteEvents; + if (events === null) { + events = this._discreteEvents = []; + } + } else { + events = this._nonDiscreteEvents; + if (events === null) { + events = this._nonDiscreteEvents = []; + } } + events.push(syntheticEvent); } else { - events = this._nonDiscreteEvents; - if (events === null) { - events = this._nonDiscreteEvents = []; + if (discrete) { + interactiveUpdates(() => { + executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + }); + } else { + executeDispatch(syntheticEvent, eventListener, eventTargetFiber); } } - events.push(syntheticEvent); }; -DOMEventResponderContext.prototype._runEventsInBatch = function(): void { - if (this._discreteEvents !== null) { - interactiveUpdates(() => { - runEventsInBatch(this._discreteEvents); - }); +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; + } } - if (this._nonDiscreteEvents !== null) { - runEventsInBatch(this._nonDiscreteEvents); + 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; }; -function createValidEventTypeSet(targetEventTypes): Set { - const eventTypeSet = new Set(); - // Go through each target event type of the event responder - for (let i = 0, length = targetEventTypes.length; i < length; ++i) { - const targetEventType = targetEventTypes[i]; +DOMEventResponderContext.prototype.addRootEventTypes = function( + rootEventTypes: Array, +) { + const element = this.eventTarget.ownerDocument; + listenToEventResponderEventTypes(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); + } +}; - if (typeof targetEventType === 'string') { - eventTypeSet.add(((targetEventType: any): DOMTopLevelEventType)); - } else { - if (__DEV__) { - warning( - typeof targetEventType === 'object' && targetEventType !== null, - 'Event Responder: invalid entry in targetEventTypes array. ' + - 'Entry must be string or an object. Instead, got %s.', - targetEventType, - ); - } - const targetEventConfigObject = ((targetEventType: any): { - name: DOMTopLevelEventType, - passive?: boolean, - capture?: boolean, - }); - eventTypeSet.add(targetEventConfigObject.name); +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); } } - return eventTypeSet; -} +}; function handleTopLevelType( topLevelType: DOMTopLevelEventType, fiber: Fiber, context: Object, + isRootLevelEvent: boolean, ): void { const responder: ReactEventResponder = fiber.type.responder; - let {props, state} = fiber.stateNode; - let validEventTypesForResponder = eventResponderValidEventTypes.get( - responder, - ); - - if (validEventTypesForResponder === undefined) { - validEventTypesForResponder = createValidEventTypeSet( - responder.targetEventTypes, - ); - eventResponderValidEventTypes.set(responder, validEventTypesForResponder); - } - if (!validEventTypesForResponder.has(topLevelType)) { - return; + if (!isRootLevelEvent) { + // Validate the target event type exists on the responder + const targetEventTypes = responder.targetEventTypes; + if (targetEventTypes.indexOf(topLevelType) === -1) { + return; + } } + let {props, state} = fiber.stateNode; if (state === null && responder.createInitialState !== undefined) { state = fiber.stateNode.state = responder.createInitialState(props); } @@ -187,9 +227,30 @@ 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); + handleTopLevelType(topLevelType, node, context, false); } node = node.return; } - context._runEventsInBatch(); + // Handle root level events + const rootEventComponents = rootEventTypesToEventComponents.get(topLevelType); + if (rootEventComponents !== undefined) { + const rootEventComponentFibers = Array.from(rootEventComponents); + + for (let i = 0; i < rootEventComponentFibers.length; i++) { + const rootEventComponentFiber = rootEventComponentFibers[i]; + handleTopLevelType(topLevelType, rootEventComponentFiber, context, true); + } + } + // Run batched events + const discreteEvents = context._discreteEvents; + if (discreteEvents !== null) { + interactiveUpdates(() => { + runEventsInBatch(discreteEvents); + }); + } + const nonDiscreteEvents = context._nonDiscreteEvents; + if (nonDiscreteEvents !== null) { + runEventsInBatch(nonDiscreteEvents); + } + context._isBatching = false; } diff --git a/packages/react-events/npm/press.js b/packages/react-events/npm/press.js new file mode 100644 index 00000000000..deaba326bba --- /dev/null +++ b/packages/react-events/npm/press.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-events-press.production.min.js'); +} else { + module.exports = require('./cjs/react-events-press.development.js'); +} diff --git a/packages/react-events/package.json b/packages/react-events/package.json index b680069e115..a61330b9ce4 100644 --- a/packages/react-events/package.json +++ b/packages/react-events/package.json @@ -11,6 +11,7 @@ "files": [ "LICENSE", "README.md", + "press.js", "build-info.json", "cjs/", "umd/" diff --git a/packages/react-events/press.js b/packages/react-events/press.js new file mode 100644 index 00000000000..8b5d2f28345 --- /dev/null +++ b/packages/react-events/press.js @@ -0,0 +1,14 @@ +/** + * 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 + */ + +'use strict'; + +import Press from './src/Press'; + +export default Press; diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js new file mode 100644 index 00000000000..5c940ca43a1 --- /dev/null +++ b/packages/react-events/src/Press.js @@ -0,0 +1,332 @@ +/** + * 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 {EventResponderContext} from 'events/EventTypes'; +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; + +const targetEventTypes = [ + {name: 'click', passive: false}, + {name: 'keydown', passive: false}, + 'pointerdown', + 'pointercancel', + 'contextmenu', +]; +const rootEventTypes = ['pointerup', 'scroll']; + +// In the case we don't have PointerEvents (Safari), we listen to touch events +// too +if (typeof window !== 'undefined' && window.PointerEvent === undefined) { + targetEventTypes.push( + 'touchstart', + {name: 'touchend', passive: false}, + 'mousedown', + 'touchcancel', + ); + rootEventTypes.push('mouseup'); +} + +type PressState = { + defaultPrevented: boolean, + isAnchorTouched: boolean, + isLongPressed: boolean, + isPressed: boolean, + longPressTimeout: null | TimeoutID, + pressTarget: null | EventTarget, +}; + +function dispatchPressEvent( + context: EventResponderContext, + name: string, + state: PressState, + listener: (e: Object) => void, +): void { + context.dispatchEvent(name, listener, state.pressTarget, true); +} + +function dispatchPressInEvents( + context: EventResponderContext, + props: Object, + state: PressState, +): void { + if (props.onPressIn) { + context.dispatchEvent('pressin', props.onPressIn, state.pressTarget, false); + } + if (props.onPressChange) { + const pressChangeEventListener = () => { + props.onPressChange(true); + }; + context.dispatchEvent( + 'presschange', + pressChangeEventListener, + state.pressTarget, + true, + ); + } + if (!state.isLongPressed && (props.onLongPress || props.onLongPressChange)) { + const longPressDelay = props.longPressDelay || 1000; + state.longPressTimeout = setTimeout(() => { + state.isLongPressed = true; + state.longPressTimeout = null; + if (props.onLongPressChange) { + const longPressChangeEventListener = () => { + props.onLongPressChange(true); + }; + context.dispatchEvent( + 'longpresschange', + longPressChangeEventListener, + state.pressTarget, + true, + ); + } + }, longPressDelay); + } +} + +function dispatchPressOutEvents( + context: EventResponderContext, + props: Object, + state: PressState, +): void { + if (state.longPressTimeout !== null) { + clearTimeout(state.longPressTimeout); + state.longPressTimeout = null; + } + if (props.onPressOut) { + context.dispatchEvent( + 'pressout', + props.onPressOut, + state.pressTarget, + false, + ); + } + if (props.onPressChange) { + const pressChangeEventListener = () => { + props.onPressChange(false); + }; + context.dispatchEvent( + 'presschange', + pressChangeEventListener, + state.pressTarget, + true, + ); + } + if (props.onLongPressChange && state.isLongPressed) { + const longPressChangeEventListener = () => { + props.onLongPressChange(false); + }; + context.dispatchEvent( + 'longpresschange', + longPressChangeEventListener, + state.pressTarget, + true, + ); + } +} + +function isAnchorTagElement(eventTarget: EventTarget): boolean { + return (eventTarget: any).nodeName === 'A'; +} + +const PressResponder = { + targetEventTypes, + createInitialState(): PressState { + return { + defaultPrevented: false, + isAnchorTouched: false, + isLongPressed: false, + isPressed: false, + longPressTimeout: null, + pressTarget: null, + }; + }, + handleEvent( + context: EventResponderContext, + props: Object, + state: PressState, + ): void { + const {eventTarget, eventType, event} = context; + + switch (eventType) { + case 'keydown': { + if (!props.onPress || context.isTargetOwned(eventTarget)) { + return; + } + const isValidKeyPress = + (event: any).which === 13 || + (event: any).which === 32 || + (event: any).keyCode === 13; + + if (!isValidKeyPress) { + return; + } + let keyPressEventListener = props.onPress; + + // Wrap listener with prevent default behaviour, unless + // we are dealing with an anchor + if (!isAnchorTagElement(eventTarget)) { + keyPressEventListener = (e, key) => { + if (!e.isDefaultPrevented() && !e.nativeEvent.defaultPrevented) { + e.preventDefault(); + state.defaultPrevented = true; + props.onPress(e, key); + } + }; + } + dispatchPressEvent(context, 'press', state, keyPressEventListener); + break; + } + case 'touchstart': + // Touch events are for Safari, which lack pointer event support. + if (!state.isPressed && !context.isTargetOwned(eventTarget)) { + // We bail out of polyfilling anchor tags + if (isAnchorTagElement(eventTarget)) { + state.isAnchorTouched = true; + return; + } + state.pressTarget = eventTarget; + dispatchPressInEvents(context, props, state); + state.isPressed = true; + context.addRootEventTypes(rootEventTypes); + } + + break; + case 'touchend': { + // Touch events are for Safari, which lack pointer event support + if (state.isAnchorTouched) { + return; + } + if (state.isPressed) { + dispatchPressOutEvents(context, props, state); + if ( + eventType !== 'touchcancel' && + (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 doc = (eventTarget: any).ownerDocument; + const target = doc.elementFromPoint( + changedTouch.screenX, + changedTouch.screenY, + ); + if ( + target !== null && + context.isTargetWithinEventComponent(target) + ) { + if (state.isLongPressed && props.onLongPress) { + dispatchPressEvent( + context, + 'longpress', + state, + props.onLongPress, + ); + } else if (props.onPress) { + dispatchPressEvent(context, 'press', state, props.onPress); + } + } + } + state.isPressed = false; + state.isLongPressed = false; + // Prevent mouse events from firing + (event: any).preventDefault(); + context.removeRootEventTypes(rootEventTypes); + } + break; + } + case 'pointerdown': + case 'mousedown': { + if (!state.isPressed && !context.isTargetOwned(eventTarget)) { + if ((event: any).pointerType === 'mouse') { + // Ignore if we are pressing on hit slop area with mouse + if ( + context.isPositionWithinTouchHitTarget( + (event: any).x, + (event: any).y, + ) + ) { + return; + } + // Ignore right-clicks + if (event.button === 2) { + return; + } + } + state.pressTarget = eventTarget; + dispatchPressInEvents(context, props, state); + state.isPressed = true; + context.addRootEventTypes(rootEventTypes); + } + break; + } + case 'mouseup': + case 'pointerup': { + if (state.isPressed && !context.isPassive) { + dispatchPressOutEvents(context, props, state); + if ( + state.pressTarget !== null && + (props.onPress || props.onLongPress) + ) { + if (context.isTargetWithinElement(eventTarget, state.pressTarget)) { + if (state.isLongPressed && props.onLongPress) { + const longPressEventListener = e => { + props.onLongPress(e); + if (e.nativeEvent.defaultPrevented) { + state.defaultPrevented = true; + } + }; + dispatchPressEvent( + context, + 'longpress', + state, + longPressEventListener, + ); + } else if (props.onPress) { + const pressEventListener = (e, key) => { + props.onPress(e, key); + if (e.nativeEvent.defaultPrevented) { + state.defaultPrevented = true; + } + }; + dispatchPressEvent(context, 'press', state, pressEventListener); + } + } + } + state.isPressed = false; + state.isLongPressed = false; + context.removeRootEventTypes(rootEventTypes); + } + state.isAnchorTouched = false; + break; + } + case 'scroll': + case 'touchcancel': + case 'contextmenu': + case 'pointercancel': { + if (state.isPressed) { + dispatchPressOutEvents(context, props, state); + state.isPressed = false; + state.isLongPressed = false; + context.removeRootEventTypes(rootEventTypes); + } + break; + } + case 'click': { + if (state.defaultPrevented && !context.isPassive) { + (event: any).preventDefault(); + state.defaultPrevented = false; + } + } + } + }, +}; + +export default { + $$typeof: REACT_EVENT_COMPONENT_TYPE, + props: null, + responder: PressResponder, +}; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 898c39728f9..cde77647b67 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -470,6 +470,14 @@ const bundles = [ global: 'ReactEvents', externals: [], }, + + { + bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], + moduleType: NON_FIBER_RENDERER, + entry: 'react-events/press', + global: 'ReactEventsPress', + externals: [], + }, ]; // Based on deep-freeze by substack (public domain) diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 346e655064d..3eb05826088 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1152,29 +1152,57 @@ "filename": "react-events.development.js", "bundleType": "NODE_DEV", "packageName": "react-events", - "size": 1135, - "gzip": 623 + "size": 990, + "gzip": 545 }, { "filename": "react-events.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-events", - "size": 448, - "gzip": 328 + "size": 506, + "gzip": 343 }, { "filename": "ReactEvents-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-events", - "size": 1106, - "gzip": 613 + "size": 956, + "gzip": 536 }, { "filename": "ReactEvents-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-events", - "size": 643, - "gzip": 377 + "size": 687, + "gzip": 410 + }, + { + "filename": "react-events-press.development.js", + "bundleType": "NODE_DEV", + "packageName": "react-events", + "size": 9224, + "gzip": 2156 + }, + { + "filename": "react-events-press.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-events", + "size": 3819, + "gzip": 1305 + }, + { + "filename": "ReactEventsPress-dev.js", + "bundleType": "FB_WWW_DEV", + "packageName": "react-events", + "size": 9445, + "gzip": 2203 + }, + { + "filename": "ReactEventsPress-prod.js", + "bundleType": "FB_WWW_PROD", + "packageName": "react-events", + "size": 7636, + "gzip": 1668 } ] } \ No newline at end of file From aa3801da79652d009d84a9fbc337a38f280f58d2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 12:48:50 -0700 Subject: [PATCH 2/8] Add hover implementation --- packages/events/EventPluginUtils.js | 2 +- .../src/events/DOMEventResponderSystem.js | 16 ++ packages/react-events/hover.js | 14 ++ packages/react-events/npm/hover.js | 7 + packages/react-events/src/Hover.js | 179 +++++++++++++++ packages/react-events/src/Press.js | 8 +- scripts/rollup/bundles.js | 33 ++- scripts/rollup/results.json | 212 ++++++++++++------ 8 files changed, 393 insertions(+), 78 deletions(-) create mode 100644 packages/react-events/hover.js create mode 100644 packages/react-events/npm/hover.js create mode 100644 packages/react-events/src/Hover.js diff --git a/packages/events/EventPluginUtils.js b/packages/events/EventPluginUtils.js index ac7cd97dd15..2e9b7e9d16e 100644 --- a/packages/events/EventPluginUtils.js +++ b/packages/events/EventPluginUtils.js @@ -63,7 +63,7 @@ if (__DEV__) { * @param {function} listener Application-level callback * @param {*} inst Internal component instance */ -function executeDispatch(event, listener, inst) { +export function executeDispatch(event, listener, inst) { const type = event.type || 'unknown-event'; event.currentTarget = getNodeFromInstance(inst); invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 5e97259fdbb..87ca209d765 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -187,6 +187,22 @@ DOMEventResponderContext.prototype.removeRootEventTypes = function( } }; +DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() { + // TODO +}; + +DOMEventResponderContext.prototype.isTargetOwned = function() { + // TODO +}; + +DOMEventResponderContext.prototype.requestOwnership = function() { + // TODO +}; + +DOMEventResponderContext.prototype.releaseOwnership = function() { + // TODO +}; + function handleTopLevelType( topLevelType: DOMTopLevelEventType, fiber: Fiber, diff --git a/packages/react-events/hover.js b/packages/react-events/hover.js new file mode 100644 index 00000000000..a6e602045f4 --- /dev/null +++ b/packages/react-events/hover.js @@ -0,0 +1,14 @@ +/** + * 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 + */ + +'use strict'; + +import Hover from './src/Hover'; + +export default Hover; diff --git a/packages/react-events/npm/hover.js b/packages/react-events/npm/hover.js new file mode 100644 index 00000000000..1000d874490 --- /dev/null +++ b/packages/react-events/npm/hover.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-events-hover.production.min.js'); +} else { + module.exports = require('./cjs/react-events-hover.development.js'); +} diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js new file mode 100644 index 00000000000..8cb78b77517 --- /dev/null +++ b/packages/react-events/src/Hover.js @@ -0,0 +1,179 @@ +/** + * 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 {EventResponderContext} from 'events/EventTypes'; +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols'; + +const targetEventTypes = [ + 'pointerover', + 'pointermove', + 'pointerout', + 'pointercancel', +]; + +type HoverState = { + isHovered: boolean, + isInHitSlop: boolean, + isTouched: boolean, +}; + +// In the case we don't have PointerEvents (Safari), we listen to touch events +// too +if (typeof window !== 'undefined' && window.PointerEvent === undefined) { + targetEventTypes.push('touchstart', 'mouseover', 'mouseout'); +} + +function dispatchHoverInEvents( + context: EventResponderContext, + props: Object, + state: HoverState, +): void { + const {event, eventTarget} = context; + if (props.onHoverChange) { + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { + return; + } + if (props.onHoverIn) { + context.dispatchEvent('hoverin', props.onHoverIn, eventTarget, true); + } + const hoverChangeEventListener = () => { + props.onHoverChange(true); + }; + context.dispatchEvent( + 'hoverchange', + hoverChangeEventListener, + eventTarget, + true, + ); + } +} + +function dispatchHoverOutEvents(context: EventResponderContext, props: Object) { + const {event, eventTarget} = context; + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) { + return; + } + if (props.onHoverOut) { + context.dispatchEvent('hoverout', props.onHoverOut, eventTarget, true); + } + if (props.onHoverChange) { + const hoverChangeEventListener = () => { + props.onHoverChange(false); + }; + context.dispatchEvent( + 'hoverchange', + hoverChangeEventListener, + eventTarget, + true, + ); + } +} + +const HoverResponder = { + targetEventTypes, + createInitialState() { + return { + isHovered: false, + isInHitSlop: false, + isTouched: false, + }; + }, + handleEvent( + context: EventResponderContext, + props: Object, + state: HoverState, + ): void { + const {eventType, eventTarget, event} = context; + + switch (eventType) { + case 'touchstart': + // Touch devices don't have hover support + if (!state.isTouched) { + state.isTouched = true; + } + break; + case 'pointerover': + case 'mouseover': { + if ( + !state.isHovered && + !state.isTouched && + !context.isTargetOwned(eventTarget) + ) { + if ((event: any).pointerType === 'touch') { + state.isTouched = true; + return; + } + if ( + context.isPositionWithinTouchHitTarget( + (event: any).x, + (event: any).y, + ) + ) { + state.isInHitSlop = true; + return; + } + dispatchHoverInEvents(context, props, state); + state.isHovered = true; + } + break; + } + case 'pointerout': + case 'mouseout': { + if (state.isHovered && !state.isTouched) { + dispatchHoverOutEvents(context, props); + state.isHovered = false; + } + state.isInHitSlop = false; + state.isTouched = false; + break; + } + case 'pointermove': { + if (!state.isTouched) { + if (state.isInHitSlop) { + if ( + !context.isPositionWithinTouchHitTarget( + (event: any).x, + (event: any).y, + ) + ) { + dispatchHoverInEvents(context, props, state); + state.isHovered = true; + state.isInHitSlop = false; + } + } else if ( + state.isHovered && + context.isPositionWithinTouchHitTarget( + (event: any).x, + (event: any).y, + ) + ) { + dispatchHoverOutEvents(context, props); + state.isHovered = false; + state.isInHitSlop = true; + } + } + break; + } + case 'pointercancel': { + if (state.isHovered && !state.isTouched) { + dispatchHoverOutEvents(context, props); + state.isHovered = false; + state.isTouched = false; + } + break; + } + } + }, +}; + +export default { + $$typeof: REACT_EVENT_COMPONENT_TYPE, + props: null, + responder: HoverResponder, +}; diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index 5c940ca43a1..7cbec2441e9 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -55,7 +55,7 @@ function dispatchPressInEvents( state: PressState, ): void { if (props.onPressIn) { - context.dispatchEvent('pressin', props.onPressIn, state.pressTarget, false); + context.dispatchEvent('pressin', props.onPressIn, state.pressTarget, true); } if (props.onPressChange) { const pressChangeEventListener = () => { @@ -102,7 +102,7 @@ function dispatchPressOutEvents( 'pressout', props.onPressOut, state.pressTarget, - false, + true, ); } if (props.onPressChange) { @@ -265,7 +265,7 @@ const PressResponder = { } case 'mouseup': case 'pointerup': { - if (state.isPressed && !context.isPassive) { + if (state.isPressed) { dispatchPressOutEvents(context, props, state); if ( state.pressTarget !== null && @@ -316,7 +316,7 @@ const PressResponder = { break; } case 'click': { - if (state.defaultPrevented && !context.isPassive) { + if (state.defaultPrevented) { (event: any).preventDefault(); state.defaultPrevented = false; } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index cde77647b67..078a4697e52 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -464,7 +464,14 @@ const bundles = [ /******* React Events (experimental) *******/ { - bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], + bundleTypes: [ + UMD_DEV, + UMD_PROD, + NODE_DEV, + NODE_PROD, + FB_WWW_DEV, + FB_WWW_PROD, + ], moduleType: ISOMORPHIC, entry: 'react-events', global: 'ReactEvents', @@ -472,12 +479,34 @@ const bundles = [ }, { - bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], + bundleTypes: [ + UMD_DEV, + UMD_PROD, + NODE_DEV, + NODE_PROD, + FB_WWW_DEV, + FB_WWW_PROD, + ], moduleType: NON_FIBER_RENDERER, entry: 'react-events/press', global: 'ReactEventsPress', externals: [], }, + + { + bundleTypes: [ + UMD_DEV, + UMD_PROD, + NODE_DEV, + NODE_PROD, + FB_WWW_DEV, + FB_WWW_PROD, + ], + moduleType: NON_FIBER_RENDERER, + entry: 'react-events/hover', + global: 'ReactEventsHover', + externals: [], + }, ]; // Based on deep-freeze by substack (public domain) diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 3eb05826088..1a1a295e2e8 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -18,15 +18,15 @@ "filename": "react.development.js", "bundleType": "NODE_DEV", "packageName": "react", - "size": 64139, - "gzip": 17318 + "size": 65436, + "gzip": 17596 }, { "filename": "react.production.min.js", "bundleType": "NODE_PROD", "packageName": "react", - "size": 6834, - "gzip": 2814 + "size": 6529, + "gzip": 2695 }, { "filename": "React-dev.js", @@ -60,15 +60,15 @@ "filename": "react-dom.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 813661, - "gzip": 184364 + "size": 825762, + "gzip": 186861 }, { "filename": "react-dom.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108035, - "gzip": 34515 + "size": 107656, + "gzip": 34298 }, { "filename": "ReactDOM-dev.js", @@ -137,8 +137,8 @@ "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 61643, - "gzip": 16033 + "size": 61854, + "gzip": 16078 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", @@ -179,15 +179,15 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 132758, - "gzip": 35195 + "size": 132878, + "gzip": 35237 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 19756, - "gzip": 7540 + "size": 19287, + "gzip": 7290 }, { "filename": "ReactDOMServer-dev.js", @@ -207,15 +207,15 @@ "filename": "react-dom-server.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 134747, - "gzip": 35752 + "size": 134867, + "gzip": 35791 }, { "filename": "react-dom-server.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 20639, - "gzip": 7850 + "size": 20170, + "gzip": 7598 }, { "filename": "react-art.development.js", @@ -235,15 +235,15 @@ "filename": "react-art.development.js", "bundleType": "NODE_DEV", "packageName": "react-art", - "size": 499479, - "gzip": 106084 + "size": 509836, + "gzip": 107536 }, { "filename": "react-art.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-art", - "size": 63450, - "gzip": 19455 + "size": 63242, + "gzip": 19335 }, { "filename": "ReactART-dev.js", @@ -305,15 +305,15 @@ "filename": "react-test-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 504043, - "gzip": 106587 + "size": 516895, + "gzip": 108547 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 64119, - "gzip": 19538 + "size": 63931, + "gzip": 19391 }, { "filename": "ReactTestRenderer-dev.js", @@ -340,15 +340,15 @@ "filename": "react-test-renderer-shallow.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 32275, - "gzip": 8333 + "size": 33992, + "gzip": 8604 }, { "filename": "react-test-renderer-shallow.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 12046, - "gzip": 3733 + "size": 11884, + "gzip": 3709 }, { "filename": "ReactShallowRenderer-dev.js", @@ -361,57 +361,57 @@ "filename": "react-noop-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 26949, - "gzip": 6362 + "size": 32746, + "gzip": 8125 }, { "filename": "react-noop-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 9933, - "gzip": 3165 + "size": 10367, + "gzip": 3370 }, { "filename": "react-reconciler.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 497879, - "gzip": 104645 + "size": 509952, + "gzip": 106359 }, { "filename": "react-reconciler.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 64551, - "gzip": 19325 + "size": 64472, + "gzip": 19170 }, { "filename": "react-reconciler-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 495898, - "gzip": 103848 + "size": 507789, + "gzip": 105473 }, { "filename": "react-reconciler-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 64562, - "gzip": 19330 + "size": 64483, + "gzip": 19176 }, { "filename": "react-reconciler-reflection.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", "size": 16161, - "gzip": 5096 + "gzip": 5015 }, { "filename": "react-reconciler-reflection.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 2760, - "gzip": 1244 + "size": 2423, + "gzip": 1082 }, { "filename": "react-call-return.development.js", @@ -704,22 +704,22 @@ "filename": "react-noop-renderer-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 27068, - "gzip": 6375 + "size": 32865, + "gzip": 8138 }, { "filename": "react-noop-renderer-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 9955, - "gzip": 3170 + "size": 10389, + "gzip": 3375 }, { "filename": "react-dom.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111211, - "gzip": 35133 + "size": 110831, + "gzip": 34942 }, { "filename": "ReactNativeRenderer-profiling.js", @@ -886,15 +886,15 @@ "filename": "jest-react.development.js", "bundleType": "NODE_DEV", "packageName": "jest-react", - "size": 7455, - "gzip": 2737 + "size": 7100, + "gzip": 2546 }, { "filename": "jest-react.production.min.js", "bundleType": "NODE_PROD", "packageName": "jest-react", - "size": 2930, - "gzip": 1442 + "size": 2599, + "gzip": 1299 }, { "filename": "JestReact-dev.js", @@ -928,15 +928,15 @@ "filename": "eslint-plugin-react-hooks.development.js", "bundleType": "NODE_DEV", "packageName": "eslint-plugin-react-hooks", - "size": 75099, - "gzip": 17223 + "size": 77541, + "gzip": 17683 }, { "filename": "eslint-plugin-react-hooks.production.min.js", "bundleType": "NODE_PROD", "packageName": "eslint-plugin-react-hooks", - "size": 19731, - "gzip": 6811 + "size": 20485, + "gzip": 7082 }, { "filename": "ReactDOMFizzServer-dev.js", @@ -1054,22 +1054,22 @@ "filename": "react-dom-unstable-fire.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 814014, - "gzip": 184503 + "size": 826115, + "gzip": 187004 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108049, - "gzip": 34524 + "size": 107670, + "gzip": 34307 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111225, - "gzip": 35142 + "size": 110845, + "gzip": 34952 }, { "filename": "ReactFire-dev.js", @@ -1180,29 +1180,99 @@ "filename": "react-events-press.development.js", "bundleType": "NODE_DEV", "packageName": "react-events", - "size": 9224, - "gzip": 2156 + "size": 9222, + "gzip": 2151 }, { "filename": "react-events-press.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-events", "size": 3819, - "gzip": 1305 + "gzip": 1301 }, { "filename": "ReactEventsPress-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-events", - "size": 9445, - "gzip": 2203 + "size": 9443, + "gzip": 2195 }, { "filename": "ReactEventsPress-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-events", "size": 7636, - "gzip": 1668 + "gzip": 1665 + }, + { + "filename": "react-events-hover.development.js", + "bundleType": "NODE_DEV", + "packageName": "react-events", + "size": 4527, + "gzip": 1245 + }, + { + "filename": "react-events-hover.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-events", + "size": 1950, + "gzip": 805 + }, + { + "filename": "ReactEventsHover-dev.js", + "bundleType": "FB_WWW_DEV", + "packageName": "react-events", + "size": 4496, + "gzip": 1263 + }, + { + "filename": "ReactEventsHover-prod.js", + "bundleType": "FB_WWW_PROD", + "packageName": "react-events", + "size": 3623, + "gzip": 1024 + }, + { + "filename": "react-events.development.js", + "bundleType": "UMD_DEV", + "packageName": "react-events", + "size": 1183, + "gzip": 605 + }, + { + "filename": "react-events.production.min.js", + "bundleType": "UMD_PROD", + "packageName": "react-events", + "size": 676, + "gzip": 420 + }, + { + "filename": "react-events-press.development.js", + "bundleType": "UMD_DEV", + "packageName": "react-events", + "size": 9396, + "gzip": 2197 + }, + { + "filename": "react-events-press.production.min.js", + "bundleType": "UMD_PROD", + "packageName": "react-events", + "size": 3990, + "gzip": 1358 + }, + { + "filename": "react-events-hover.development.js", + "bundleType": "UMD_DEV", + "packageName": "react-events", + "size": 4701, + "gzip": 1289 + }, + { + "filename": "react-events-hover.production.min.js", + "bundleType": "UMD_PROD", + "packageName": "react-events", + "size": 2123, + "gzip": 867 } ] } \ No newline at end of file From 620c16c9465491ca15caf89df5dc142fab2fa2db Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 13:17:08 -0700 Subject: [PATCH 3/8] Adds test --- .../react-dom/src/client/ReactDOMComponent.js | 2 +- .../src/events/DOMEventResponderSystem.js | 26 +++- packages/react-events/hover.js | 4 +- packages/react-events/press.js | 4 +- .../react-events/src/__tests__/Press-test.js | 129 ++++++++++++++++++ scripts/rollup/results.json | 16 +-- 6 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 packages/react-events/src/__tests__/Press-test.js diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index 2d671b6cd73..d3ba6ae8ee3 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -1322,7 +1322,7 @@ export function listenToEventResponderEventTypes( // Create a unique name for this event, plus its properties. We'll // use this to ensure we don't listen to the same event with the same // properties again. - const passiveKey = passive ? '_passive' : ''; + const passiveKey = passive ? '_passive' : '_active'; const captureKey = capture ? '_capture' : ''; const listeningName = `${topLevelType}${passiveKey}${captureKey}`; if (!listeningSet.has(listeningName)) { diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 87ca209d765..330e3144dbd 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -31,6 +31,10 @@ const rootEventTypesToEventComponents: Map< DOMTopLevelEventType | string, Set, > = new Map(); +const targetEventTypeCached: Map< + Array, + Set, +> = new Map(); type EventListener = (event: SyntheticEvent) => void; @@ -203,6 +207,24 @@ DOMEventResponderContext.prototype.releaseOwnership = function() { // TODO }; +function getTargetEventTypes( + eventTypes: Array, +): Set { + let cachedSet = targetEventTypeCached.get(eventTypes); + + if (cachedSet === undefined) { + cachedSet = new Set(); + for (let i = 0; i < eventTypes.length; i++) { + const eventType = eventTypes[i]; + const topLevelEventType = + typeof eventType === 'string' ? eventType : eventType.name; + cachedSet.add(topLevelEventType); + } + targetEventTypeCached.set(eventTypes, cachedSet); + } + return cachedSet; +} + function handleTopLevelType( topLevelType: DOMTopLevelEventType, fiber: Fiber, @@ -212,8 +234,8 @@ function handleTopLevelType( const responder: ReactEventResponder = fiber.type.responder; if (!isRootLevelEvent) { // Validate the target event type exists on the responder - const targetEventTypes = responder.targetEventTypes; - if (targetEventTypes.indexOf(topLevelType) === -1) { + const targetEventTypes = getTargetEventTypes(responder.targetEventTypes); + if (!targetEventTypes.has(topLevelType)) { return; } } diff --git a/packages/react-events/hover.js b/packages/react-events/hover.js index a6e602045f4..a53675ca5c1 100644 --- a/packages/react-events/hover.js +++ b/packages/react-events/hover.js @@ -9,6 +9,6 @@ 'use strict'; -import Hover from './src/Hover'; +const Hover = require('./src/Hover'); -export default Hover; +module.exports = Hover.default || Hover; diff --git a/packages/react-events/press.js b/packages/react-events/press.js index 8b5d2f28345..2add5ba8ed9 100644 --- a/packages/react-events/press.js +++ b/packages/react-events/press.js @@ -9,6 +9,6 @@ 'use strict'; -import Press from './src/Press'; +const Press = require('./src/Press'); -export default Press; +module.exports = Press.default || Press; diff --git a/packages/react-events/src/__tests__/Press-test.js b/packages/react-events/src/__tests__/Press-test.js new file mode 100644 index 00000000000..1dc7cf5a8dc --- /dev/null +++ b/packages/react-events/src/__tests__/Press-test.js @@ -0,0 +1,129 @@ +/** + * 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. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactFeatureFlags; +let ReactDOM; +let Press; + +describe('Press event responder', () => { + let container; + + beforeEach(() => { + jest.resetModules(); + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableEventAPI = true; + React = require('react'); + ReactDOM = require('react-dom'); + Press = require('react-events/press'); + + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should support onPress', () => { + let buttonRef = React.createRef(); + let events = []; + + function handleOnPress1() { + events.push('press 1'); + } + + function handleOnPress2() { + events.push('press 2'); + } + + function handleOnMouseDown() { + events.push('mousedown'); + } + + function handleKeyDown() { + events.push('keydown'); + } + + function Component() { + return ( + + + + + + ); + } + + ReactDOM.render(, container); + + const mouseDownEvent = document.createEvent('Event'); + mouseDownEvent.initEvent('mousedown', true, true); + buttonRef.current.dispatchEvent(mouseDownEvent); + + const mouseUpEvent = document.createEvent('Event'); + mouseUpEvent.initEvent('mouseup', true, true); + buttonRef.current.dispatchEvent(mouseUpEvent); + + expect(events).toEqual(['mousedown', 'press 2', 'press 1']); + + events = []; + const keyDownEvent = new KeyboardEvent('keydown', { + which: 13, + keyCode: 13, + bubbles: true, + cancelable: true, + }); + buttonRef.current.dispatchEvent(keyDownEvent); + + // press 1 should not occur as press 2 will preventDefault + expect(events).toEqual(['keydown', 'press 2']); + }); + + it('should support onPressIn and onPressOut', () => { + let divRef = React.createRef(); + let events = []; + + function handleOnPressIn() { + events.push('onPressIn'); + } + + function handleOnPressOut() { + events.push('onPressOut'); + } + + function Component() { + return ( + +
Press me!
+
+ ); + } + + ReactDOM.render(, container); + + const pointerEnterEvent = document.createEvent('Event'); + pointerEnterEvent.initEvent('pointerdown', true, true); + divRef.current.dispatchEvent(pointerEnterEvent); + + const pointerLeaveEvent = document.createEvent('Event'); + pointerLeaveEvent.initEvent('pointerup', true, true); + divRef.current.dispatchEvent(pointerLeaveEvent); + + expect(events).toEqual(['onPressIn', 'onPressOut']); + }); +}); diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 1a1a295e2e8..c494131beaa 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1250,29 +1250,29 @@ "filename": "react-events-press.development.js", "bundleType": "UMD_DEV", "packageName": "react-events", - "size": 9396, - "gzip": 2197 + "size": 9488, + "gzip": 2236 }, { "filename": "react-events-press.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-events", - "size": 3990, - "gzip": 1358 + "size": 4001, + "gzip": 1373 }, { "filename": "react-events-hover.development.js", "bundleType": "UMD_DEV", "packageName": "react-events", - "size": 4701, - "gzip": 1289 + "size": 4837, + "gzip": 1343 }, { "filename": "react-events-hover.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-events", - "size": 2123, - "gzip": 867 + "size": 2162, + "gzip": 891 } ] } \ No newline at end of file From 01cca624c0c1aa5b95459512c68a8c63402e4891 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 13:23:25 -0700 Subject: [PATCH 4/8] Refine Flow type annotation --- packages/react-dom/src/events/DOMEventResponderSystem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 330e3144dbd..d11347999ed 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -209,7 +209,7 @@ DOMEventResponderContext.prototype.releaseOwnership = function() { function getTargetEventTypes( eventTypes: Array, -): Set { +): Set { let cachedSet = targetEventTypeCached.get(eventTypes); if (cachedSet === undefined) { @@ -218,7 +218,7 @@ function getTargetEventTypes( const eventType = eventTypes[i]; const topLevelEventType = typeof eventType === 'string' ? eventType : eventType.name; - cachedSet.add(topLevelEventType); + cachedSet.add(((topLevelEventType: any): DOMTopLevelEventType)); } targetEventTypeCached.set(eventTypes, cachedSet); } From 1f426d2dd4295182e60f46b8d690f13aabceec38 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 13:39:30 -0700 Subject: [PATCH 5/8] More DCE handling --- .../src/events/DOMEventResponderSystem.js | 75 +++--- scripts/rollup/results.json | 244 ++++++------------ 2 files changed, 116 insertions(+), 203 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index d11347999ed..165edaa8ee8 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -27,6 +27,8 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import {listenToEventResponderEventTypes} from '../client/ReactDOMComponent'; import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; +import {enableEventAPI} from 'shared/ReactFeatureFlags'; + const rootEventTypesToEventComponents: Map< DOMTopLevelEventType | string, Set, @@ -255,40 +257,49 @@ export function runResponderEventsInBatch( nativeEventTarget: EventTarget, eventSystemFlags: EventSystemFlags, ): void { - const context = new DOMEventResponderContext( - topLevelType, - nativeEvent, - nativeEventTarget, - 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, context, false); + if (enableEventAPI) { + const context = new DOMEventResponderContext( + topLevelType, + nativeEvent, + nativeEventTarget, + 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, context, false); + } + node = node.return; } - node = node.return; - } - // Handle root level events - const rootEventComponents = rootEventTypesToEventComponents.get(topLevelType); - if (rootEventComponents !== undefined) { - const rootEventComponentFibers = Array.from(rootEventComponents); + // Handle root level events + const rootEventComponents = rootEventTypesToEventComponents.get( + topLevelType, + ); + if (rootEventComponents !== undefined) { + const rootEventComponentFibers = Array.from(rootEventComponents); - for (let i = 0; i < rootEventComponentFibers.length; i++) { - const rootEventComponentFiber = rootEventComponentFibers[i]; - handleTopLevelType(topLevelType, rootEventComponentFiber, context, true); + for (let i = 0; i < rootEventComponentFibers.length; i++) { + const rootEventComponentFiber = rootEventComponentFibers[i]; + handleTopLevelType( + topLevelType, + rootEventComponentFiber, + context, + true, + ); + } } + // Run batched events + const discreteEvents = context._discreteEvents; + if (discreteEvents !== null) { + interactiveUpdates(() => { + runEventsInBatch(discreteEvents); + }); + } + const nonDiscreteEvents = context._nonDiscreteEvents; + if (nonDiscreteEvents !== null) { + runEventsInBatch(nonDiscreteEvents); + } + context._isBatching = false; } - // Run batched events - const discreteEvents = context._discreteEvents; - if (discreteEvents !== null) { - interactiveUpdates(() => { - runEventsInBatch(discreteEvents); - }); - } - const nonDiscreteEvents = context._nonDiscreteEvents; - if (nonDiscreteEvents !== null) { - runEventsInBatch(nonDiscreteEvents); - } - context._isBatching = false; } diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index c494131beaa..346e655064d 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -18,15 +18,15 @@ "filename": "react.development.js", "bundleType": "NODE_DEV", "packageName": "react", - "size": 65436, - "gzip": 17596 + "size": 64139, + "gzip": 17318 }, { "filename": "react.production.min.js", "bundleType": "NODE_PROD", "packageName": "react", - "size": 6529, - "gzip": 2695 + "size": 6834, + "gzip": 2814 }, { "filename": "React-dev.js", @@ -60,15 +60,15 @@ "filename": "react-dom.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 825762, - "gzip": 186861 + "size": 813661, + "gzip": 184364 }, { "filename": "react-dom.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 107656, - "gzip": 34298 + "size": 108035, + "gzip": 34515 }, { "filename": "ReactDOM-dev.js", @@ -137,8 +137,8 @@ "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 61854, - "gzip": 16078 + "size": 61643, + "gzip": 16033 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", @@ -179,15 +179,15 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 132878, - "gzip": 35237 + "size": 132758, + "gzip": 35195 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 19287, - "gzip": 7290 + "size": 19756, + "gzip": 7540 }, { "filename": "ReactDOMServer-dev.js", @@ -207,15 +207,15 @@ "filename": "react-dom-server.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 134867, - "gzip": 35791 + "size": 134747, + "gzip": 35752 }, { "filename": "react-dom-server.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 20170, - "gzip": 7598 + "size": 20639, + "gzip": 7850 }, { "filename": "react-art.development.js", @@ -235,15 +235,15 @@ "filename": "react-art.development.js", "bundleType": "NODE_DEV", "packageName": "react-art", - "size": 509836, - "gzip": 107536 + "size": 499479, + "gzip": 106084 }, { "filename": "react-art.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-art", - "size": 63242, - "gzip": 19335 + "size": 63450, + "gzip": 19455 }, { "filename": "ReactART-dev.js", @@ -305,15 +305,15 @@ "filename": "react-test-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 516895, - "gzip": 108547 + "size": 504043, + "gzip": 106587 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 63931, - "gzip": 19391 + "size": 64119, + "gzip": 19538 }, { "filename": "ReactTestRenderer-dev.js", @@ -340,15 +340,15 @@ "filename": "react-test-renderer-shallow.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 33992, - "gzip": 8604 + "size": 32275, + "gzip": 8333 }, { "filename": "react-test-renderer-shallow.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 11884, - "gzip": 3709 + "size": 12046, + "gzip": 3733 }, { "filename": "ReactShallowRenderer-dev.js", @@ -361,57 +361,57 @@ "filename": "react-noop-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 32746, - "gzip": 8125 + "size": 26949, + "gzip": 6362 }, { "filename": "react-noop-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 10367, - "gzip": 3370 + "size": 9933, + "gzip": 3165 }, { "filename": "react-reconciler.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 509952, - "gzip": 106359 + "size": 497879, + "gzip": 104645 }, { "filename": "react-reconciler.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 64472, - "gzip": 19170 + "size": 64551, + "gzip": 19325 }, { "filename": "react-reconciler-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 507789, - "gzip": 105473 + "size": 495898, + "gzip": 103848 }, { "filename": "react-reconciler-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 64483, - "gzip": 19176 + "size": 64562, + "gzip": 19330 }, { "filename": "react-reconciler-reflection.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", "size": 16161, - "gzip": 5015 + "gzip": 5096 }, { "filename": "react-reconciler-reflection.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 2423, - "gzip": 1082 + "size": 2760, + "gzip": 1244 }, { "filename": "react-call-return.development.js", @@ -704,22 +704,22 @@ "filename": "react-noop-renderer-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 32865, - "gzip": 8138 + "size": 27068, + "gzip": 6375 }, { "filename": "react-noop-renderer-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 10389, - "gzip": 3375 + "size": 9955, + "gzip": 3170 }, { "filename": "react-dom.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 110831, - "gzip": 34942 + "size": 111211, + "gzip": 35133 }, { "filename": "ReactNativeRenderer-profiling.js", @@ -886,15 +886,15 @@ "filename": "jest-react.development.js", "bundleType": "NODE_DEV", "packageName": "jest-react", - "size": 7100, - "gzip": 2546 + "size": 7455, + "gzip": 2737 }, { "filename": "jest-react.production.min.js", "bundleType": "NODE_PROD", "packageName": "jest-react", - "size": 2599, - "gzip": 1299 + "size": 2930, + "gzip": 1442 }, { "filename": "JestReact-dev.js", @@ -928,15 +928,15 @@ "filename": "eslint-plugin-react-hooks.development.js", "bundleType": "NODE_DEV", "packageName": "eslint-plugin-react-hooks", - "size": 77541, - "gzip": 17683 + "size": 75099, + "gzip": 17223 }, { "filename": "eslint-plugin-react-hooks.production.min.js", "bundleType": "NODE_PROD", "packageName": "eslint-plugin-react-hooks", - "size": 20485, - "gzip": 7082 + "size": 19731, + "gzip": 6811 }, { "filename": "ReactDOMFizzServer-dev.js", @@ -1054,22 +1054,22 @@ "filename": "react-dom-unstable-fire.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 826115, - "gzip": 187004 + "size": 814014, + "gzip": 184503 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 107670, - "gzip": 34307 + "size": 108049, + "gzip": 34524 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 110845, - "gzip": 34952 + "size": 111225, + "gzip": 35142 }, { "filename": "ReactFire-dev.js", @@ -1152,127 +1152,29 @@ "filename": "react-events.development.js", "bundleType": "NODE_DEV", "packageName": "react-events", - "size": 990, - "gzip": 545 + "size": 1135, + "gzip": 623 }, { "filename": "react-events.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-events", - "size": 506, - "gzip": 343 + "size": 448, + "gzip": 328 }, { "filename": "ReactEvents-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-events", - "size": 956, - "gzip": 536 + "size": 1106, + "gzip": 613 }, { "filename": "ReactEvents-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-events", - "size": 687, - "gzip": 410 - }, - { - "filename": "react-events-press.development.js", - "bundleType": "NODE_DEV", - "packageName": "react-events", - "size": 9222, - "gzip": 2151 - }, - { - "filename": "react-events-press.production.min.js", - "bundleType": "NODE_PROD", - "packageName": "react-events", - "size": 3819, - "gzip": 1301 - }, - { - "filename": "ReactEventsPress-dev.js", - "bundleType": "FB_WWW_DEV", - "packageName": "react-events", - "size": 9443, - "gzip": 2195 - }, - { - "filename": "ReactEventsPress-prod.js", - "bundleType": "FB_WWW_PROD", - "packageName": "react-events", - "size": 7636, - "gzip": 1665 - }, - { - "filename": "react-events-hover.development.js", - "bundleType": "NODE_DEV", - "packageName": "react-events", - "size": 4527, - "gzip": 1245 - }, - { - "filename": "react-events-hover.production.min.js", - "bundleType": "NODE_PROD", - "packageName": "react-events", - "size": 1950, - "gzip": 805 - }, - { - "filename": "ReactEventsHover-dev.js", - "bundleType": "FB_WWW_DEV", - "packageName": "react-events", - "size": 4496, - "gzip": 1263 - }, - { - "filename": "ReactEventsHover-prod.js", - "bundleType": "FB_WWW_PROD", - "packageName": "react-events", - "size": 3623, - "gzip": 1024 - }, - { - "filename": "react-events.development.js", - "bundleType": "UMD_DEV", - "packageName": "react-events", - "size": 1183, - "gzip": 605 - }, - { - "filename": "react-events.production.min.js", - "bundleType": "UMD_PROD", - "packageName": "react-events", - "size": 676, - "gzip": 420 - }, - { - "filename": "react-events-press.development.js", - "bundleType": "UMD_DEV", - "packageName": "react-events", - "size": 9488, - "gzip": 2236 - }, - { - "filename": "react-events-press.production.min.js", - "bundleType": "UMD_PROD", - "packageName": "react-events", - "size": 4001, - "gzip": 1373 - }, - { - "filename": "react-events-hover.development.js", - "bundleType": "UMD_DEV", - "packageName": "react-events", - "size": 4837, - "gzip": 1343 - }, - { - "filename": "react-events-hover.production.min.js", - "bundleType": "UMD_PROD", - "packageName": "react-events", - "size": 2162, - "gzip": 891 + "size": 643, + "gzip": 377 } ] } \ No newline at end of file From 6cb61686515174e2aa0f1bdc2373c0951a41d0f8 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 13:40:29 -0700 Subject: [PATCH 6/8] Make new test internal --- .../src/__tests__/{Press-test.js => Press-test.internal.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react-events/src/__tests__/{Press-test.js => Press-test.internal.js} (100%) diff --git a/packages/react-events/src/__tests__/Press-test.js b/packages/react-events/src/__tests__/Press-test.internal.js similarity index 100% rename from packages/react-events/src/__tests__/Press-test.js rename to packages/react-events/src/__tests__/Press-test.internal.js From 8436f398bca27093f111e4627de470585d68e4e3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 14:49:15 -0700 Subject: [PATCH 7/8] Address PR feedback --- .../src/events/DOMEventResponderSystem.js | 254 +++++++++--------- packages/react-events/src/Press.js | 34 ++- 2 files changed, 150 insertions(+), 138 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 165edaa8ee8..88daf99061b 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -40,6 +40,12 @@ const targetEventTypeCached: Map< type EventListener = (event: SyntheticEvent) => void; +function copyEventProperties(eventData, syntheticEvent) { + for (let propName in eventData) { + syntheticEvent[propName] = eventData[propName]; + } +} + // TODO add context methods for dispatching events function DOMEventResponderContext( topLevelType: DOMTopLevelEventType, @@ -58,156 +64,152 @@ function DOMEventResponderContext( this._isBatching = true; } -DOMEventResponderContext.prototype.isPassive = function(): boolean { - return (this._flags & IS_PASSIVE) !== 0; -}; +if (enableEventAPI) { + DOMEventResponderContext.prototype.isPassive = function(): boolean { + return (this._flags & IS_PASSIVE) !== 0; + }; -DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean { - return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; -}; - -function copyEventProperties(eventData, syntheticEvent) { - for (let propName in eventData) { - syntheticEvent[propName] = eventData[propName]; - } -} + DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean { + return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; + }; -DOMEventResponderContext.prototype.dispatchEvent = function( - eventName: string, - eventListener: EventListener, - eventTarget: AnyNativeEvent, - discrete: boolean, - extraProperties?: Object, -): void { - const eventTargetFiber = getClosestInstanceFromNode(eventTarget); - const syntheticEvent = SyntheticEvent.getPooled( - null, - eventTargetFiber, - this.event, - eventTarget, - ); - if (extraProperties !== undefined) { - copyEventProperties(extraProperties, syntheticEvent); - } - syntheticEvent.type = eventName; - syntheticEvent._dispatchInstances = [eventTargetFiber]; - syntheticEvent._dispatchListeners = [eventListener]; + DOMEventResponderContext.prototype.dispatchEvent = function( + eventName: string, + eventListener: EventListener, + eventTarget: AnyNativeEvent, + discrete: boolean, + extraProperties?: Object, + ): void { + const eventTargetFiber = getClosestInstanceFromNode(eventTarget); + const syntheticEvent = SyntheticEvent.getPooled( + null, + eventTargetFiber, + this.event, + eventTarget, + ); + if (extraProperties !== undefined) { + copyEventProperties(extraProperties, syntheticEvent); + } + syntheticEvent.type = eventName; + syntheticEvent._dispatchInstances = [eventTargetFiber]; + syntheticEvent._dispatchListeners = [eventListener]; - if (this._isBatching) { - let events; - if (discrete) { - events = this._discreteEvents; - if (events === null) { - events = this._discreteEvents = []; + if (this._isBatching) { + let events; + if (discrete) { + events = this._discreteEvents; + if (events === null) { + events = this._discreteEvents = []; + } + } else { + events = this._nonDiscreteEvents; + if (events === null) { + events = this._nonDiscreteEvents = []; + } } + events.push(syntheticEvent); } else { - events = this._nonDiscreteEvents; - if (events === null) { - events = this._nonDiscreteEvents = []; - } - } - events.push(syntheticEvent); - } else { - if (discrete) { - interactiveUpdates(() => { + if (discrete) { + interactiveUpdates(() => { + executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + }); + } else { executeDispatch(syntheticEvent, eventListener, eventTargetFiber); - }); - } else { - executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + } } - } -}; + }; -DOMEventResponderContext.prototype.isTargetWithinEventComponent = function( - target: AnyNativeEvent, -): boolean { - const eventFiber = this._fiber; + 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; + if (target != null) { + let fiber = getClosestInstanceFromNode(target); + while (fiber !== null) { + if (fiber === eventFiber || fiber === eventFiber.alternate) { + return true; + } + fiber = fiber.return; } - fiber = fiber.return; } - } - return false; -}; + return false; + }; -DOMEventResponderContext.prototype.isTargetWithinElement = function( - childTarget: EventTarget, - parentTarget: EventTarget, -): boolean { - const childFiber = getClosestInstanceFromNode(childTarget); - const parentFiber = getClosestInstanceFromNode(parentTarget); + 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; + let currentFiber = childFiber; + while (currentFiber !== null) { + if (currentFiber === parentFiber) { + return true; + } + currentFiber = currentFiber.return; } - currentFiber = currentFiber.return; - } - return false; -}; + return false; + }; -DOMEventResponderContext.prototype.addRootEventTypes = function( - rootEventTypes: Array, -) { - const element = this.eventTarget.ownerDocument; - listenToEventResponderEventTypes(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( + DOMEventResponderContext.prototype.addRootEventTypes = function( + rootEventTypes: Array, + ) { + const element = this.eventTarget.ownerDocument; + listenToEventResponderEventTypes(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, - rootEventComponents, ); + if (rootEventComponents === undefined) { + rootEventComponents = new Set(); + rootEventTypesToEventComponents.set( + topLevelEventType, + rootEventComponents, + ); + } + rootEventComponents.add(eventComponent); } - 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.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.isPositionWithinTouchHitTarget = function() { + // TODO + }; -DOMEventResponderContext.prototype.isTargetOwned = function() { - // TODO -}; + DOMEventResponderContext.prototype.isTargetOwned = function() { + // TODO + }; -DOMEventResponderContext.prototype.requestOwnership = function() { - // TODO -}; + DOMEventResponderContext.prototype.requestOwnership = function() { + // TODO + }; -DOMEventResponderContext.prototype.releaseOwnership = function() { - // TODO -}; + DOMEventResponderContext.prototype.releaseOwnership = function() { + // TODO + }; +} function getTargetEventTypes( eventTypes: Array, diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index 7cbec2441e9..f4b9bd8e4d8 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -22,12 +22,7 @@ const rootEventTypes = ['pointerup', 'scroll']; // In the case we don't have PointerEvents (Safari), we listen to touch events // too if (typeof window !== 'undefined' && window.PointerEvent === undefined) { - targetEventTypes.push( - 'touchstart', - {name: 'touchend', passive: false}, - 'mousedown', - 'touchcancel', - ); + targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel'); rootEventTypes.push('mouseup'); } @@ -38,6 +33,7 @@ type PressState = { isPressed: boolean, longPressTimeout: null | TimeoutID, pressTarget: null | EventTarget, + shouldSkipMouseAfterTouch: boolean, }; function dispatchPressEvent( @@ -143,6 +139,7 @@ const PressResponder = { isPressed: false, longPressTimeout: null, pressTarget: null, + shouldSkipMouseAfterTouch: false, }; }, handleEvent( @@ -168,7 +165,11 @@ const PressResponder = { let keyPressEventListener = props.onPress; // Wrap listener with prevent default behaviour, unless - // we are dealing with an anchor + // we are dealing with an anchor. Anchor tags are special beacuse + // we need to use the "click" event, to properly allow browser + // heuristics for cancelling link clicks. Furthermore, iOS and + // Android can show previous of anchor tags that requires working + // with click rather than touch events (and mouse down/up). if (!isAnchorTagElement(eventTarget)) { keyPressEventListener = (e, key) => { if (!e.isDefaultPrevented() && !e.nativeEvent.defaultPrevented) { @@ -184,7 +185,8 @@ const PressResponder = { case 'touchstart': // Touch events are for Safari, which lack pointer event support. if (!state.isPressed && !context.isTargetOwned(eventTarget)) { - // We bail out of polyfilling anchor tags + // We bail out of polyfilling anchor tags, given the same heuristics + // explained above in regards to needing to use click events. if (isAnchorTagElement(eventTarget)) { state.isAnchorTouched = true; return; @@ -232,15 +234,18 @@ const PressResponder = { } state.isPressed = false; state.isLongPressed = false; - // Prevent mouse events from firing - (event: any).preventDefault(); + state.shouldSkipMouseAfterTouch = true; context.removeRootEventTypes(rootEventTypes); } break; } case 'pointerdown': case 'mousedown': { - if (!state.isPressed && !context.isTargetOwned(eventTarget)) { + if ( + !state.isPressed && + !context.isTargetOwned(eventTarget) && + !state.shouldSkipMouseAfterTouch + ) { if ((event: any).pointerType === 'mouse') { // Ignore if we are pressing on hit slop area with mouse if ( @@ -252,7 +257,7 @@ const PressResponder = { return; } // Ignore right-clicks - if (event.button === 2) { + if (event.button === 2 || event.button === 1) { return; } } @@ -266,6 +271,10 @@ const PressResponder = { case 'mouseup': case 'pointerup': { if (state.isPressed) { + if (state.shouldSkipMouseAfterTouch) { + state.shouldSkipMouseAfterTouch = false; + return; + } dispatchPressOutEvents(context, props, state); if ( state.pressTarget !== null && @@ -308,6 +317,7 @@ const PressResponder = { case 'contextmenu': case 'pointercancel': { if (state.isPressed) { + state.shouldSkipMouseAfterTouch = false; dispatchPressOutEvents(context, props, state); state.isPressed = false; state.isLongPressed = false; From cf5b52311f7a525526a9d31477331f265431bc5d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 27 Mar 2019 15:58:13 -0700 Subject: [PATCH 8/8] Remove unnecessary conditional --- .../src/events/DOMEventResponderSystem.js | 242 +++++++++--------- scripts/rollup/results.json | 136 +++++----- 2 files changed, 188 insertions(+), 190 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 88daf99061b..058d633fa03 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -64,152 +64,150 @@ function DOMEventResponderContext( this._isBatching = true; } -if (enableEventAPI) { - DOMEventResponderContext.prototype.isPassive = function(): boolean { - return (this._flags & IS_PASSIVE) !== 0; - }; +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.isPassiveSupported = function(): boolean { + return (this._flags & PASSIVE_NOT_SUPPORTED) === 0; +}; - DOMEventResponderContext.prototype.dispatchEvent = function( - eventName: string, - eventListener: EventListener, - eventTarget: AnyNativeEvent, - discrete: boolean, - extraProperties?: Object, - ): void { - const eventTargetFiber = getClosestInstanceFromNode(eventTarget); - const syntheticEvent = SyntheticEvent.getPooled( - null, - eventTargetFiber, - this.event, - eventTarget, - ); - if (extraProperties !== undefined) { - copyEventProperties(extraProperties, syntheticEvent); - } - syntheticEvent.type = eventName; - syntheticEvent._dispatchInstances = [eventTargetFiber]; - syntheticEvent._dispatchListeners = [eventListener]; +DOMEventResponderContext.prototype.dispatchEvent = function( + eventName: string, + eventListener: EventListener, + eventTarget: AnyNativeEvent, + discrete: boolean, + extraProperties?: Object, +): void { + const eventTargetFiber = getClosestInstanceFromNode(eventTarget); + const syntheticEvent = SyntheticEvent.getPooled( + null, + eventTargetFiber, + this.event, + eventTarget, + ); + if (extraProperties !== undefined) { + copyEventProperties(extraProperties, syntheticEvent); + } + syntheticEvent.type = eventName; + syntheticEvent._dispatchInstances = [eventTargetFiber]; + syntheticEvent._dispatchListeners = [eventListener]; - if (this._isBatching) { - let events; - if (discrete) { - events = this._discreteEvents; - if (events === null) { - events = this._discreteEvents = []; - } - } else { - events = this._nonDiscreteEvents; - if (events === null) { - events = this._nonDiscreteEvents = []; - } + if (this._isBatching) { + let events; + if (discrete) { + events = this._discreteEvents; + if (events === null) { + events = this._discreteEvents = []; } - events.push(syntheticEvent); } else { - if (discrete) { - interactiveUpdates(() => { - executeDispatch(syntheticEvent, eventListener, eventTargetFiber); - }); - } else { - executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + events = this._nonDiscreteEvents; + if (events === null) { + events = this._nonDiscreteEvents = []; } } - }; + events.push(syntheticEvent); + } else { + if (discrete) { + interactiveUpdates(() => { + executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + }); + } else { + executeDispatch(syntheticEvent, eventListener, eventTargetFiber); + } + } +}; - DOMEventResponderContext.prototype.isTargetWithinEventComponent = function( - target: AnyNativeEvent, - ): boolean { - const eventFiber = this._fiber; +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; + if (target != null) { + let fiber = getClosestInstanceFromNode(target); + while (fiber !== null) { + if (fiber === eventFiber || fiber === eventFiber.alternate) { + return true; } + fiber = fiber.return; } - return false; - }; + } + return false; +}; - DOMEventResponderContext.prototype.isTargetWithinElement = function( - childTarget: EventTarget, - parentTarget: EventTarget, - ): boolean { - const childFiber = getClosestInstanceFromNode(childTarget); - const parentFiber = getClosestInstanceFromNode(parentTarget); +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; + let currentFiber = childFiber; + while (currentFiber !== null) { + if (currentFiber === parentFiber) { + return true; } - return false; - }; + currentFiber = currentFiber.return; + } + return false; +}; - DOMEventResponderContext.prototype.addRootEventTypes = function( - rootEventTypes: Array, - ) { - const element = this.eventTarget.ownerDocument; - listenToEventResponderEventTypes(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( +DOMEventResponderContext.prototype.addRootEventTypes = function( + rootEventTypes: Array, +) { + const element = this.eventTarget.ownerDocument; + listenToEventResponderEventTypes(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, ); - if (rootEventComponents === undefined) { - rootEventComponents = new Set(); - rootEventTypesToEventComponents.set( - topLevelEventType, - rootEventComponents, - ); - } - rootEventComponents.add(eventComponent); } - }; + 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.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.isPositionWithinTouchHitTarget = function() { + // TODO +}; - DOMEventResponderContext.prototype.isTargetOwned = function() { - // TODO - }; +DOMEventResponderContext.prototype.isTargetOwned = function() { + // TODO +}; - DOMEventResponderContext.prototype.requestOwnership = function() { - // TODO - }; +DOMEventResponderContext.prototype.requestOwnership = function() { + // TODO +}; - DOMEventResponderContext.prototype.releaseOwnership = function() { - // TODO - }; -} +DOMEventResponderContext.prototype.releaseOwnership = function() { + // TODO +}; function getTargetEventTypes( eventTypes: Array, diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 346e655064d..ddb87f522b2 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -46,29 +46,29 @@ "filename": "react-dom.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 803793, - "gzip": 183116 + "size": 832345, + "gzip": 188603 }, { "filename": "react-dom.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 107752, - "gzip": 34911 + "size": 107683, + "gzip": 34867 }, { "filename": "react-dom.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 813661, - "gzip": 184364 + "size": 826372, + "gzip": 186963 }, { "filename": "react-dom.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108035, - "gzip": 34515 + "size": 107664, + "gzip": 34301 }, { "filename": "ReactDOM-dev.js", @@ -88,15 +88,15 @@ "filename": "react-dom-test-utils.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 48274, - "gzip": 13318 + "size": 48620, + "gzip": 13278 }, { "filename": "react-dom-test-utils.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 10511, - "gzip": 3880 + "size": 10184, + "gzip": 3732 }, { "filename": "react-dom-test-utils.development.js", @@ -123,22 +123,22 @@ "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 62061, - "gzip": 16285 + "size": 62190, + "gzip": 16206 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 11266, - "gzip": 3889 + "size": 10936, + "gzip": 3741 }, { "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 61643, - "gzip": 16033 + "size": 61854, + "gzip": 16078 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", @@ -165,29 +165,29 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 133264, - "gzip": 35517 + "size": 136840, + "gzip": 36205 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 19666, - "gzip": 7443 + "size": 19363, + "gzip": 7290 }, { "filename": "react-dom-server.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 132758, - "gzip": 35195 + "size": 132878, + "gzip": 35237 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 19756, - "gzip": 7540 + "size": 19287, + "gzip": 7290 }, { "filename": "ReactDOMServer-dev.js", @@ -207,15 +207,15 @@ "filename": "react-dom-server.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 134747, - "gzip": 35752 + "size": 134867, + "gzip": 35791 }, { "filename": "react-dom-server.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 20639, - "gzip": 7850 + "size": 20170, + "gzip": 7598 }, { "filename": "react-art.development.js", @@ -515,50 +515,50 @@ "filename": "ReactDOM-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 821918, - "gzip": 182691 + "size": 851672, + "gzip": 188651 }, { "filename": "ReactDOM-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 333267, - "gzip": 61003 + "size": 339041, + "gzip": 62418 }, { "filename": "ReactTestUtils-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 44870, - "gzip": 12188 + "size": 46251, + "gzip": 12476 }, { "filename": "ReactDOMUnstableNativeDependencies-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 59002, - "gzip": 14967 + "size": 60296, + "gzip": 15251 }, { "filename": "ReactDOMUnstableNativeDependencies-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 26900, - "gzip": 5426 + "size": 26767, + "gzip": 5381 }, { "filename": "ReactDOMServer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 130310, - "gzip": 33947 + "size": 135272, + "gzip": 35002 }, { "filename": "ReactDOMServer-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 46994, - "gzip": 10956 + "size": 46877, + "gzip": 10879 }, { "filename": "ReactART-dev.js", @@ -718,8 +718,8 @@ "filename": "react-dom.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111211, - "gzip": 35133 + "size": 110839, + "gzip": 34944 }, { "filename": "ReactNativeRenderer-profiling.js", @@ -767,8 +767,8 @@ "filename": "ReactDOM-profiling.js", "bundleType": "FB_WWW_PROFILING", "packageName": "react-dom", - "size": 339339, - "gzip": 62379 + "size": 345581, + "gzip": 63810 }, { "filename": "ReactNativeRenderer-profiling.js", @@ -795,8 +795,8 @@ "filename": "react-dom.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react-dom", - "size": 110829, - "gzip": 35621 + "size": 110730, + "gzip": 35485 }, { "filename": "scheduler-tracing.development.js", @@ -1033,64 +1033,64 @@ "filename": "react-dom-unstable-fire.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 804147, - "gzip": 183253 + "size": 832471, + "gzip": 188731 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 107767, - "gzip": 34920 + "size": 107698, + "gzip": 34877 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react-dom", - "size": 110844, - "gzip": 35630 + "size": 110745, + "gzip": 35493 }, { "filename": "react-dom-unstable-fire.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 814014, - "gzip": 184503 + "size": 826725, + "gzip": 187104 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108049, - "gzip": 34524 + "size": 107678, + "gzip": 34310 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111225, - "gzip": 35142 + "size": 110853, + "gzip": 34953 }, { "filename": "ReactFire-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 821109, - "gzip": 182607 + "size": 850863, + "gzip": 188551 }, { "filename": "ReactFire-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 321531, - "gzip": 58583 + "size": 327881, + "gzip": 60185 }, { "filename": "ReactFire-profiling.js", "bundleType": "FB_WWW_PROFILING", "packageName": "react-dom", - "size": 327694, - "gzip": 59916 + "size": 334366, + "gzip": 61586 }, { "filename": "jest-mock-scheduler.development.js",