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",