mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Adds experimental event component responder surfaces (#15228)
* Adds Press and Hover event modules + more features to the Event Responder System
This commit is contained in:
@@ -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);
|
||||
|
||||
37
packages/events/EventTypes.js
Normal file
37
packages/events/EventTypes.js
Normal file
@@ -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<string>) => void,
|
||||
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
|
||||
requestOwnership: (target: EventTarget | null) => boolean,
|
||||
releaseOwnership: (target: EventTarget | null) => boolean,
|
||||
};
|
||||
@@ -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<ReactEventResponderEventType>,
|
||||
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;
|
||||
@@ -1323,7 +1322,7 @@ export function listenToEventResponderEvents(
|
||||
// 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)) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,29 +13,39 @@ 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,
|
||||
import {enableEventAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const rootEventTypesToEventComponents: Map<
|
||||
DOMTopLevelEventType | string,
|
||||
Set<Fiber>,
|
||||
> = new Map();
|
||||
const targetEventTypeCached: Map<
|
||||
Array<ReactEventResponderEventType>,
|
||||
Set<DOMTopLevelEventType>,
|
||||
> = new 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,
|
||||
@@ -44,13 +54,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 {
|
||||
@@ -61,12 +72,6 @@ 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.dispatchEvent = function(
|
||||
eventName: string,
|
||||
eventListener: EventListener,
|
||||
@@ -88,80 +93,155 @@ DOMEventResponderContext.prototype.dispatchEvent = function(
|
||||
syntheticEvent._dispatchInstances = [eventTargetFiber];
|
||||
syntheticEvent._dispatchListeners = [eventListener];
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype._runEventsInBatch = function(): void {
|
||||
if (this._discreteEvents !== null) {
|
||||
interactiveUpdates(() => {
|
||||
runEventsInBatch(this._discreteEvents);
|
||||
});
|
||||
}
|
||||
if (this._nonDiscreteEvents !== null) {
|
||||
runEventsInBatch(this._nonDiscreteEvents);
|
||||
}
|
||||
};
|
||||
|
||||
function createValidEventTypeSet(targetEventTypes): Set<DOMTopLevelEventType> {
|
||||
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];
|
||||
|
||||
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,
|
||||
);
|
||||
if (this._isBatching) {
|
||||
let events;
|
||||
if (discrete) {
|
||||
events = this._discreteEvents;
|
||||
if (events === null) {
|
||||
events = this._discreteEvents = [];
|
||||
}
|
||||
const targetEventConfigObject = ((targetEventType: any): {
|
||||
name: DOMTopLevelEventType,
|
||||
passive?: boolean,
|
||||
capture?: boolean,
|
||||
} else {
|
||||
events = this._nonDiscreteEvents;
|
||||
if (events === null) {
|
||||
events = this._nonDiscreteEvents = [];
|
||||
}
|
||||
}
|
||||
events.push(syntheticEvent);
|
||||
} else {
|
||||
if (discrete) {
|
||||
interactiveUpdates(() => {
|
||||
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
|
||||
});
|
||||
eventTypeSet.add(targetEventConfigObject.name);
|
||||
} else {
|
||||
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
|
||||
}
|
||||
}
|
||||
return eventTypeSet;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
|
||||
target: AnyNativeEvent,
|
||||
): boolean {
|
||||
const eventFiber = this._fiber;
|
||||
|
||||
if (target != null) {
|
||||
let fiber = getClosestInstanceFromNode(target);
|
||||
while (fiber !== null) {
|
||||
if (fiber === eventFiber || fiber === eventFiber.alternate) {
|
||||
return true;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetWithinElement = function(
|
||||
childTarget: EventTarget,
|
||||
parentTarget: EventTarget,
|
||||
): boolean {
|
||||
const childFiber = getClosestInstanceFromNode(childTarget);
|
||||
const parentFiber = getClosestInstanceFromNode(parentTarget);
|
||||
|
||||
let currentFiber = childFiber;
|
||||
while (currentFiber !== null) {
|
||||
if (currentFiber === parentFiber) {
|
||||
return true;
|
||||
}
|
||||
currentFiber = currentFiber.return;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.addRootEventTypes = function(
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.removeRootEventTypes = function(
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
): void {
|
||||
const eventComponent = this._fiber;
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponents = rootEventTypesToEventComponents.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponents !== undefined) {
|
||||
rootEventComponents.delete(eventComponent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
|
||||
// TODO
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetOwned = function() {
|
||||
// TODO
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.requestOwnership = function() {
|
||||
// TODO
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.releaseOwnership = function() {
|
||||
// TODO
|
||||
};
|
||||
|
||||
function getTargetEventTypes(
|
||||
eventTypes: Array<ReactEventResponderEventType>,
|
||||
): Set<DOMTopLevelEventType> {
|
||||
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: any): DOMTopLevelEventType));
|
||||
}
|
||||
targetEventTypeCached.set(eventTypes, cachedSet);
|
||||
}
|
||||
return cachedSet;
|
||||
}
|
||||
|
||||
function handleTopLevelType(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
isRootLevelEvent: boolean,
|
||||
): void {
|
||||
const responder: ReactEventResponder = fiber.type.responder;
|
||||
if (!isRootLevelEvent) {
|
||||
// Validate the target event type exists on the responder
|
||||
const targetEventTypes = getTargetEventTypes(responder.targetEventTypes);
|
||||
if (!targetEventTypes.has(topLevelType)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
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 (state === null && responder.createInitialState !== undefined) {
|
||||
state = fiber.stateNode.state = responder.createInitialState(props);
|
||||
}
|
||||
@@ -177,19 +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);
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
context._runEventsInBatch();
|
||||
}
|
||||
|
||||
14
packages/react-events/hover.js
vendored
Normal file
14
packages/react-events/hover.js
vendored
Normal file
@@ -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';
|
||||
|
||||
const Hover = require('./src/Hover');
|
||||
|
||||
module.exports = Hover.default || Hover;
|
||||
7
packages/react-events/npm/hover.js
vendored
Normal file
7
packages/react-events/npm/hover.js
vendored
Normal file
@@ -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');
|
||||
}
|
||||
7
packages/react-events/npm/press.js
vendored
Normal file
7
packages/react-events/npm/press.js
vendored
Normal file
@@ -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');
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"press.js",
|
||||
"build-info.json",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
|
||||
14
packages/react-events/press.js
vendored
Normal file
14
packages/react-events/press.js
vendored
Normal file
@@ -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';
|
||||
|
||||
const Press = require('./src/Press');
|
||||
|
||||
module.exports = Press.default || Press;
|
||||
179
packages/react-events/src/Hover.js
vendored
Normal file
179
packages/react-events/src/Hover.js
vendored
Normal file
@@ -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,
|
||||
};
|
||||
342
packages/react-events/src/Press.js
vendored
Normal file
342
packages/react-events/src/Press.js
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* 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', 'touchend', 'mousedown', 'touchcancel');
|
||||
rootEventTypes.push('mouseup');
|
||||
}
|
||||
|
||||
type PressState = {
|
||||
defaultPrevented: boolean,
|
||||
isAnchorTouched: boolean,
|
||||
isLongPressed: boolean,
|
||||
isPressed: boolean,
|
||||
longPressTimeout: null | TimeoutID,
|
||||
pressTarget: null | EventTarget,
|
||||
shouldSkipMouseAfterTouch: boolean,
|
||||
};
|
||||
|
||||
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, true);
|
||||
}
|
||||
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,
|
||||
true,
|
||||
);
|
||||
}
|
||||
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,
|
||||
shouldSkipMouseAfterTouch: false,
|
||||
};
|
||||
},
|
||||
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. 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) {
|
||||
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, given the same heuristics
|
||||
// explained above in regards to needing to use click events.
|
||||
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;
|
||||
state.shouldSkipMouseAfterTouch = true;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pointerdown':
|
||||
case 'mousedown': {
|
||||
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 (
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Ignore right-clicks
|
||||
if (event.button === 2 || event.button === 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
dispatchPressInEvents(context, props, state);
|
||||
state.isPressed = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'mouseup':
|
||||
case 'pointerup': {
|
||||
if (state.isPressed) {
|
||||
if (state.shouldSkipMouseAfterTouch) {
|
||||
state.shouldSkipMouseAfterTouch = false;
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
state.shouldSkipMouseAfterTouch = false;
|
||||
dispatchPressOutEvents(context, props, state);
|
||||
state.isPressed = false;
|
||||
state.isLongPressed = false;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'click': {
|
||||
if (state.defaultPrevented) {
|
||||
(event: any).preventDefault();
|
||||
state.defaultPrevented = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
$$typeof: REACT_EVENT_COMPONENT_TYPE,
|
||||
props: null,
|
||||
responder: PressResponder,
|
||||
};
|
||||
129
packages/react-events/src/__tests__/Press-test.internal.js
Normal file
129
packages/react-events/src/__tests__/Press-test.internal.js
Normal file
@@ -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 (
|
||||
<Press onPress={handleOnPress1}>
|
||||
<Press onPress={handleOnPress2}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onMouseDown={handleOnMouseDown}
|
||||
onKeyDown={handleKeyDown}>
|
||||
Press me!
|
||||
</button>
|
||||
</Press>
|
||||
</Press>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Component />, 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 onPressIn={handleOnPressIn} onPressOut={handleOnPressOut}>
|
||||
<div ref={divRef}>Press me!</div>
|
||||
</Press>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Component />, 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']);
|
||||
});
|
||||
});
|
||||
@@ -464,12 +464,49 @@ 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',
|
||||
externals: [],
|
||||
},
|
||||
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user