@samitouri / QOS-React / commits / 669cafb36f

Adds experimental event component responder surfaces (#15228)

* Adds Press and Hover event modules + more features to the Event Responder System

Dominic Gannaway committed Mar 27, 2019 at 16:42 UTC 669cafb36f7252e2f83655f48f48a85b0f4e2051
15 files changed +1040 -161
packages/events/EventPluginUtils.js
+1 -1
@@ -63,7 +63,7 @@ if (__DEV__) {
63 * @param {function} listener Application-level callback
64 * @param {*} inst Internal component instance
65 */
66 -function executeDispatch(event, listener, inst) {
66 +export function executeDispatch(event, listener, inst) {
67 const type = event.type || 'unknown-event';
68 event.currentTarget = getNodeFromInstance(inst);
69 invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
packages/events/EventTypes.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import SyntheticEvent from 'events/SyntheticEvent';
11 +import type {AnyNativeEvent} from 'events/PluginModuleType';
12 +
13 +export type EventResponderContext = {
14 + event: AnyNativeEvent,
15 + eventTarget: EventTarget,
16 + eventType: string,
17 + isPassive: () => boolean,
18 + isPassiveSupported: () => boolean,
19 + dispatchEvent: (
20 + name: string,
21 + listener: (e: SyntheticEvent) => void | null,
22 + pressTarget: EventTarget | null,
23 + discrete: boolean,
24 + extraProperties?: Object,
25 + ) => void,
26 + isTargetWithinElement: (
27 + childTarget: EventTarget,
28 + parentTarget: EventTarget,
29 + ) => boolean,
30 + isTargetOwned: EventTarget => boolean,
31 + isTargetWithinEventComponent: EventTarget => boolean,
32 + isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
33 + addRootEventTypes: (rootEventTypes: Array<string>) => void,
34 + removeRootEventTypes: (rootEventTypes: Array<string>) => void,
35 + requestOwnership: (target: EventTarget | null) => boolean,
36 + releaseOwnership: (target: EventTarget | null) => boolean,
37 +};
packages/react-dom/src/client/ReactDOMComponent.js
+6 -7
@@ -13,7 +13,7 @@ import {registrationNameModules} from 'events/EventPluginRegistry';
13 import warning from 'shared/warning';
14 import {canUseDOM} from 'shared/ExecutionEnvironment';
15 import warningWithoutStack from 'shared/warningWithoutStack';
16 -import type {ReactEventResponder} from 'shared/ReactTypes';
16 +import type {ReactEventResponderEventType} from 'shared/ReactTypes';
17 import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
18
19 import {
@@ -1277,19 +1277,18 @@ export function restoreControlledState(
1277 }
1278 }
1279
1280 -export function listenToEventResponderEvents(
1281 - eventResponder: ReactEventResponder,
1280 +export function listenToEventResponderEventTypes(
1281 + eventTypes: Array<ReactEventResponderEventType>,
1282 element: Element | Document,
1283 ): void {
1284 if (enableEventAPI) {
1285 - const {targetEventTypes} = eventResponder;
1285 // Get the listening Set for this element. We use this to track
1286 // what events we're listening to.
1287 const listeningSet = getListeningSetForElement(element);
1288
1289 // Go through each target event type of the event responder
1291 - for (let i = 0, length = targetEventTypes.length; i < length; ++i) {
1292 - const targetEventType = targetEventTypes[i];
1290 + for (let i = 0, length = eventTypes.length; i < length; ++i) {
1291 + const targetEventType = eventTypes[i];
1292 let topLevelType;
1293 let capture = false;
1294 let passive = true;
@@ -1323,7 +1322,7 @@ export function listenToEventResponderEvents(
1322 // Create a unique name for this event, plus its properties. We'll
1323 // use this to ensure we don't listen to the same event with the same
1324 // properties again.
1326 - const passiveKey = passive ? '_passive' : '';
1325 + const passiveKey = passive ? '_passive' : '_active';
1326 const captureKey = capture ? '_capture' : '';
1327 const listeningName = `${topLevelType}${passiveKey}${captureKey}`;
1328 if (!listeningSet.has(listeningName)) {
packages/react-dom/src/client/ReactDOMHostConfig.js
+5 -2
@@ -24,7 +24,7 @@ import {
24 warnForDeletedHydratableText,
25 warnForInsertedHydratedElement,
26 warnForInsertedHydratedText,
27 - listenToEventResponderEvents,
27 + listenToEventResponderEventTypes,
28 } from './ReactDOMComponent';
29 import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
30 import setTextContent from './setTextContent';
@@ -864,7 +864,10 @@ export function handleEventComponent(
864 ): void {
865 if (enableEventAPI) {
866 const rootElement = rootContainerInstance.ownerDocument;
867 - listenToEventResponderEvents(eventResponder, rootElement);
867 + listenToEventResponderEventTypes(
868 + eventResponder.targetEventTypes,
869 + rootElement,
870 + );
871 }
872 }
873
packages/react-dom/src/events/DOMEventResponderSystem.js
+192 -82
@@ -13,29 +13,39 @@ import {
13 } from 'events/EventSystemFlags';
14 import type {AnyNativeEvent} from 'events/PluginModuleType';
15 import {EventComponent} from 'shared/ReactWorkTags';
16 -import type {ReactEventResponder} from 'shared/ReactTypes';
17 -import warning from 'shared/warning';
16 +import type {
17 + ReactEventResponder,
18 + ReactEventResponderEventType,
19 +} from 'shared/ReactTypes';
20 import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
21 import SyntheticEvent from 'events/SyntheticEvent';
22 import {runEventsInBatch} from 'events/EventBatching';
23 import {interactiveUpdates} from 'events/ReactGenericBatching';
24 +import {executeDispatch} from 'events/EventPluginUtils';
25 import type {Fiber} from 'react-reconciler/src/ReactFiber';
26
27 +import {listenToEventResponderEventTypes} from '../client/ReactDOMComponent';
28 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
29
26 -// Event responders provide us an array of target event types.
27 -// To ensure we fire the right responders for given events, we check
28 -// if the incoming event type is actually relevant for an event
29 -// responder. Instead of doing an O(n) lookup on the event responder
30 -// target event types array each time, we instead create a Set for
31 -// faster O(1) lookups.
32 -export const eventResponderValidEventTypes: Map<
33 - ReactEventResponder,
30 +import {enableEventAPI} from 'shared/ReactFeatureFlags';
31 +
32 +const rootEventTypesToEventComponents: Map<
33 + DOMTopLevelEventType | string,
34 + Set<Fiber>,
35 +> = new Map();
36 +const targetEventTypeCached: Map<
37 + Array<ReactEventResponderEventType>,
38 Set<DOMTopLevelEventType>,
39 > = new Map();
40
41 type EventListener = (event: SyntheticEvent) => void;
42
43 +function copyEventProperties(eventData, syntheticEvent) {
44 + for (let propName in eventData) {
45 + syntheticEvent[propName] = eventData[propName];
46 + }
47 +}
48 +
49 // TODO add context methods for dispatching events
50 function DOMEventResponderContext(
51 topLevelType: DOMTopLevelEventType,
@@ -44,13 +54,14 @@ function DOMEventResponderContext(
54 eventSystemFlags: EventSystemFlags,
55 ) {
56 this.event = nativeEvent;
47 - this.eventType = topLevelType;
57 this.eventTarget = nativeEventTarget;
58 + this.eventType = topLevelType;
59 this._flags = eventSystemFlags;
60 this._fiber = null;
61 this._responder = null;
62 this._discreteEvents = null;
63 this._nonDiscreteEvents = null;
64 + this._isBatching = true;
65 }
66
67 DOMEventResponderContext.prototype.isPassive = function(): boolean {
@@ -61,12 +72,6 @@ DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
72 return (this._flags & PASSIVE_NOT_SUPPORTED) === 0;
73 };
74
64 -function copyEventProperties(eventData, syntheticEvent) {
65 - for (let propName in eventData) {
66 - syntheticEvent[propName] = eventData[propName];
67 - }
68 -}
69 -
75 DOMEventResponderContext.prototype.dispatchEvent = function(
76 eventName: string,
77 eventListener: EventListener,
@@ -88,80 +93,155 @@ DOMEventResponderContext.prototype.dispatchEvent = function(
93 syntheticEvent._dispatchInstances = [eventTargetFiber];
94 syntheticEvent._dispatchListeners = [eventListener];
95
91 - let events;
92 - if (discrete) {
93 - events = this._discreteEvents;
94 - if (events === null) {
95 - events = this._discreteEvents = [];
96 + if (this._isBatching) {
97 + let events;
98 + if (discrete) {
99 + events = this._discreteEvents;
100 + if (events === null) {
101 + events = this._discreteEvents = [];
102 + }
103 + } else {
104 + events = this._nonDiscreteEvents;
105 + if (events === null) {
106 + events = this._nonDiscreteEvents = [];
107 + }
108 }
109 + events.push(syntheticEvent);
110 } else {
98 - events = this._nonDiscreteEvents;
99 - if (events === null) {
100 - events = this._nonDiscreteEvents = [];
111 + if (discrete) {
112 + interactiveUpdates(() => {
113 + executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
114 + });
115 + } else {
116 + executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
117 }
118 }
103 - events.push(syntheticEvent);
119 };
120
106 -DOMEventResponderContext.prototype._runEventsInBatch = function(): void {
107 - if (this._discreteEvents !== null) {
108 - interactiveUpdates(() => {
109 - runEventsInBatch(this._discreteEvents);
110 - });
121 +DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
122 + target: AnyNativeEvent,
123 +): boolean {
124 + const eventFiber = this._fiber;
125 +
126 + if (target != null) {
127 + let fiber = getClosestInstanceFromNode(target);
128 + while (fiber !== null) {
129 + if (fiber === eventFiber || fiber === eventFiber.alternate) {
130 + return true;
131 + }
132 + fiber = fiber.return;
133 + }
134 }
112 - if (this._nonDiscreteEvents !== null) {
113 - runEventsInBatch(this._nonDiscreteEvents);
135 + return false;
136 +};
137 +
138 +DOMEventResponderContext.prototype.isTargetWithinElement = function(
139 + childTarget: EventTarget,
140 + parentTarget: EventTarget,
141 +): boolean {
142 + const childFiber = getClosestInstanceFromNode(childTarget);
143 + const parentFiber = getClosestInstanceFromNode(parentTarget);
144 +
145 + let currentFiber = childFiber;
146 + while (currentFiber !== null) {
147 + if (currentFiber === parentFiber) {
148 + return true;
149 + }
150 + currentFiber = currentFiber.return;
151 }
152 + return false;
153 };
154
117 -function createValidEventTypeSet(targetEventTypes): Set<DOMTopLevelEventType> {
118 - const eventTypeSet = new Set();
119 - // Go through each target event type of the event responder
120 - for (let i = 0, length = targetEventTypes.length; i < length; ++i) {
121 - const targetEventType = targetEventTypes[i];
155 +DOMEventResponderContext.prototype.addRootEventTypes = function(
156 + rootEventTypes: Array<ReactEventResponderEventType>,
157 +) {
158 + const element = this.eventTarget.ownerDocument;
159 + listenToEventResponderEventTypes(rootEventTypes, element);
160 + const eventComponent = this._fiber;
161 + for (let i = 0; i < rootEventTypes.length; i++) {
162 + const rootEventType = rootEventTypes[i];
163 + const topLevelEventType =
164 + typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
165 + let rootEventComponents = rootEventTypesToEventComponents.get(
166 + topLevelEventType,
167 + );
168 + if (rootEventComponents === undefined) {
169 + rootEventComponents = new Set();
170 + rootEventTypesToEventComponents.set(
171 + topLevelEventType,
172 + rootEventComponents,
173 + );
174 + }
175 + rootEventComponents.add(eventComponent);
176 + }
177 +};
178
123 - if (typeof targetEventType === 'string') {
124 - eventTypeSet.add(((targetEventType: any): DOMTopLevelEventType));
125 - } else {
126 - if (__DEV__) {
127 - warning(
128 - typeof targetEventType === 'object' && targetEventType !== null,
129 - 'Event Responder: invalid entry in targetEventTypes array. ' +
130 - 'Entry must be string or an object. Instead, got %s.',
131 - targetEventType,
132 - );
133 - }
134 - const targetEventConfigObject = ((targetEventType: any): {
135 - name: DOMTopLevelEventType,
136 - passive?: boolean,
137 - capture?: boolean,
138 - });
139 - eventTypeSet.add(targetEventConfigObject.name);
179 +DOMEventResponderContext.prototype.removeRootEventTypes = function(
180 + rootEventTypes: Array<ReactEventResponderEventType>,
181 +): void {
182 + const eventComponent = this._fiber;
183 + for (let i = 0; i < rootEventTypes.length; i++) {
184 + const rootEventType = rootEventTypes[i];
185 + const topLevelEventType =
186 + typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
187 + let rootEventComponents = rootEventTypesToEventComponents.get(
188 + topLevelEventType,
189 + );
190 + if (rootEventComponents !== undefined) {
191 + rootEventComponents.delete(eventComponent);
192 + }
193 + }
194 +};
195 +
196 +DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
197 + // TODO
198 +};
199 +
200 +DOMEventResponderContext.prototype.isTargetOwned = function() {
201 + // TODO
202 +};
203 +
204 +DOMEventResponderContext.prototype.requestOwnership = function() {
205 + // TODO
206 +};
207 +
208 +DOMEventResponderContext.prototype.releaseOwnership = function() {
209 + // TODO
210 +};
211 +
212 +function getTargetEventTypes(
213 + eventTypes: Array<ReactEventResponderEventType>,
214 +): Set<DOMTopLevelEventType> {
215 + let cachedSet = targetEventTypeCached.get(eventTypes);
216 +
217 + if (cachedSet === undefined) {
218 + cachedSet = new Set();
219 + for (let i = 0; i < eventTypes.length; i++) {
220 + const eventType = eventTypes[i];
221 + const topLevelEventType =
222 + typeof eventType === 'string' ? eventType : eventType.name;
223 + cachedSet.add(((topLevelEventType: any): DOMTopLevelEventType));
224 }
225 + targetEventTypeCached.set(eventTypes, cachedSet);
226 }
142 - return eventTypeSet;
227 + return cachedSet;
228 }
229
230 function handleTopLevelType(
231 topLevelType: DOMTopLevelEventType,
232 fiber: Fiber,
233 context: Object,
234 + isRootLevelEvent: boolean,
235 ): void {
236 const responder: ReactEventResponder = fiber.type.responder;
151 - let {props, state} = fiber.stateNode;
152 - let validEventTypesForResponder = eventResponderValidEventTypes.get(
153 - responder,
154 - );
155 -
156 - if (validEventTypesForResponder === undefined) {
157 - validEventTypesForResponder = createValidEventTypeSet(
158 - responder.targetEventTypes,
159 - );
160 - eventResponderValidEventTypes.set(responder, validEventTypesForResponder);
161 - }
162 - if (!validEventTypesForResponder.has(topLevelType)) {
163 - return;
237 + if (!isRootLevelEvent) {
238 + // Validate the target event type exists on the responder
239 + const targetEventTypes = getTargetEventTypes(responder.targetEventTypes);
240 + if (!targetEventTypes.has(topLevelType)) {
241 + return;
242 + }
243 }
244 + let {props, state} = fiber.stateNode;
245 if (state === null && responder.createInitialState !== undefined) {
246 state = fiber.stateNode.state = responder.createInitialState(props);
247 }
@@ -177,19 +257,49 @@ export function runResponderEventsInBatch(
257 nativeEventTarget: EventTarget,
258 eventSystemFlags: EventSystemFlags,
259 ): void {
180 - const context = new DOMEventResponderContext(
181 - topLevelType,
182 - nativeEvent,
183 - nativeEventTarget,
184 - eventSystemFlags,
185 - );
186 - let node = targetFiber;
187 - // Traverse up the fiber tree till we find event component fibers.
188 - while (node !== null) {
189 - if (node.tag === EventComponent) {
190 - handleTopLevelType(topLevelType, node, context);
260 + if (enableEventAPI) {
261 + const context = new DOMEventResponderContext(
262 + topLevelType,
263 + nativeEvent,
264 + nativeEventTarget,
265 + eventSystemFlags,
266 + );
267 + let node = targetFiber;
268 + // Traverse up the fiber tree till we find event component fibers.
269 + while (node !== null) {
270 + if (node.tag === EventComponent) {
271 + handleTopLevelType(topLevelType, node, context, false);
272 + }
273 + node = node.return;
274 + }
275 + // Handle root level events
276 + const rootEventComponents = rootEventTypesToEventComponents.get(
277 + topLevelType,
278 + );
279 + if (rootEventComponents !== undefined) {
280 + const rootEventComponentFibers = Array.from(rootEventComponents);
281 +
282 + for (let i = 0; i < rootEventComponentFibers.length; i++) {
283 + const rootEventComponentFiber = rootEventComponentFibers[i];
284 + handleTopLevelType(
285 + topLevelType,
286 + rootEventComponentFiber,
287 + context,
288 + true,
289 + );
290 + }
291 + }
292 + // Run batched events
293 + const discreteEvents = context._discreteEvents;
294 + if (discreteEvents !== null) {
295 + interactiveUpdates(() => {
296 + runEventsInBatch(discreteEvents);
297 + });
298 + }
299 + const nonDiscreteEvents = context._nonDiscreteEvents;
300 + if (nonDiscreteEvents !== null) {
301 + runEventsInBatch(nonDiscreteEvents);
302 }
192 - node = node.return;
303 + context._isBatching = false;
304 }
194 - context._runEventsInBatch();
305 }
packages/react-events/hover.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const Hover = require('./src/Hover');
13 +
14 +module.exports = Hover.default || Hover;
packages/react-events/npm/hover.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-events-hover.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-events-hover.development.js');
7 +}
packages/react-events/npm/press.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-events-press.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-events-press.development.js');
7 +}
packages/react-events/package.json
+1
@@ -11,6 +11,7 @@
11 "files": [
12 "LICENSE",
13 "README.md",
14 + "press.js",
15 "build-info.json",
16 "cjs/",
17 "umd/"
packages/react-events/press.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const Press = require('./src/Press');
13 +
14 +module.exports = Press.default || Press;
packages/react-events/src/Hover.js new
+179
@@ -0,0 +1,179 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {EventResponderContext} from 'events/EventTypes';
11 +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const targetEventTypes = [
14 + 'pointerover',
15 + 'pointermove',
16 + 'pointerout',
17 + 'pointercancel',
18 +];
19 +
20 +type HoverState = {
21 + isHovered: boolean,
22 + isInHitSlop: boolean,
23 + isTouched: boolean,
24 +};
25 +
26 +// In the case we don't have PointerEvents (Safari), we listen to touch events
27 +// too
28 +if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
29 + targetEventTypes.push('touchstart', 'mouseover', 'mouseout');
30 +}
31 +
32 +function dispatchHoverInEvents(
33 + context: EventResponderContext,
34 + props: Object,
35 + state: HoverState,
36 +): void {
37 + const {event, eventTarget} = context;
38 + if (props.onHoverChange) {
39 + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
40 + return;
41 + }
42 + if (props.onHoverIn) {
43 + context.dispatchEvent('hoverin', props.onHoverIn, eventTarget, true);
44 + }
45 + const hoverChangeEventListener = () => {
46 + props.onHoverChange(true);
47 + };
48 + context.dispatchEvent(
49 + 'hoverchange',
50 + hoverChangeEventListener,
51 + eventTarget,
52 + true,
53 + );
54 + }
55 +}
56 +
57 +function dispatchHoverOutEvents(context: EventResponderContext, props: Object) {
58 + const {event, eventTarget} = context;
59 + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
60 + return;
61 + }
62 + if (props.onHoverOut) {
63 + context.dispatchEvent('hoverout', props.onHoverOut, eventTarget, true);
64 + }
65 + if (props.onHoverChange) {
66 + const hoverChangeEventListener = () => {
67 + props.onHoverChange(false);
68 + };
69 + context.dispatchEvent(
70 + 'hoverchange',
71 + hoverChangeEventListener,
72 + eventTarget,
73 + true,
74 + );
75 + }
76 +}
77 +
78 +const HoverResponder = {
79 + targetEventTypes,
80 + createInitialState() {
81 + return {
82 + isHovered: false,
83 + isInHitSlop: false,
84 + isTouched: false,
85 + };
86 + },
87 + handleEvent(
88 + context: EventResponderContext,
89 + props: Object,
90 + state: HoverState,
91 + ): void {
92 + const {eventType, eventTarget, event} = context;
93 +
94 + switch (eventType) {
95 + case 'touchstart':
96 + // Touch devices don't have hover support
97 + if (!state.isTouched) {
98 + state.isTouched = true;
99 + }
100 + break;
101 + case 'pointerover':
102 + case 'mouseover': {
103 + if (
104 + !state.isHovered &&
105 + !state.isTouched &&
106 + !context.isTargetOwned(eventTarget)
107 + ) {
108 + if ((event: any).pointerType === 'touch') {
109 + state.isTouched = true;
110 + return;
111 + }
112 + if (
113 + context.isPositionWithinTouchHitTarget(
114 + (event: any).x,
115 + (event: any).y,
116 + )
117 + ) {
118 + state.isInHitSlop = true;
119 + return;
120 + }
121 + dispatchHoverInEvents(context, props, state);
122 + state.isHovered = true;
123 + }
124 + break;
125 + }
126 + case 'pointerout':
127 + case 'mouseout': {
128 + if (state.isHovered && !state.isTouched) {
129 + dispatchHoverOutEvents(context, props);
130 + state.isHovered = false;
131 + }
132 + state.isInHitSlop = false;
133 + state.isTouched = false;
134 + break;
135 + }
136 + case 'pointermove': {
137 + if (!state.isTouched) {
138 + if (state.isInHitSlop) {
139 + if (
140 + !context.isPositionWithinTouchHitTarget(
141 + (event: any).x,
142 + (event: any).y,
143 + )
144 + ) {
145 + dispatchHoverInEvents(context, props, state);
146 + state.isHovered = true;
147 + state.isInHitSlop = false;
148 + }
149 + } else if (
150 + state.isHovered &&
151 + context.isPositionWithinTouchHitTarget(
152 + (event: any).x,
153 + (event: any).y,
154 + )
155 + ) {
156 + dispatchHoverOutEvents(context, props);
157 + state.isHovered = false;
158 + state.isInHitSlop = true;
159 + }
160 + }
161 + break;
162 + }
163 + case 'pointercancel': {
164 + if (state.isHovered && !state.isTouched) {
165 + dispatchHoverOutEvents(context, props);
166 + state.isHovered = false;
167 + state.isTouched = false;
168 + }
169 + break;
170 + }
171 + }
172 + },
173 +};
174 +
175 +export default {
176 + $$typeof: REACT_EVENT_COMPONENT_TYPE,
177 + props: null,
178 + responder: HoverResponder,
179 +};
packages/react-events/src/Press.js new
+342
@@ -0,0 +1,342 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {EventResponderContext} from 'events/EventTypes';
11 +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const targetEventTypes = [
14 + {name: 'click', passive: false},
15 + {name: 'keydown', passive: false},
16 + 'pointerdown',
17 + 'pointercancel',
18 + 'contextmenu',
19 +];
20 +const rootEventTypes = ['pointerup', 'scroll'];
21 +
22 +// In the case we don't have PointerEvents (Safari), we listen to touch events
23 +// too
24 +if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
25 + targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
26 + rootEventTypes.push('mouseup');
27 +}
28 +
29 +type PressState = {
30 + defaultPrevented: boolean,
31 + isAnchorTouched: boolean,
32 + isLongPressed: boolean,
33 + isPressed: boolean,
34 + longPressTimeout: null | TimeoutID,
35 + pressTarget: null | EventTarget,
36 + shouldSkipMouseAfterTouch: boolean,
37 +};
38 +
39 +function dispatchPressEvent(
40 + context: EventResponderContext,
41 + name: string,
42 + state: PressState,
43 + listener: (e: Object) => void,
44 +): void {
45 + context.dispatchEvent(name, listener, state.pressTarget, true);
46 +}
47 +
48 +function dispatchPressInEvents(
49 + context: EventResponderContext,
50 + props: Object,
51 + state: PressState,
52 +): void {
53 + if (props.onPressIn) {
54 + context.dispatchEvent('pressin', props.onPressIn, state.pressTarget, true);
55 + }
56 + if (props.onPressChange) {
57 + const pressChangeEventListener = () => {
58 + props.onPressChange(true);
59 + };
60 + context.dispatchEvent(
61 + 'presschange',
62 + pressChangeEventListener,
63 + state.pressTarget,
64 + true,
65 + );
66 + }
67 + if (!state.isLongPressed && (props.onLongPress || props.onLongPressChange)) {
68 + const longPressDelay = props.longPressDelay || 1000;
69 + state.longPressTimeout = setTimeout(() => {
70 + state.isLongPressed = true;
71 + state.longPressTimeout = null;
72 + if (props.onLongPressChange) {
73 + const longPressChangeEventListener = () => {
74 + props.onLongPressChange(true);
75 + };
76 + context.dispatchEvent(
77 + 'longpresschange',
78 + longPressChangeEventListener,
79 + state.pressTarget,
80 + true,
81 + );
82 + }
83 + }, longPressDelay);
84 + }
85 +}
86 +
87 +function dispatchPressOutEvents(
88 + context: EventResponderContext,
89 + props: Object,
90 + state: PressState,
91 +): void {
92 + if (state.longPressTimeout !== null) {
93 + clearTimeout(state.longPressTimeout);
94 + state.longPressTimeout = null;
95 + }
96 + if (props.onPressOut) {
97 + context.dispatchEvent(
98 + 'pressout',
99 + props.onPressOut,
100 + state.pressTarget,
101 + true,
102 + );
103 + }
104 + if (props.onPressChange) {
105 + const pressChangeEventListener = () => {
106 + props.onPressChange(false);
107 + };
108 + context.dispatchEvent(
109 + 'presschange',
110 + pressChangeEventListener,
111 + state.pressTarget,
112 + true,
113 + );
114 + }
115 + if (props.onLongPressChange && state.isLongPressed) {
116 + const longPressChangeEventListener = () => {
117 + props.onLongPressChange(false);
118 + };
119 + context.dispatchEvent(
120 + 'longpresschange',
121 + longPressChangeEventListener,
122 + state.pressTarget,
123 + true,
124 + );
125 + }
126 +}
127 +
128 +function isAnchorTagElement(eventTarget: EventTarget): boolean {
129 + return (eventTarget: any).nodeName === 'A';
130 +}
131 +
132 +const PressResponder = {
133 + targetEventTypes,
134 + createInitialState(): PressState {
135 + return {
136 + defaultPrevented: false,
137 + isAnchorTouched: false,
138 + isLongPressed: false,
139 + isPressed: false,
140 + longPressTimeout: null,
141 + pressTarget: null,
142 + shouldSkipMouseAfterTouch: false,
143 + };
144 + },
145 + handleEvent(
146 + context: EventResponderContext,
147 + props: Object,
148 + state: PressState,
149 + ): void {
150 + const {eventTarget, eventType, event} = context;
151 +
152 + switch (eventType) {
153 + case 'keydown': {
154 + if (!props.onPress || context.isTargetOwned(eventTarget)) {
155 + return;
156 + }
157 + const isValidKeyPress =
158 + (event: any).which === 13 ||
159 + (event: any).which === 32 ||
160 + (event: any).keyCode === 13;
161 +
162 + if (!isValidKeyPress) {
163 + return;
164 + }
165 + let keyPressEventListener = props.onPress;
166 +
167 + // Wrap listener with prevent default behaviour, unless
168 + // we are dealing with an anchor. Anchor tags are special beacuse
169 + // we need to use the "click" event, to properly allow browser
170 + // heuristics for cancelling link clicks. Furthermore, iOS and
171 + // Android can show previous of anchor tags that requires working
172 + // with click rather than touch events (and mouse down/up).
173 + if (!isAnchorTagElement(eventTarget)) {
174 + keyPressEventListener = (e, key) => {
175 + if (!e.isDefaultPrevented() && !e.nativeEvent.defaultPrevented) {
176 + e.preventDefault();
177 + state.defaultPrevented = true;
178 + props.onPress(e, key);
179 + }
180 + };
181 + }
182 + dispatchPressEvent(context, 'press', state, keyPressEventListener);
183 + break;
184 + }
185 + case 'touchstart':
186 + // Touch events are for Safari, which lack pointer event support.
187 + if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
188 + // We bail out of polyfilling anchor tags, given the same heuristics
189 + // explained above in regards to needing to use click events.
190 + if (isAnchorTagElement(eventTarget)) {
191 + state.isAnchorTouched = true;
192 + return;
193 + }
194 + state.pressTarget = eventTarget;
195 + dispatchPressInEvents(context, props, state);
196 + state.isPressed = true;
197 + context.addRootEventTypes(rootEventTypes);
198 + }
199 +
200 + break;
201 + case 'touchend': {
202 + // Touch events are for Safari, which lack pointer event support
203 + if (state.isAnchorTouched) {
204 + return;
205 + }
206 + if (state.isPressed) {
207 + dispatchPressOutEvents(context, props, state);
208 + if (
209 + eventType !== 'touchcancel' &&
210 + (props.onPress || props.onLongPress)
211 + ) {
212 + // Find if the X/Y of the end touch is still that of the original target
213 + const changedTouch = (event: any).changedTouches[0];
214 + const doc = (eventTarget: any).ownerDocument;
215 + const target = doc.elementFromPoint(
216 + changedTouch.screenX,
217 + changedTouch.screenY,
218 + );
219 + if (
220 + target !== null &&
221 + context.isTargetWithinEventComponent(target)
222 + ) {
223 + if (state.isLongPressed && props.onLongPress) {
224 + dispatchPressEvent(
225 + context,
226 + 'longpress',
227 + state,
228 + props.onLongPress,
229 + );
230 + } else if (props.onPress) {
231 + dispatchPressEvent(context, 'press', state, props.onPress);
232 + }
233 + }
234 + }
235 + state.isPressed = false;
236 + state.isLongPressed = false;
237 + state.shouldSkipMouseAfterTouch = true;
238 + context.removeRootEventTypes(rootEventTypes);
239 + }
240 + break;
241 + }
242 + case 'pointerdown':
243 + case 'mousedown': {
244 + if (
245 + !state.isPressed &&
246 + !context.isTargetOwned(eventTarget) &&
247 + !state.shouldSkipMouseAfterTouch
248 + ) {
249 + if ((event: any).pointerType === 'mouse') {
250 + // Ignore if we are pressing on hit slop area with mouse
251 + if (
252 + context.isPositionWithinTouchHitTarget(
253 + (event: any).x,
254 + (event: any).y,
255 + )
256 + ) {
257 + return;
258 + }
259 + // Ignore right-clicks
260 + if (event.button === 2 || event.button === 1) {
261 + return;
262 + }
263 + }
264 + state.pressTarget = eventTarget;
265 + dispatchPressInEvents(context, props, state);
266 + state.isPressed = true;
267 + context.addRootEventTypes(rootEventTypes);
268 + }
269 + break;
270 + }
271 + case 'mouseup':
272 + case 'pointerup': {
273 + if (state.isPressed) {
274 + if (state.shouldSkipMouseAfterTouch) {
275 + state.shouldSkipMouseAfterTouch = false;
276 + return;
277 + }
278 + dispatchPressOutEvents(context, props, state);
279 + if (
280 + state.pressTarget !== null &&
281 + (props.onPress || props.onLongPress)
282 + ) {
283 + if (context.isTargetWithinElement(eventTarget, state.pressTarget)) {
284 + if (state.isLongPressed && props.onLongPress) {
285 + const longPressEventListener = e => {
286 + props.onLongPress(e);
287 + if (e.nativeEvent.defaultPrevented) {
288 + state.defaultPrevented = true;
289 + }
290 + };
291 + dispatchPressEvent(
292 + context,
293 + 'longpress',
294 + state,
295 + longPressEventListener,
296 + );
297 + } else if (props.onPress) {
298 + const pressEventListener = (e, key) => {
299 + props.onPress(e, key);
300 + if (e.nativeEvent.defaultPrevented) {
301 + state.defaultPrevented = true;
302 + }
303 + };
304 + dispatchPressEvent(context, 'press', state, pressEventListener);
305 + }
306 + }
307 + }
308 + state.isPressed = false;
309 + state.isLongPressed = false;
310 + context.removeRootEventTypes(rootEventTypes);
311 + }
312 + state.isAnchorTouched = false;
313 + break;
314 + }
315 + case 'scroll':
316 + case 'touchcancel':
317 + case 'contextmenu':
318 + case 'pointercancel': {
319 + if (state.isPressed) {
320 + state.shouldSkipMouseAfterTouch = false;
321 + dispatchPressOutEvents(context, props, state);
322 + state.isPressed = false;
323 + state.isLongPressed = false;
324 + context.removeRootEventTypes(rootEventTypes);
325 + }
326 + break;
327 + }
328 + case 'click': {
329 + if (state.defaultPrevented) {
330 + (event: any).preventDefault();
331 + state.defaultPrevented = false;
332 + }
333 + }
334 + }
335 + },
336 +};
337 +
338 +export default {
339 + $$typeof: REACT_EVENT_COMPONENT_TYPE,
340 + props: null,
341 + responder: PressResponder,
342 +};
packages/react-events/src/__tests__/Press-test.internal.js new
+129
@@ -0,0 +1,129 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactFeatureFlags;
14 +let ReactDOM;
15 +let Press;
16 +
17 +describe('Press event responder', () => {
18 + let container;
19 +
20 + beforeEach(() => {
21 + jest.resetModules();
22 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 + ReactFeatureFlags.enableEventAPI = true;
24 + React = require('react');
25 + ReactDOM = require('react-dom');
26 + Press = require('react-events/press');
27 +
28 + container = document.createElement('div');
29 + document.body.appendChild(container);
30 + });
31 +
32 + afterEach(() => {
33 + document.body.removeChild(container);
34 + container = null;
35 + });
36 +
37 + it('should support onPress', () => {
38 + let buttonRef = React.createRef();
39 + let events = [];
40 +
41 + function handleOnPress1() {
42 + events.push('press 1');
43 + }
44 +
45 + function handleOnPress2() {
46 + events.push('press 2');
47 + }
48 +
49 + function handleOnMouseDown() {
50 + events.push('mousedown');
51 + }
52 +
53 + function handleKeyDown() {
54 + events.push('keydown');
55 + }
56 +
57 + function Component() {
58 + return (
59 + <Press onPress={handleOnPress1}>
60 + <Press onPress={handleOnPress2}>
61 + <button
62 + ref={buttonRef}
63 + onMouseDown={handleOnMouseDown}
64 + onKeyDown={handleKeyDown}>
65 + Press me!
66 + </button>
67 + </Press>
68 + </Press>
69 + );
70 + }
71 +
72 + ReactDOM.render(<Component />, container);
73 +
74 + const mouseDownEvent = document.createEvent('Event');
75 + mouseDownEvent.initEvent('mousedown', true, true);
76 + buttonRef.current.dispatchEvent(mouseDownEvent);
77 +
78 + const mouseUpEvent = document.createEvent('Event');
79 + mouseUpEvent.initEvent('mouseup', true, true);
80 + buttonRef.current.dispatchEvent(mouseUpEvent);
81 +
82 + expect(events).toEqual(['mousedown', 'press 2', 'press 1']);
83 +
84 + events = [];
85 + const keyDownEvent = new KeyboardEvent('keydown', {
86 + which: 13,
87 + keyCode: 13,
88 + bubbles: true,
89 + cancelable: true,
90 + });
91 + buttonRef.current.dispatchEvent(keyDownEvent);
92 +
93 + // press 1 should not occur as press 2 will preventDefault
94 + expect(events).toEqual(['keydown', 'press 2']);
95 + });
96 +
97 + it('should support onPressIn and onPressOut', () => {
98 + let divRef = React.createRef();
99 + let events = [];
100 +
101 + function handleOnPressIn() {
102 + events.push('onPressIn');
103 + }
104 +
105 + function handleOnPressOut() {
106 + events.push('onPressOut');
107 + }
108 +
109 + function Component() {
110 + return (
111 + <Press onPressIn={handleOnPressIn} onPressOut={handleOnPressOut}>
112 + <div ref={divRef}>Press me!</div>
113 + </Press>
114 + );
115 + }
116 +
117 + ReactDOM.render(<Component />, container);
118 +
119 + const pointerEnterEvent = document.createEvent('Event');
120 + pointerEnterEvent.initEvent('pointerdown', true, true);
121 + divRef.current.dispatchEvent(pointerEnterEvent);
122 +
123 + const pointerLeaveEvent = document.createEvent('Event');
124 + pointerLeaveEvent.initEvent('pointerup', true, true);
125 + divRef.current.dispatchEvent(pointerLeaveEvent);
126 +
127 + expect(events).toEqual(['onPressIn', 'onPressOut']);
128 + });
129 +});
scripts/rollup/bundles.js
+38 -1
@@ -464,12 +464,49 @@ const bundles = [
464
465 /******* React Events (experimental) *******/
466 {
467 - bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
467 + bundleTypes: [
468 + UMD_DEV,
469 + UMD_PROD,
470 + NODE_DEV,
471 + NODE_PROD,
472 + FB_WWW_DEV,
473 + FB_WWW_PROD,
474 + ],
475 moduleType: ISOMORPHIC,
476 entry: 'react-events',
477 global: 'ReactEvents',
478 externals: [],
479 },
480 +
481 + {
482 + bundleTypes: [
483 + UMD_DEV,
484 + UMD_PROD,
485 + NODE_DEV,
486 + NODE_PROD,
487 + FB_WWW_DEV,
488 + FB_WWW_PROD,
489 + ],
490 + moduleType: NON_FIBER_RENDERER,
491 + entry: 'react-events/press',
492 + global: 'ReactEventsPress',
493 + externals: [],
494 + },
495 +
496 + {
497 + bundleTypes: [
498 + UMD_DEV,
499 + UMD_PROD,
500 + NODE_DEV,
501 + NODE_PROD,
502 + FB_WWW_DEV,
503 + FB_WWW_PROD,
504 + ],
505 + moduleType: NON_FIBER_RENDERER,
506 + entry: 'react-events/hover',
507 + global: 'ReactEventsHover',
508 + externals: [],
509 + },
510 ];
511
512 // Based on deep-freeze by substack (public domain)
scripts/rollup/results.json
+68 -68
@@ -46,29 +46,29 @@
46 "filename": "react-dom.development.js",
47 "bundleType": "UMD_DEV",
48 "packageName": "react-dom",
49 - "size": 803793,
50 - "gzip": 183116
49 + "size": 832345,
50 + "gzip": 188603
51 },
52 {
53 "filename": "react-dom.production.min.js",
54 "bundleType": "UMD_PROD",
55 "packageName": "react-dom",
56 - "size": 107752,
57 - "gzip": 34911
56 + "size": 107683,
57 + "gzip": 34867
58 },
59 {
60 "filename": "react-dom.development.js",
61 "bundleType": "NODE_DEV",
62 "packageName": "react-dom",
63 - "size": 813661,
64 - "gzip": 184364
63 + "size": 826372,
64 + "gzip": 186963
65 },
66 {
67 "filename": "react-dom.production.min.js",
68 "bundleType": "NODE_PROD",
69 "packageName": "react-dom",
70 - "size": 108035,
71 - "gzip": 34515
70 + "size": 107664,
71 + "gzip": 34301
72 },
73 {
74 "filename": "ReactDOM-dev.js",
@@ -88,15 +88,15 @@
88 "filename": "react-dom-test-utils.development.js",
89 "bundleType": "UMD_DEV",
90 "packageName": "react-dom",
91 - "size": 48274,
92 - "gzip": 13318
91 + "size": 48620,
92 + "gzip": 13278
93 },
94 {
95 "filename": "react-dom-test-utils.production.min.js",
96 "bundleType": "UMD_PROD",
97 "packageName": "react-dom",
98 - "size": 10511,
99 - "gzip": 3880
98 + "size": 10184,
99 + "gzip": 3732
100 },
101 {
102 "filename": "react-dom-test-utils.development.js",
@@ -123,22 +123,22 @@
123 "filename": "react-dom-unstable-native-dependencies.development.js",
124 "bundleType": "UMD_DEV",
125 "packageName": "react-dom",
126 - "size": 62061,
127 - "gzip": 16285
126 + "size": 62190,
127 + "gzip": 16206
128 },
129 {
130 "filename": "react-dom-unstable-native-dependencies.production.min.js",
131 "bundleType": "UMD_PROD",
132 "packageName": "react-dom",
133 - "size": 11266,
134 - "gzip": 3889
133 + "size": 10936,
134 + "gzip": 3741
135 },
136 {
137 "filename": "react-dom-unstable-native-dependencies.development.js",
138 "bundleType": "NODE_DEV",
139 "packageName": "react-dom",
140 - "size": 61643,
141 - "gzip": 16033
140 + "size": 61854,
141 + "gzip": 16078
142 },
143 {
144 "filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -165,29 +165,29 @@
165 "filename": "react-dom-server.browser.development.js",
166 "bundleType": "UMD_DEV",
167 "packageName": "react-dom",
168 - "size": 133264,
169 - "gzip": 35517
168 + "size": 136840,
169 + "gzip": 36205
170 },
171 {
172 "filename": "react-dom-server.browser.production.min.js",
173 "bundleType": "UMD_PROD",
174 "packageName": "react-dom",
175 - "size": 19666,
176 - "gzip": 7443
175 + "size": 19363,
176 + "gzip": 7290
177 },
178 {
179 "filename": "react-dom-server.browser.development.js",
180 "bundleType": "NODE_DEV",
181 "packageName": "react-dom",
182 - "size": 132758,
183 - "gzip": 35195
182 + "size": 132878,
183 + "gzip": 35237
184 },
185 {
186 "filename": "react-dom-server.browser.production.min.js",
187 "bundleType": "NODE_PROD",
188 "packageName": "react-dom",
189 - "size": 19756,
190 - "gzip": 7540
189 + "size": 19287,
190 + "gzip": 7290
191 },
192 {
193 "filename": "ReactDOMServer-dev.js",
@@ -207,15 +207,15 @@
207 "filename": "react-dom-server.node.development.js",
208 "bundleType": "NODE_DEV",
209 "packageName": "react-dom",
210 - "size": 134747,
211 - "gzip": 35752
210 + "size": 134867,
211 + "gzip": 35791
212 },
213 {
214 "filename": "react-dom-server.node.production.min.js",
215 "bundleType": "NODE_PROD",
216 "packageName": "react-dom",
217 - "size": 20639,
218 - "gzip": 7850
217 + "size": 20170,
218 + "gzip": 7598
219 },
220 {
221 "filename": "react-art.development.js",
@@ -515,50 +515,50 @@
515 "filename": "ReactDOM-dev.js",
516 "bundleType": "FB_WWW_DEV",
517 "packageName": "react-dom",
518 - "size": 821918,
519 - "gzip": 182691
518 + "size": 851672,
519 + "gzip": 188651
520 },
521 {
522 "filename": "ReactDOM-prod.js",
523 "bundleType": "FB_WWW_PROD",
524 "packageName": "react-dom",
525 - "size": 333267,
526 - "gzip": 61003
525 + "size": 339041,
526 + "gzip": 62418
527 },
528 {
529 "filename": "ReactTestUtils-dev.js",
530 "bundleType": "FB_WWW_DEV",
531 "packageName": "react-dom",
532 - "size": 44870,
533 - "gzip": 12188
532 + "size": 46251,
533 + "gzip": 12476
534 },
535 {
536 "filename": "ReactDOMUnstableNativeDependencies-dev.js",
537 "bundleType": "FB_WWW_DEV",
538 "packageName": "react-dom",
539 - "size": 59002,
540 - "gzip": 14967
539 + "size": 60296,
540 + "gzip": 15251
541 },
542 {
543 "filename": "ReactDOMUnstableNativeDependencies-prod.js",
544 "bundleType": "FB_WWW_PROD",
545 "packageName": "react-dom",
546 - "size": 26900,
547 - "gzip": 5426
546 + "size": 26767,
547 + "gzip": 5381
548 },
549 {
550 "filename": "ReactDOMServer-dev.js",
551 "bundleType": "FB_WWW_DEV",
552 "packageName": "react-dom",
553 - "size": 130310,
554 - "gzip": 33947
553 + "size": 135272,
554 + "gzip": 35002
555 },
556 {
557 "filename": "ReactDOMServer-prod.js",
558 "bundleType": "FB_WWW_PROD",
559 "packageName": "react-dom",
560 - "size": 46994,
561 - "gzip": 10956
560 + "size": 46877,
561 + "gzip": 10879
562 },
563 {
564 "filename": "ReactART-dev.js",
@@ -718,8 +718,8 @@
718 "filename": "react-dom.profiling.min.js",
719 "bundleType": "NODE_PROFILING",
720 "packageName": "react-dom",
721 - "size": 111211,
722 - "gzip": 35133
721 + "size": 110839,
722 + "gzip": 34944
723 },
724 {
725 "filename": "ReactNativeRenderer-profiling.js",
@@ -767,8 +767,8 @@
767 "filename": "ReactDOM-profiling.js",
768 "bundleType": "FB_WWW_PROFILING",
769 "packageName": "react-dom",
770 - "size": 339339,
771 - "gzip": 62379
770 + "size": 345581,
771 + "gzip": 63810
772 },
773 {
774 "filename": "ReactNativeRenderer-profiling.js",
@@ -795,8 +795,8 @@
795 "filename": "react-dom.profiling.min.js",
796 "bundleType": "UMD_PROFILING",
797 "packageName": "react-dom",
798 - "size": 110829,
799 - "gzip": 35621
798 + "size": 110730,
799 + "gzip": 35485
800 },
801 {
802 "filename": "scheduler-tracing.development.js",
@@ -1033,64 +1033,64 @@
1033 "filename": "react-dom-unstable-fire.development.js",
1034 "bundleType": "UMD_DEV",
1035 "packageName": "react-dom",
1036 - "size": 804147,
1037 - "gzip": 183253
1036 + "size": 832471,
1037 + "gzip": 188731
1038 },
1039 {
1040 "filename": "react-dom-unstable-fire.production.min.js",
1041 "bundleType": "UMD_PROD",
1042 "packageName": "react-dom",
1043 - "size": 107767,
1044 - "gzip": 34920
1043 + "size": 107698,
1044 + "gzip": 34877
1045 },
1046 {
1047 "filename": "react-dom-unstable-fire.profiling.min.js",
1048 "bundleType": "UMD_PROFILING",
1049 "packageName": "react-dom",
1050 - "size": 110844,
1051 - "gzip": 35630
1050 + "size": 110745,
1051 + "gzip": 35493
1052 },
1053 {
1054 "filename": "react-dom-unstable-fire.development.js",
1055 "bundleType": "NODE_DEV",
1056 "packageName": "react-dom",
1057 - "size": 814014,
1058 - "gzip": 184503
1057 + "size": 826725,
1058 + "gzip": 187104
1059 },
1060 {
1061 "filename": "react-dom-unstable-fire.production.min.js",
1062 "bundleType": "NODE_PROD",
1063 "packageName": "react-dom",
1064 - "size": 108049,
1065 - "gzip": 34524
1064 + "size": 107678,
1065 + "gzip": 34310
1066 },
1067 {
1068 "filename": "react-dom-unstable-fire.profiling.min.js",
1069 "bundleType": "NODE_PROFILING",
1070 "packageName": "react-dom",
1071 - "size": 111225,
1072 - "gzip": 35142
1071 + "size": 110853,
1072 + "gzip": 34953
1073 },
1074 {
1075 "filename": "ReactFire-dev.js",
1076 "bundleType": "FB_WWW_DEV",
1077 "packageName": "react-dom",
1078 - "size": 821109,
1079 - "gzip": 182607
1078 + "size": 850863,
1079 + "gzip": 188551
1080 },
1081 {
1082 "filename": "ReactFire-prod.js",
1083 "bundleType": "FB_WWW_PROD",
1084 "packageName": "react-dom",
1085 - "size": 321531,
1086 - "gzip": 58583
1085 + "size": 327881,
1086 + "gzip": 60185
1087 },
1088 {
1089 "filename": "ReactFire-profiling.js",
1090 "bundleType": "FB_WWW_PROFILING",
1091 "packageName": "react-dom",
1092 - "size": 327694,
1093 - "gzip": 59916
1092 + "size": 334366,
1093 + "gzip": 61586
1094 },
1095 {
1096 "filename": "jest-mock-scheduler.development.js",